Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle IP address visibility (PSG-860) #7546

Merged
merged 17 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7546.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Device Manager] Toggle IP address visibility
2 changes: 2 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3351,6 +3351,8 @@
<item quantity="one">Sign out of %1$d session</item>
<item quantity="other">Sign out of %1$d sessions</item>
</plurals>
<string name="device_manager_other_sessions_show_ip_address">Show IP address</string>
<string name="device_manager_other_sessions_hide_ip_address">Hide IP address</string>
<string name="device_manager_session_overview_signout">Sign out of this session</string>
<string name="device_manager_session_details_title">Session details</string>
<string name="device_manager_session_details_description">Application, device, and activity information.</string>
Expand Down
2 changes: 2 additions & 0 deletions vector-config/src/main/res/values/config-settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,7 @@

<!-- Level 1: Legals -->

<!-- Level 3: Security & Privacy, Sessions -->
<bool name="settings_session_manager_show_ip_address">false</bool>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ class VectorPreferences @Inject constructor(
private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG"
const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG"

// New Session Manager
const val SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS = "SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS"

// other
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
private const val SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY"
Expand Down Expand Up @@ -1228,4 +1231,14 @@ class VectorPreferences @Inject constructor(
return vectorFeatures.isVoiceBroadcastEnabled() &&
defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default))
}

fun showIpAddressInSessionManagerScreens(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, getDefault(R.bool.settings_session_manager_show_ip_address))
}

fun setIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
defaultPrefs.edit {
putBoolean(VectorPreferences.SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, isVisible)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ sealed class DevicesAction : VectorViewModelAction {
object VerifyCurrentSession : DevicesAction()
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
object MultiSignoutOtherSessions : DevicesAction()
object ToggleIpAddressVisibility : DevicesAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package im.vector.app.features.settings.devices.v2

import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import dagger.assisted.Assisted
Expand All @@ -25,6 +26,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.features.auth.PendingAuthHandler
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded
Expand All @@ -49,7 +51,12 @@ class DevicesViewModel @AssistedInject constructor(
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
private val pendingAuthHandler: PendingAuthHandler,
refreshDevicesUseCase: RefreshDevicesUseCase,
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
private val vectorPreferences: VectorPreferences,
private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
) : VectorSessionsListViewModel<DevicesViewState,
DevicesAction,
DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase),
SharedPreferences.OnSharedPreferenceChangeListener {

@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
Expand All @@ -63,6 +70,28 @@ class DevicesViewModel @AssistedInject constructor(
observeDevices()
refreshDevicesOnCryptoDevicesChange()
refreshDeviceList()
refreshIpAddressVisibility()
observePreferences()
}

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
refreshIpAddressVisibility()
}

private fun observePreferences() {
vectorPreferences.subscribeToChanges(this)
}

override fun onCleared() {
vectorPreferences.unsubscribeToChanges(this)
super.onCleared()
}

private fun refreshIpAddressVisibility() {
val shouldShowIpAddress = vectorPreferences.showIpAddressInSessionManagerScreens()
setState {
copy(isShowingIpAddress = shouldShowIpAddress)
}
}

private fun observeCurrentSessionCrossSigningInfo() {
Expand Down Expand Up @@ -112,9 +141,14 @@ class DevicesViewModel @AssistedInject constructor(
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions()
DevicesAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
}
}

private fun handleToggleIpAddressVisibility() {
toggleIpAddressVisibilityUseCase.execute()
}

private fun handleVerifyCurrentSessionAction() {
viewModelScope.launch {
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ data class DevicesViewState(
val unverifiedSessionsCount: Int = 0,
val inactiveSessionsCount: Int = 0,
val isLoading: Boolean = false,
val isShowingIpAddress: Boolean = false,
) : MavericksState
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.settings.devices.v2

import im.vector.app.features.settings.VectorPreferences
import javax.inject.Inject

class ToggleIpAddressVisibilityUseCase @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add unit tests for this new use case.

private val vectorPreferences: VectorPreferences,
) {

fun execute() {
val currentVisibility = vectorPreferences.showIpAddressInSessionManagerScreens()
vectorPreferences.setIpAddressVisibilityInDeviceManagerScreens(!currentVisibility)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,19 @@ class VectorSettingsDevicesFragment :
confirmMultiSignoutOtherSessions()
true
}
R.id.otherSessionsHeaderToggleIpAddress -> {
handleToggleIpAddressVisibility()
true
}
else -> false
}
}
}

private fun handleToggleIpAddressVisibility() {
viewModel.handle(DevicesAction.ToggleIpAddressVisibility)
}

private fun confirmMultiSignoutOtherSessions() {
activity?.let {
buildConfirmSignoutDialogUseCase.execute(it, this::multiSignoutOtherSessions)
Expand Down Expand Up @@ -240,7 +248,7 @@ class VectorSettingsDevicesFragment :

renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices)
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
} else {
hideSecurityRecommendations()
hideCurrentSessionView()
Expand Down Expand Up @@ -297,7 +305,7 @@ class VectorSettingsDevicesFragment :
hideInactiveSessionsRecommendation()
}

