diff --git a/changelog.d/7546.feature b/changelog.d/7546.feature
new file mode 100644
index 0000000000..94450082c9
--- /dev/null
+++ b/changelog.d/7546.feature
@@ -0,0 +1 @@
+[Device Manager] Toggle IP address visibility
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 0e2b2bef94..f1d5bfbcad 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3356,6 +3356,8 @@
- Sign out of %1$d session
- Sign out of %1$d sessions
+ Show IP address
+ Hide IP address
Sign out of this session
Session details
Application, device, and activity information.
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 504c587b8d..ad9c16c214 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -57,5 +57,7 @@
+
+ false
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 9f40a7cede..447038d768 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -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"
@@ -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)
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
index 21cbb86e94..6f002359c8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
@@ -29,4 +29,5 @@ sealed class DevicesAction : VectorViewModelAction {
object VerifyCurrentSession : DevicesAction()
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
object MultiSignoutOtherSessions : DevicesAction()
+ object ToggleIpAddressVisibility : DevicesAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index cd97795b69..f42d5af398 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -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
@@ -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
@@ -49,7 +51,12 @@ class DevicesViewModel @AssistedInject constructor(
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
private val pendingAuthHandler: PendingAuthHandler,
refreshDevicesUseCase: RefreshDevicesUseCase,
-) : VectorSessionsListViewModel(initialState, activeSessionHolder, refreshDevicesUseCase) {
+ private val vectorPreferences: VectorPreferences,
+ private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
+) : VectorSessionsListViewModel(initialState, activeSessionHolder, refreshDevicesUseCase),
+ SharedPreferences.OnSharedPreferenceChangeListener {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory {
@@ -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() {
@@ -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()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
index e8bed35e24..e0531c34dc 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
@@ -27,4 +27,5 @@ data class DevicesViewState(
val unverifiedSessionsCount: Int = 0,
val inactiveSessionsCount: Int = 0,
val isLoading: Boolean = false,
+ val isShowingIpAddress: Boolean = false,
) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCase.kt
new file mode 100644
index 0000000000..1e1dc19c96
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCase.kt
@@ -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(
+ private val vectorPreferences: VectorPreferences,
+) {
+
+ fun execute() {
+ val currentVisibility = vectorPreferences.showIpAddressInSessionManagerScreens()
+ vectorPreferences.setIpAddressVisibilityInDeviceManagerScreens(!currentVisibility)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 3a3c3463fb..b27d8a7270 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -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)
@@ -240,7 +248,7 @@ class VectorSettingsDevicesFragment :
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo)
- renderOtherSessionsView(otherDevices)
+ renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
} else {
hideSecurityRecommendations()
hideCurrentSessionView()
@@ -297,7 +305,7 @@ class VectorSettingsDevicesFragment :
hideInactiveSessionsRecommendation()
}
- private fun renderOtherSessionsView(otherDevices: List?) {
+ private fun renderOtherSessionsView(otherDevices: List?, isShowingIpAddress: Boolean) {
if (otherDevices.isNullOrEmpty()) {
hideOtherSessionsView()
} else {
@@ -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() {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
index de1cd33d35..9d9cb15c28 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
@@ -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
@@ -69,6 +70,9 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la
@EpoxyAttribute
var selected: Boolean = false
+ @EpoxyAttribute
+ var ipAddress: String? = null
+
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null
@@ -100,6 +104,7 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la
holder.otherSessionDescriptionTextView.setTextColor(it)
}
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
+ holder.otherSessionIpAddressTextView.setTextOrHide(ipAddress)
holder.otherSessionItemBackgroundView.isSelected = selected
}
@@ -108,6 +113,7 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la
val otherSessionVerificationStatusImageView by bind(R.id.otherSessionVerificationStatusImageView)
val otherSessionNameTextView by bind(R.id.otherSessionNameTextView)
val otherSessionDescriptionTextView by bind(R.id.otherSessionDescriptionTextView)
+ val otherSessionIpAddressTextView by bind(R.id.otherSessionIpAddressTextView)
val otherSessionItemBackgroundView by bind(R.id.otherSessionItemBackground)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
index 8d70552101..5e2549f42a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
@@ -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)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index 3d9c3a8f37..c6044d04a4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -76,6 +76,7 @@ class SessionInfoView @JvmOverloads constructor(
sessionInfoViewState.deviceFullInfo.isInactive,
sessionInfoViewState.deviceFullInfo.deviceInfo,
sessionInfoViewState.isLastSeenDetailsVisible,
+ sessionInfoViewState.isShowingIpAddress,
dateFormatter,
drawableProvider,
colorProvider,
@@ -157,6 +158,7 @@ class SessionInfoView @JvmOverloads constructor(
isInactive: Boolean,
deviceInfo: DeviceInfo,
isLastSeenDetailsVisible: Boolean,
+ isShowingIpAddress: Boolean,
dateFormatter: VectorDateFormatter,
drawableProvider: DrawableProvider,
colorProvider: ColorProvider,
@@ -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) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
index 287bb956f5..5d3c4b4f4b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -25,4 +25,5 @@ data class SessionInfoViewState(
val isDetailsButtonVisible: Boolean = true,
val isLearnMoreLinkVisible: Boolean = false,
val isLastSeenDetailsVisible: Boolean = false,
+ val isShowingIpAddress: Boolean = false,
)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt
index 24d2a08bdc..bdad65ca43 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt
@@ -33,4 +33,5 @@ sealed class OtherSessionsAction : VectorViewModelAction {
object SelectAll : OtherSessionsAction()
object DeselectAll : OtherSessionsAction()
object MultiSignout : OtherSessionsAction()
+ object ToggleIpAddressVisibility : OtherSessionsAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 74a78b2415..87330b087a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -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)
}
}
@@ -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)
@@ -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)
}
}
@@ -237,7 +251,7 @@ class OtherSessionsFragment :
toolbar?.title = title
}
- private fun renderDevices(devices: List, currentFilter: DeviceManagerFilterType) {
+ private fun renderDevices(devices: List, 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
@@ -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)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
index 9b4c26ee4f..a5282e7ba2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2.othersessions
+import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import dagger.assisted.Assisted
@@ -25,8 +26,10 @@ 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.GetDeviceFullInfoListUseCase
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.ToggleIpAddressVisibilityUseCase
import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded
@@ -42,10 +45,12 @@ class OtherSessionsViewModel @AssistedInject constructor(
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
private val signoutSessionsUseCase: SignoutSessionsUseCase,
private val pendingAuthHandler: PendingAuthHandler,
- refreshDevicesUseCase: RefreshDevicesUseCase
+ refreshDevicesUseCase: RefreshDevicesUseCase,
+ private val vectorPreferences: VectorPreferences,
+ private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
) : VectorSessionsListViewModel(
initialState, activeSessionHolder, refreshDevicesUseCase
-) {
+), SharedPreferences.OnSharedPreferenceChangeListener {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory {
@@ -58,6 +63,28 @@ class OtherSessionsViewModel @AssistedInject constructor(
init {
observeDevices(initialState.currentFilter)
+ 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 observeDevices(currentFilter: DeviceManagerFilterType) {
@@ -85,9 +112,14 @@ class OtherSessionsViewModel @AssistedInject constructor(
OtherSessionsAction.DeselectAll -> handleDeselectAll()
OtherSessionsAction.SelectAll -> handleSelectAll()
OtherSessionsAction.MultiSignout -> handleMultiSignout()
+ OtherSessionsAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
}
}
+ private fun handleToggleIpAddressVisibility() {
+ toggleIpAddressVisibilityUseCase.execute()
+ }
+
private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) {
setState {
copy(
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt
index c0b50fded8..f4dd3640ee 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt
@@ -28,6 +28,7 @@ data class OtherSessionsViewState(
val excludeCurrentDevice: Boolean = false,
val isSelectModeEnabled: Boolean = false,
val isLoading: Boolean = false,
+ val isShowingIpAddress: Boolean = false,
) : MavericksState {
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
index 9a92d5b629..2b6c40eead 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
@@ -29,4 +29,5 @@ sealed class SessionOverviewAction : VectorViewModelAction {
val deviceId: String,
val enabled: Boolean,
) : SessionOverviewAction()
+ object ToggleIpAddressVisibility : SessionOverviewAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index d722cda7a1..be60b3b805 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2.overview
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
@@ -156,16 +157,34 @@ class SessionOverviewFragment :
override fun getMenuRes() = R.menu.menu_session_overview
+ override fun handlePrepareMenu(menu: Menu) {
+ withState(viewModel) { state ->
+ menu.findItem(R.id.sessionOverviewToggleIpAddress).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)
+ }
+ }
+ }
+
override fun handleMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.sessionOverviewRename -> {
goToRenameSession()
true
}
+ R.id.sessionOverviewToggleIpAddress -> {
+ toggleIpAddressVisibility()
+ true
+ }
else -> false
}
}
+ private fun toggleIpAddressVisibility() {
+ viewModel.handle(SessionOverviewAction.ToggleIpAddressVisibility)
+ }
+
private fun goToRenameSession() = withState(viewModel) { state ->
viewNavigator.goToRenameSession(requireContext(), state.deviceId)
}
@@ -206,6 +225,7 @@ class SessionOverviewFragment :
isDetailsButtonVisible = false,
isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
isLastSeenDetailsVisible = !isCurrentSession,
+ isShowingIpAddress = viewState.isShowingIpAddress,
)
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
views.sessionOverviewInfo.onLearnMoreClickListener = {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index a56872e648..472e0a4269 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2.overview
+import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import dagger.assisted.Assisted
@@ -25,7 +26,9 @@ 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.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.ToggleIpAddressVisibilityUseCase
import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel
import im.vector.app.features.settings.devices.v2.notification.GetNotificationsStatusUseCase
import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
@@ -54,9 +57,11 @@ class SessionOverviewViewModel @AssistedInject constructor(
private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
private val getNotificationsStatusUseCase: GetNotificationsStatusUseCase,
refreshDevicesUseCase: RefreshDevicesUseCase,
+ private val vectorPreferences: VectorPreferences,
+ private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
) : VectorSessionsListViewModel(
initialState, activeSessionHolder, refreshDevicesUseCase
-) {
+), SharedPreferences.OnSharedPreferenceChangeListener {
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
@@ -70,6 +75,27 @@ class SessionOverviewViewModel @AssistedInject constructor(
observeSessionInfo(initialState.deviceId)
observeCurrentSessionInfo()
observeNotificationsStatus(initialState.deviceId)
+ 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 refreshPushers() {
@@ -111,9 +137,14 @@ class SessionOverviewViewModel @AssistedInject constructor(
is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action)
SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled()
is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action)
+ SessionOverviewAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
}
}
+ private fun handleToggleIpAddressVisibility() {
+ toggleIpAddressVisibilityUseCase.execute()
+ }
+
private fun handleVerifySessionAction() = withState { viewState ->
if (viewState.deviceInfo.invoke()?.isCurrentDevice.orFalse()) {
handleVerifyCurrentSession()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index 019dd2d724..0f66605f98 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -28,6 +28,7 @@ data class SessionOverviewViewState(
val deviceInfo: Async = Uninitialized,
val isLoading: Boolean = false,
val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED,
+ val isShowingIpAddress: Boolean = false,
) : MavericksState {
constructor(args: SessionOverviewArgs) : this(
deviceId = args.deviceId
diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml
index f514cea56b..a6205e7d50 100644
--- a/vector/src/main/res/layout/item_other_session.xml
+++ b/vector/src/main/res/layout/item_other_session.xml
@@ -13,7 +13,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_other_session"
- app:layout_constraintBottom_toBottomOf="@id/otherSessionVerificationStatusImageView"
+ app:layout_constraintBottom_toBottomOf="@id/otherSessionSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -53,11 +53,12 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
+ android:layout_marginTop="8dp"
android:ellipsize="end"
android:lines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView"
- app:layout_constraintTop_toTopOf="@id/otherSessionDeviceTypeImageView"
+ app:layout_constraintTop_toTopOf="@id/otherSessionItemBackground"
tools:text="Element Mobile: Android" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/otherSessionIpAddressTextView" />
diff --git a/vector/src/main/res/menu/menu_other_sessions.xml b/vector/src/main/res/menu/menu_other_sessions.xml
index 7893575dde..98f9dd8256 100644
--- a/vector/src/main/res/menu/menu_other_sessions.xml
+++ b/vector/src/main/res/menu/menu_other_sessions.xml
@@ -9,6 +9,11 @@
android:title="@string/device_manager_other_sessions_select"
app:showAsAction="withText|never" />
+
+
-
+
+
-
+
+
- ()
private val fakePendingAuthHandler = FakePendingAuthHandler()
private val fakeRefreshDevicesUseCase = mockk(relaxUnitFun = true)
+ private val fakeVectorPreferences = FakeVectorPreferences()
+ private val toggleIpAddressVisibilityUseCase = mockk()
private fun createViewModel(): DevicesViewModel {
return DevicesViewModel(
@@ -85,6 +90,8 @@ class DevicesViewModelTest {
interceptSignoutFlowResponseUseCase = fakeInterceptSignoutFlowResponseUseCase,
pendingAuthHandler = fakePendingAuthHandler.instance,
refreshDevicesUseCase = fakeRefreshDevicesUseCase,
+ vectorPreferences = fakeVectorPreferences.instance,
+ toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
)
}
@@ -97,6 +104,7 @@ class DevicesViewModelTest {
givenVerificationService()
givenCurrentSessionCrossSigningInfo()
givenDeviceFullInfoList(deviceId1 = A_DEVICE_ID_1, deviceId2 = A_DEVICE_ID_2)
+ fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
}
private fun givenVerificationService(): FakeVerificationService {
@@ -343,6 +351,33 @@ class DevicesViewModelTest {
}
}
+ @Test
+ fun `given the viewModel when initializing it then view state of ip address visibility is false`() {
+ // When
+ val viewModelTest = createViewModel().test()
+
+ // Then
+ viewModelTest.assertLatestState { it.isShowingIpAddress == false }
+ viewModelTest.finish()
+ }
+
+ @Test
+ fun `given the viewModel when toggleIpAddressVisibility action is triggered then view state and preference change accordingly`() {
+ // When
+ val viewModel = createViewModel()
+ val viewModelTest = viewModel.test()
+ every { toggleIpAddressVisibilityUseCase.execute() } just runs
+ every { fakeVectorPreferences.instance.setIpAddressVisibilityInDeviceManagerScreens(true) } just runs
+ every { fakeVectorPreferences.instance.showIpAddressInSessionManagerScreens() } returns true
+
+ viewModel.handle(DevicesAction.ToggleIpAddressVisibility)
+ viewModel.onSharedPreferenceChanged(null, null)
+
+ // Then
+ viewModelTest.assertLatestState { it.isShowingIpAddress == true }
+ viewModelTest.finish()
+ }
+
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
val currentSessionCrossSigningInfo = mockk()
every { currentSessionCrossSigningInfo.deviceId } returns A_CURRENT_DEVICE_ID
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
index 1e8c511c42..82f40d911d 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
@@ -22,10 +22,12 @@ import com.airbnb.mvrx.test.MavericksTestRule
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.ToggleIpAddressVisibilityUseCase
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakePendingAuthHandler
import im.vector.app.test.fakes.FakeSignoutSessionsUseCase
+import im.vector.app.test.fakes.FakeVectorPreferences
import im.vector.app.test.fakes.FakeVerificationService
import im.vector.app.test.fixtures.aDeviceFullInfo
import im.vector.app.test.test
@@ -66,6 +68,8 @@ class OtherSessionsViewModelTest {
private val fakeRefreshDevicesUseCase = mockk(relaxed = true)
private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase()
private val fakePendingAuthHandler = FakePendingAuthHandler()
+ private val fakeVectorPreferences = FakeVectorPreferences()
+ private val toggleIpAddressVisibilityUseCase = mockk()
private fun createViewModel(viewState: OtherSessionsViewState = OtherSessionsViewState(defaultArgs)) =
OtherSessionsViewModel(
@@ -75,6 +79,8 @@ class OtherSessionsViewModelTest {
signoutSessionsUseCase = fakeSignoutSessionsUseCase.instance,
pendingAuthHandler = fakePendingAuthHandler.instance,
refreshDevicesUseCase = fakeRefreshDevicesUseCase,
+ vectorPreferences = fakeVectorPreferences.instance,
+ toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
)
@Before
@@ -84,6 +90,7 @@ class OtherSessionsViewModelTest {
every { SystemClock.elapsedRealtime() } returns 1234
givenVerificationService()
+ fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
}
private fun givenVerificationService(): FakeVerificationService {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
index 1a57b76020..287bdd159c 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -22,6 +22,7 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MavericksTestRule
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.ToggleIpAddressVisibilityUseCase
import im.vector.app.features.settings.devices.v2.notification.NotificationsStatus
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
@@ -30,6 +31,7 @@ import im.vector.app.test.fakes.FakeGetNotificationsStatusUseCase
import im.vector.app.test.fakes.FakePendingAuthHandler
import im.vector.app.test.fakes.FakeSignoutSessionsUseCase
import im.vector.app.test.fakes.FakeTogglePushNotificationUseCase
+import im.vector.app.test.fakes.FakeVectorPreferences
import im.vector.app.test.fakes.FakeVerificationService
import im.vector.app.test.test
import im.vector.app.test.testDispatcher
@@ -77,6 +79,8 @@ class SessionOverviewViewModelTest {
private val togglePushNotificationUseCase = FakeTogglePushNotificationUseCase()
private val fakeGetNotificationsStatusUseCase = FakeGetNotificationsStatusUseCase()
private val notificationsStatus = NotificationsStatus.ENABLED
+ private val fakeVectorPreferences = FakeVectorPreferences()
+ private val toggleIpAddressVisibilityUseCase = mockk()
private fun createViewModel() = SessionOverviewViewModel(
initialState = SessionOverviewViewState(args),
@@ -89,6 +93,8 @@ class SessionOverviewViewModelTest {
refreshDevicesUseCase = refreshDevicesUseCase,
togglePushNotificationUseCase = togglePushNotificationUseCase.instance,
getNotificationsStatusUseCase = fakeGetNotificationsStatusUseCase.instance,
+ vectorPreferences = fakeVectorPreferences.instance,
+ toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
)
@Before
@@ -103,6 +109,7 @@ class SessionOverviewViewModelTest {
A_SESSION_ID_1,
notificationsStatus
)
+ fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
}
private fun givenVerificationService(): FakeVerificationService {
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
index 4baa7e2b90..d89764a77e 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
@@ -52,4 +52,8 @@ class FakeVectorPreferences {
fun verifySetNotificationEnabledForDevice(enabled: Boolean, inverse: Boolean = false) {
verify(inverse = inverse) { instance.setNotificationEnabledForDevice(enabled) }
}
+
+ fun givenSessionManagerShowIpAddress(showIpAddress: Boolean) {
+ every { instance.showIpAddressInSessionManagerScreens() } returns showIpAddress
+ }
}