private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) {
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?, isShowingIpAddress: Boolean) {
if (otherDevices.isNullOrEmpty()) {
hideOtherSessionsView()
} else {
Expand All @@ -308,12 +316,18 @@ class VectorSettingsDevicesFragment :
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
multiSignoutItem.setTextColor(color)
views.deviceListOtherSessions.isVisible = true
val devices = if (isShowingIpAddress) otherDevices else otherDevices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
views.deviceListOtherSessions.render(
devices = otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER),
totalNumberOfDevices = otherDevices.size,
showViewAll = otherDevices.size > NUMBER_OF_OTHER_DEVICES_TO_RENDER
devices = devices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER),
totalNumberOfDevices = devices.size,
showViewAll = devices.size > NUMBER_OF_OTHER_DEVICES_TO_RENDER
)
}
views.deviceListHeaderOtherSessions.menu.findItem(R.id.otherSessionsHeaderToggleIpAddress).title = if (isShowingIpAddress) {
stringProvider.getString(R.string.device_manager_other_sessions_hide_ip_address)
} else {
stringProvider.getString(R.string.device_manager_other_sessions_show_ip_address)
}
}
}

private fun hideOtherSessionsView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider
Expand Down Expand Up @@ -69,6 +70,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
@EpoxyAttribute
var selected: Boolean = false

@EpoxyAttribute
var ipAddress: String? = null

@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null

Expand Down Expand Up @@ -100,6 +104,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
holder.otherSessionDescriptionTextView.setTextColor(it)
}
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
holder.otherSessionIpAddressTextView.setTextOrHide(ipAddress)
holder.otherSessionItemBackgroundView.isSelected = selected
}

Expand All @@ -108,6 +113,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView)
val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView)
val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView)
val otherSessionIpAddressTextView by bind<TextView>(R.id.otherSessionIpAddressTextView)
val otherSessionItemBackgroundView by bind<View>(R.id.otherSessionItemBackground)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class OtherSessionsController @Inject constructor(
sessionDescription(description)
sessionDescriptionDrawable(descriptionDrawable)
sessionDescriptionColor(descriptionColor)
ipAddress(device.deviceInfo.lastSeenIp)
stringProvider(host.stringProvider)
colorProvider(host.colorProvider)
drawableProvider(host.drawableProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class SessionInfoView @JvmOverloads constructor(
sessionInfoViewState.deviceFullInfo.isInactive,
sessionInfoViewState.deviceFullInfo.deviceInfo,
sessionInfoViewState.isLastSeenDetailsVisible,
sessionInfoViewState.isShowingIpAddress,
dateFormatter,
drawableProvider,
colorProvider,
Expand Down Expand Up @@ -157,6 +158,7 @@ class SessionInfoView @JvmOverloads constructor(
isInactive: Boolean,
deviceInfo: DeviceInfo,
isLastSeenDetailsVisible: Boolean,
isShowingIpAddress: Boolean,
dateFormatter: VectorDateFormatter,
drawableProvider: DrawableProvider,
colorProvider: ColorProvider,
Expand Down Expand Up @@ -186,7 +188,7 @@ class SessionInfoView @JvmOverloads constructor(
} else {
views.sessionInfoLastActivityTextView.isGone = true
}
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible })
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible && isShowingIpAddress })
}

private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ data class SessionInfoViewState(
val isDetailsButtonVisible: Boolean = true,
val isLearnMoreLinkVisible: Boolean = false,
val isLastSeenDetailsVisible: Boolean = false,
val isShowingIpAddress: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ sealed class OtherSessionsAction : VectorViewModelAction {
object SelectAll : OtherSessionsAction()
object DeselectAll : OtherSessionsAction()
object MultiSignout : OtherSessionsAction()
object ToggleIpAddressVisibility : OtherSessionsAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ class OtherSessionsFragment :
menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled
menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled
menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled && state.devices()?.isNotEmpty().orFalse()
menu.findItem(R.id.otherSessionsToggleIpAddress).isVisible = !isSelectModeEnabled
menu.findItem(R.id.otherSessionsToggleIpAddress).title = if (state.isShowingIpAddress) {
getString(R.string.device_manager_other_sessions_hide_ip_address)
} else {
getString(R.string.device_manager_other_sessions_show_ip_address)
}
updateMultiSignoutMenuItem(menu, state)
}
}
Expand Down Expand Up @@ -130,10 +136,18 @@ class OtherSessionsFragment :
confirmMultiSignout()
true
}
R.id.otherSessionsToggleIpAddress -> {
toggleIpAddressVisibility()
true
}
else -> false
}
}

private fun toggleIpAddressVisibility() {
viewModel.handle(OtherSessionsAction.ToggleIpAddressVisibility)
}

private fun confirmMultiSignout() {
activity?.let {
buildConfirmSignoutDialogUseCase.execute(it, this::multiSignout)
Expand Down Expand Up @@ -213,7 +227,7 @@ class OtherSessionsFragment :
updateLoading(state.isLoading)
if (state.devices is Success) {
val devices = state.devices.invoke()
renderDevices(devices, state.currentFilter)
renderDevices(devices, state.currentFilter, state.isShowingIpAddress)
updateToolbar(devices, state.isSelectModeEnabled)
}
}
Expand All @@ -237,7 +251,7 @@ class OtherSessionsFragment :
toolbar?.title = title
}

private fun renderDevices(devices: List<DeviceFullInfo>, currentFilter: DeviceManagerFilterType) {
private fun renderDevices(devices: List<DeviceFullInfo>, currentFilter: DeviceManagerFilterType, isShowingIpAddress: Boolean) {
views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
Expand Down Expand Up @@ -299,7 +313,8 @@ class OtherSessionsFragment :
} else {
views.deviceListOtherSessions.isVisible = true
views.otherSessionsNotFoundLayout.isVisible = false
views.deviceListOtherSessions.render(devices = devices, totalNumberOfDevices = devices.size, showViewAll = false)
val mappedDevices = if (isShowingIpAddress) devices else devices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
views.deviceListOtherSessions.render(devices = mappedDevices, totalNumberOfDevices = mappedDevices.size, showViewAll = false)
}
}

Expand Down
Loading