Merge pull request #7546 from vector-im/feature/ons/toggle_ip_address_visibility

Toggle IP address visibility (PSG-860)
This commit is contained in:
Onuray Sahin 2022-11-22 22:41:21 +03:00 committed by GitHub
commit 0957b38329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 309 additions and 18 deletions

1
changelog.d/7546.feature Normal file
View File

@ -0,0 +1 @@
[Device Manager] Toggle IP address visibility

View File

@ -3356,6 +3356,8 @@
<item quantity="one">Sign out of %1$d session</item> <item quantity="one">Sign out of %1$d session</item>
<item quantity="other">Sign out of %1$d sessions</item> <item quantity="other">Sign out of %1$d sessions</item>
</plurals> </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_overview_signout">Sign out of this session</string>
<string name="device_manager_session_details_title">Session details</string> <string name="device_manager_session_details_title">Session details</string>
<string name="device_manager_session_details_description">Application, device, and activity information.</string> <string name="device_manager_session_details_description">Application, device, and activity information.</string>

View File

@ -57,5 +57,7 @@
<!-- Level 1: Legals --> <!-- Level 1: Legals -->
<!-- Level 3: Security & Privacy, Sessions -->
<bool name="settings_session_manager_show_ip_address">false</bool>
</resources> </resources>

View File

@ -209,6 +209,9 @@ class VectorPreferences @Inject constructor(
private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG" 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" 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 // other
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY" 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" 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() && return vectorFeatures.isVoiceBroadcastEnabled() &&
defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default)) 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)
}
}
} }

View File

@ -29,4 +29,5 @@ sealed class DevicesAction : VectorViewModelAction {
object VerifyCurrentSession : DevicesAction() object VerifyCurrentSession : DevicesAction()
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction() data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
object MultiSignoutOtherSessions : DevicesAction() object MultiSignoutOtherSessions : DevicesAction()
object ToggleIpAddressVisibility : DevicesAction()
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2 package im.vector.app.features.settings.devices.v2
import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import dagger.assisted.Assisted 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.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.features.auth.PendingAuthHandler 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.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded
@ -49,7 +51,12 @@ class DevicesViewModel @AssistedInject constructor(
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
private val pendingAuthHandler: PendingAuthHandler, private val pendingAuthHandler: PendingAuthHandler,
refreshDevicesUseCase: RefreshDevicesUseCase, 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 @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> { interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
@ -63,6 +70,28 @@ class DevicesViewModel @AssistedInject constructor(
observeDevices() observeDevices()
refreshDevicesOnCryptoDevicesChange() refreshDevicesOnCryptoDevicesChange()
refreshDeviceList() 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() { private fun observeCurrentSessionCrossSigningInfo() {
@ -112,9 +141,14 @@ class DevicesViewModel @AssistedInject constructor(
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction() is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction() is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions() DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions()
DevicesAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
} }
} }
private fun handleToggleIpAddressVisibility() {
toggleIpAddressVisibilityUseCase.execute()
}
private fun handleVerifyCurrentSessionAction() { private fun handleVerifyCurrentSessionAction() {
viewModelScope.launch { viewModelScope.launch {
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute() val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()

View File

@ -27,4 +27,5 @@ data class DevicesViewState(
val unverifiedSessionsCount: Int = 0, val unverifiedSessionsCount: Int = 0,
val inactiveSessionsCount: Int = 0, val inactiveSessionsCount: Int = 0,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val isShowingIpAddress: Boolean = false,
) : MavericksState ) : MavericksState

View File

@ -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)
}
}

View File

@ -146,11 +146,19 @@ class VectorSettingsDevicesFragment :
confirmMultiSignoutOtherSessions() confirmMultiSignoutOtherSessions()
true true
} }
R.id.otherSessionsHeaderToggleIpAddress -> {
handleToggleIpAddressVisibility()
true
}
else -> false else -> false
} }
} }
} }
private fun handleToggleIpAddressVisibility() {
viewModel.handle(DevicesAction.ToggleIpAddressVisibility)
}
private fun confirmMultiSignoutOtherSessions() { private fun confirmMultiSignoutOtherSessions() {
activity?.let { activity?.let {
buildConfirmSignoutDialogUseCase.execute(it, this::multiSignoutOtherSessions) buildConfirmSignoutDialogUseCase.execute(it, this::multiSignoutOtherSessions)
@ -240,7 +248,7 @@ class VectorSettingsDevicesFragment :
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified) renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo) renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices) renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
} else { } else {
hideSecurityRecommendations() hideSecurityRecommendations()
hideCurrentSessionView() hideCurrentSessionView()
@ -297,7 +305,7 @@ class VectorSettingsDevicesFragment :
hideInactiveSessionsRecommendation() hideInactiveSessionsRecommendation()
} }
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) { private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?, isShowingIpAddress: Boolean) {
if (otherDevices.isNullOrEmpty()) { if (otherDevices.isNullOrEmpty()) {
hideOtherSessionsView() hideOtherSessionsView()
} else { } else {
@ -308,11 +316,17 @@ class VectorSettingsDevicesFragment :
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices) multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
multiSignoutItem.setTextColor(color) multiSignoutItem.setTextColor(color)
views.deviceListOtherSessions.isVisible = true views.deviceListOtherSessions.isVisible = true
val devices = if (isShowingIpAddress) otherDevices else otherDevices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
views.deviceListOtherSessions.render( views.deviceListOtherSessions.render(
devices = otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER), devices = devices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER),
totalNumberOfDevices = otherDevices.size, totalNumberOfDevices = devices.size,
showViewAll = otherDevices.size > NUMBER_OF_OTHER_DEVICES_TO_RENDER 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)
}
} }
} }

View File

@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick 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.ColorProvider
import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
@ -69,6 +70,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
@EpoxyAttribute @EpoxyAttribute
var selected: Boolean = false var selected: Boolean = false
@EpoxyAttribute
var ipAddress: String? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null var clickListener: ClickListener? = null
@ -100,6 +104,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
holder.otherSessionDescriptionTextView.setTextColor(it) holder.otherSessionDescriptionTextView.setTextColor(it)
} }
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null) holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
holder.otherSessionIpAddressTextView.setTextOrHide(ipAddress)
holder.otherSessionItemBackgroundView.isSelected = selected holder.otherSessionItemBackgroundView.isSelected = selected
} }
@ -108,6 +113,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView) val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView)
val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView) val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView)
val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView) val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView)
val otherSessionIpAddressTextView by bind<TextView>(R.id.otherSessionIpAddressTextView)
val otherSessionItemBackgroundView by bind<View>(R.id.otherSessionItemBackground) val otherSessionItemBackgroundView by bind<View>(R.id.otherSessionItemBackground)
} }
} }

View File

@ -72,6 +72,7 @@ class OtherSessionsController @Inject constructor(
sessionDescription(description) sessionDescription(description)
sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionDrawable(descriptionDrawable)
sessionDescriptionColor(descriptionColor) sessionDescriptionColor(descriptionColor)
ipAddress(device.deviceInfo.lastSeenIp)
stringProvider(host.stringProvider) stringProvider(host.stringProvider)
colorProvider(host.colorProvider) colorProvider(host.colorProvider)
drawableProvider(host.drawableProvider) drawableProvider(host.drawableProvider)

View File

@ -76,6 +76,7 @@ class SessionInfoView @JvmOverloads constructor(
sessionInfoViewState.deviceFullInfo.isInactive, sessionInfoViewState.deviceFullInfo.isInactive,
sessionInfoViewState.deviceFullInfo.deviceInfo, sessionInfoViewState.deviceFullInfo.deviceInfo,
sessionInfoViewState.isLastSeenDetailsVisible, sessionInfoViewState.isLastSeenDetailsVisible,
sessionInfoViewState.isShowingIpAddress,
dateFormatter, dateFormatter,
drawableProvider, drawableProvider,
colorProvider, colorProvider,
@ -157,6 +158,7 @@ class SessionInfoView @JvmOverloads constructor(
isInactive: Boolean, isInactive: Boolean,
deviceInfo: DeviceInfo, deviceInfo: DeviceInfo,
isLastSeenDetailsVisible: Boolean, isLastSeenDetailsVisible: Boolean,
isShowingIpAddress: Boolean,
dateFormatter: VectorDateFormatter, dateFormatter: VectorDateFormatter,
drawableProvider: DrawableProvider, drawableProvider: DrawableProvider,
colorProvider: ColorProvider, colorProvider: ColorProvider,
@ -186,7 +188,7 @@ class SessionInfoView @JvmOverloads constructor(
} else { } else {
views.sessionInfoLastActivityTextView.isGone = true views.sessionInfoLastActivityTextView.isGone = true
} }
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible }) views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible && isShowingIpAddress })
} }
private fun renderDetailsButton(isDetailsButtonVisible: Boolean) { private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {

View File

@ -25,4 +25,5 @@ data class SessionInfoViewState(
val isDetailsButtonVisible: Boolean = true, val isDetailsButtonVisible: Boolean = true,
val isLearnMoreLinkVisible: Boolean = false, val isLearnMoreLinkVisible: Boolean = false,
val isLastSeenDetailsVisible: Boolean = false, val isLastSeenDetailsVisible: Boolean = false,
val isShowingIpAddress: Boolean = false,
) )

View File

@ -33,4 +33,5 @@ sealed class OtherSessionsAction : VectorViewModelAction {
object SelectAll : OtherSessionsAction() object SelectAll : OtherSessionsAction()
object DeselectAll : OtherSessionsAction() object DeselectAll : OtherSessionsAction()
object MultiSignout : OtherSessionsAction() object MultiSignout : OtherSessionsAction()
object ToggleIpAddressVisibility : OtherSessionsAction()
} }

View File

@ -85,6 +85,12 @@ class OtherSessionsFragment :
menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled
menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled
menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled && state.devices()?.isNotEmpty().orFalse() 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) updateMultiSignoutMenuItem(menu, state)
} }
} }
@ -130,10 +136,18 @@ class OtherSessionsFragment :
confirmMultiSignout() confirmMultiSignout()
true true
} }
R.id.otherSessionsToggleIpAddress -> {
toggleIpAddressVisibility()
true
}
else -> false else -> false
} }
} }
private fun toggleIpAddressVisibility() {
viewModel.handle(OtherSessionsAction.ToggleIpAddressVisibility)
}
private fun confirmMultiSignout() { private fun confirmMultiSignout() {
activity?.let { activity?.let {
buildConfirmSignoutDialogUseCase.execute(it, this::multiSignout) buildConfirmSignoutDialogUseCase.execute(it, this::multiSignout)
@ -213,7 +227,7 @@ class OtherSessionsFragment :
updateLoading(state.isLoading) updateLoading(state.isLoading)
if (state.devices is Success) { if (state.devices is Success) {
val devices = state.devices.invoke() val devices = state.devices.invoke()
renderDevices(devices, state.currentFilter) renderDevices(devices, state.currentFilter, state.isShowingIpAddress)
updateToolbar(devices, state.isSelectModeEnabled) updateToolbar(devices, state.isSelectModeEnabled)
} }
} }
@ -237,7 +251,7 @@ class OtherSessionsFragment :
toolbar?.title = title 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.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
@ -299,7 +313,8 @@ class OtherSessionsFragment :
} else { } else {
views.deviceListOtherSessions.isVisible = true views.deviceListOtherSessions.isVisible = true
views.otherSessionsNotFoundLayout.isVisible = false 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)
} }
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2.othersessions package im.vector.app.features.settings.devices.v2.othersessions
import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import dagger.assisted.Assisted 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.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.features.auth.PendingAuthHandler 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.GetDeviceFullInfoListUseCase
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase 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.VectorSessionsListViewModel
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded
@ -42,10 +45,12 @@ class OtherSessionsViewModel @AssistedInject constructor(
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase, private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
private val signoutSessionsUseCase: SignoutSessionsUseCase, private val signoutSessionsUseCase: SignoutSessionsUseCase,
private val pendingAuthHandler: PendingAuthHandler, private val pendingAuthHandler: PendingAuthHandler,
refreshDevicesUseCase: RefreshDevicesUseCase refreshDevicesUseCase: RefreshDevicesUseCase,
private val vectorPreferences: VectorPreferences,
private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>( ) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(
initialState, activeSessionHolder, refreshDevicesUseCase initialState, activeSessionHolder, refreshDevicesUseCase
) { ), SharedPreferences.OnSharedPreferenceChangeListener {
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> { interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
@ -58,6 +63,28 @@ class OtherSessionsViewModel @AssistedInject constructor(
init { init {
observeDevices(initialState.currentFilter) 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) { private fun observeDevices(currentFilter: DeviceManagerFilterType) {
@ -85,9 +112,14 @@ class OtherSessionsViewModel @AssistedInject constructor(
OtherSessionsAction.DeselectAll -> handleDeselectAll() OtherSessionsAction.DeselectAll -> handleDeselectAll()
OtherSessionsAction.SelectAll -> handleSelectAll() OtherSessionsAction.SelectAll -> handleSelectAll()
OtherSessionsAction.MultiSignout -> handleMultiSignout() OtherSessionsAction.MultiSignout -> handleMultiSignout()
OtherSessionsAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
} }
} }
private fun handleToggleIpAddressVisibility() {
toggleIpAddressVisibilityUseCase.execute()
}
private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) { private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) {
setState { setState {
copy( copy(

View File

@ -28,6 +28,7 @@ data class OtherSessionsViewState(
val excludeCurrentDevice: Boolean = false, val excludeCurrentDevice: Boolean = false,
val isSelectModeEnabled: Boolean = false, val isSelectModeEnabled: Boolean = false,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val isShowingIpAddress: Boolean = false,
) : MavericksState { ) : MavericksState {
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice) constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)

View File

@ -29,4 +29,5 @@ sealed class SessionOverviewAction : VectorViewModelAction {
val deviceId: String, val deviceId: String,
val enabled: Boolean, val enabled: Boolean,
) : SessionOverviewAction() ) : SessionOverviewAction()
object ToggleIpAddressVisibility : SessionOverviewAction()
} }

View File

@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2.overview
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -156,16 +157,34 @@ class SessionOverviewFragment :
override fun getMenuRes() = R.menu.menu_session_overview 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 { override fun handleMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.sessionOverviewRename -> { R.id.sessionOverviewRename -> {
goToRenameSession() goToRenameSession()
true true
} }
R.id.sessionOverviewToggleIpAddress -> {
toggleIpAddressVisibility()
true
}
else -> false else -> false
} }
} }
private fun toggleIpAddressVisibility() {
viewModel.handle(SessionOverviewAction.ToggleIpAddressVisibility)
}
private fun goToRenameSession() = withState(viewModel) { state -> private fun goToRenameSession() = withState(viewModel) { state ->
viewNavigator.goToRenameSession(requireContext(), state.deviceId) viewNavigator.goToRenameSession(requireContext(), state.deviceId)
} }
@ -206,6 +225,7 @@ class SessionOverviewFragment :
isDetailsButtonVisible = false, isDetailsButtonVisible = false,
isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default, isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
isLastSeenDetailsVisible = !isCurrentSession, isLastSeenDetailsVisible = !isCurrentSession,
isShowingIpAddress = viewState.isShowingIpAddress,
) )
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider) views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
views.sessionOverviewInfo.onLearnMoreClickListener = { views.sessionOverviewInfo.onLearnMoreClickListener = {

View File

@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2.overview package im.vector.app.features.settings.devices.v2.overview
import android.content.SharedPreferences
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import dagger.assisted.Assisted 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.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.features.auth.PendingAuthHandler 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.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.VectorSessionsListViewModel
import im.vector.app.features.settings.devices.v2.notification.GetNotificationsStatusUseCase import im.vector.app.features.settings.devices.v2.notification.GetNotificationsStatusUseCase
import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
@ -54,9 +57,11 @@ class SessionOverviewViewModel @AssistedInject constructor(
private val togglePushNotificationUseCase: TogglePushNotificationUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
private val getNotificationsStatusUseCase: GetNotificationsStatusUseCase, private val getNotificationsStatusUseCase: GetNotificationsStatusUseCase,
refreshDevicesUseCase: RefreshDevicesUseCase, refreshDevicesUseCase: RefreshDevicesUseCase,
private val vectorPreferences: VectorPreferences,
private val toggleIpAddressVisibilityUseCase: ToggleIpAddressVisibilityUseCase,
) : VectorSessionsListViewModel<SessionOverviewViewState, SessionOverviewAction, SessionOverviewViewEvent>( ) : VectorSessionsListViewModel<SessionOverviewViewState, SessionOverviewAction, SessionOverviewViewEvent>(
initialState, activeSessionHolder, refreshDevicesUseCase initialState, activeSessionHolder, refreshDevicesUseCase
) { ), SharedPreferences.OnSharedPreferenceChangeListener {
companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
@ -70,6 +75,27 @@ class SessionOverviewViewModel @AssistedInject constructor(
observeSessionInfo(initialState.deviceId) observeSessionInfo(initialState.deviceId)
observeCurrentSessionInfo() observeCurrentSessionInfo()
observeNotificationsStatus(initialState.deviceId) 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() { private fun refreshPushers() {
@ -111,9 +137,14 @@ class SessionOverviewViewModel @AssistedInject constructor(
is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action) is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action)
SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled() SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled()
is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action) is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action)
SessionOverviewAction.ToggleIpAddressVisibility -> handleToggleIpAddressVisibility()
} }
} }
private fun handleToggleIpAddressVisibility() {
toggleIpAddressVisibilityUseCase.execute()
}
private fun handleVerifySessionAction() = withState { viewState -> private fun handleVerifySessionAction() = withState { viewState ->
if (viewState.deviceInfo.invoke()?.isCurrentDevice.orFalse()) { if (viewState.deviceInfo.invoke()?.isCurrentDevice.orFalse()) {
handleVerifyCurrentSession() handleVerifyCurrentSession()

View File

@ -28,6 +28,7 @@ data class SessionOverviewViewState(
val deviceInfo: Async<DeviceFullInfo> = Uninitialized, val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED, val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED,
val isShowingIpAddress: Boolean = false,
) : MavericksState { ) : MavericksState {
constructor(args: SessionOverviewArgs) : this( constructor(args: SessionOverviewArgs) : this(
deviceId = args.deviceId deviceId = args.deviceId

View File

@ -13,7 +13,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@drawable/bg_other_session" 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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -53,11 +53,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView" app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView"
app:layout_constraintTop_toTopOf="@id/otherSessionDeviceTypeImageView" app:layout_constraintTop_toTopOf="@id/otherSessionItemBackground"
tools:text="Element Mobile: Android" /> tools:text="Element Mobile: Android" />
<TextView <TextView
@ -67,19 +68,31 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:drawablePadding="8dp" android:drawablePadding="8dp"
app:layout_constraintBottom_toBottomOf="@id/otherSessionDeviceTypeImageView"
app:layout_constraintEnd_toEndOf="@id/otherSessionNameTextView" app:layout_constraintEnd_toEndOf="@id/otherSessionNameTextView"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionNameTextView" app:layout_constraintTop_toBottomOf="@id/otherSessionNameTextView"
tools:text="@string/device_manager_verification_status_verified" /> tools:text="@string/device_manager_verification_status_verified" />
<TextView
android:id="@+id/otherSessionIpAddressTextView"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:drawablePadding="8dp"
app:layout_constraintEnd_toEndOf="@id/otherSessionNameTextView"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionDescriptionTextView"
tools:text="0.0.0.0" />
<View <View
android:id="@+id/otherSessionSeparator"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="?vctr_content_quinary" android:background="?vctr_content_quinary"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionItemBackground" /> app:layout_constraintTop_toBottomOf="@id/otherSessionIpAddressTextView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,6 +9,11 @@
android:title="@string/device_manager_other_sessions_select" android:title="@string/device_manager_other_sessions_select"
app:showAsAction="withText|never" /> app:showAsAction="withText|never" />
<item
android:id="@+id/otherSessionsToggleIpAddress"
android:title="@string/device_manager_other_sessions_show_ip_address"
app:showAsAction="withText|never" />
<item <item
android:id="@+id/otherSessionsMultiSignout" android:id="@+id/otherSessionsMultiSignout"
android:title="@plurals/device_manager_other_sessions_multi_signout_all" android:title="@plurals/device_manager_other_sessions_multi_signout_all"

View File

@ -4,6 +4,11 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction"> tools:ignore="AlwaysShowAction">
<item
android:id="@+id/otherSessionsHeaderToggleIpAddress"
android:title="@string/device_manager_other_sessions_show_ip_address"
app:showAsAction="withText|never" />
<item <item
android:id="@+id/otherSessionsHeaderMultiSignout" android:id="@+id/otherSessionsHeaderMultiSignout"
android:title="@plurals/device_manager_other_sessions_multi_signout_all" android:title="@plurals/device_manager_other_sessions_multi_signout_all"

View File

@ -4,6 +4,11 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction"> tools:ignore="AlwaysShowAction">
<item
android:id="@+id/sessionOverviewToggleIpAddress"
android:title="@string/device_manager_other_sessions_show_ip_address"
app:showAsAction="withText|never" />
<item <item
android:id="@+id/sessionOverviewRename" android:id="@+id/sessionOverviewRename"
android:title="@string/device_manager_session_rename" android:title="@string/device_manager_session_rename"

View File

@ -29,15 +29,18 @@ import im.vector.app.features.settings.devices.v2.verification.GetCurrentSession
import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakePendingAuthHandler import im.vector.app.test.fakes.FakePendingAuthHandler
import im.vector.app.test.fakes.FakeSignoutSessionsUseCase 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.fakes.FakeVerificationService
import im.vector.app.test.test import im.vector.app.test.test
import im.vector.app.test.testDispatcher import im.vector.app.test.testDispatcher
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.just
import io.mockk.justRun import io.mockk.justRun
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkAll import io.mockk.unmockkAll
import io.mockk.verify import io.mockk.verify
import io.mockk.verifyAll import io.mockk.verifyAll
@ -72,6 +75,8 @@ class DevicesViewModelTest {
private val fakeInterceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>() private val fakeInterceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>()
private val fakePendingAuthHandler = FakePendingAuthHandler() private val fakePendingAuthHandler = FakePendingAuthHandler()
private val fakeRefreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxUnitFun = true) private val fakeRefreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxUnitFun = true)
private val fakeVectorPreferences = FakeVectorPreferences()
private val toggleIpAddressVisibilityUseCase = mockk<ToggleIpAddressVisibilityUseCase>()
private fun createViewModel(): DevicesViewModel { private fun createViewModel(): DevicesViewModel {
return DevicesViewModel( return DevicesViewModel(
@ -85,6 +90,8 @@ class DevicesViewModelTest {
interceptSignoutFlowResponseUseCase = fakeInterceptSignoutFlowResponseUseCase, interceptSignoutFlowResponseUseCase = fakeInterceptSignoutFlowResponseUseCase,
pendingAuthHandler = fakePendingAuthHandler.instance, pendingAuthHandler = fakePendingAuthHandler.instance,
refreshDevicesUseCase = fakeRefreshDevicesUseCase, refreshDevicesUseCase = fakeRefreshDevicesUseCase,
vectorPreferences = fakeVectorPreferences.instance,
toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
) )
} }
@ -97,6 +104,7 @@ class DevicesViewModelTest {
givenVerificationService() givenVerificationService()
givenCurrentSessionCrossSigningInfo() givenCurrentSessionCrossSigningInfo()
givenDeviceFullInfoList(deviceId1 = A_DEVICE_ID_1, deviceId2 = A_DEVICE_ID_2) givenDeviceFullInfoList(deviceId1 = A_DEVICE_ID_1, deviceId2 = A_DEVICE_ID_2)
fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
} }
private fun givenVerificationService(): FakeVerificationService { 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 { private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>() val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
every { currentSessionCrossSigningInfo.deviceId } returns A_CURRENT_DEVICE_ID every { currentSessionCrossSigningInfo.deviceId } returns A_CURRENT_DEVICE_ID

View File

@ -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.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase 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.RefreshDevicesUseCase
import im.vector.app.features.settings.devices.v2.ToggleIpAddressVisibilityUseCase
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakePendingAuthHandler import im.vector.app.test.fakes.FakePendingAuthHandler
import im.vector.app.test.fakes.FakeSignoutSessionsUseCase 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.fakes.FakeVerificationService
import im.vector.app.test.fixtures.aDeviceFullInfo import im.vector.app.test.fixtures.aDeviceFullInfo
import im.vector.app.test.test import im.vector.app.test.test
@ -66,6 +68,8 @@ class OtherSessionsViewModelTest {
private val fakeRefreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxed = true) private val fakeRefreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxed = true)
private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase() private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase()
private val fakePendingAuthHandler = FakePendingAuthHandler() private val fakePendingAuthHandler = FakePendingAuthHandler()
private val fakeVectorPreferences = FakeVectorPreferences()
private val toggleIpAddressVisibilityUseCase = mockk<ToggleIpAddressVisibilityUseCase>()
private fun createViewModel(viewState: OtherSessionsViewState = OtherSessionsViewState(defaultArgs)) = private fun createViewModel(viewState: OtherSessionsViewState = OtherSessionsViewState(defaultArgs)) =
OtherSessionsViewModel( OtherSessionsViewModel(
@ -75,6 +79,8 @@ class OtherSessionsViewModelTest {
signoutSessionsUseCase = fakeSignoutSessionsUseCase.instance, signoutSessionsUseCase = fakeSignoutSessionsUseCase.instance,
pendingAuthHandler = fakePendingAuthHandler.instance, pendingAuthHandler = fakePendingAuthHandler.instance,
refreshDevicesUseCase = fakeRefreshDevicesUseCase, refreshDevicesUseCase = fakeRefreshDevicesUseCase,
vectorPreferences = fakeVectorPreferences.instance,
toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
) )
@Before @Before
@ -84,6 +90,7 @@ class OtherSessionsViewModelTest {
every { SystemClock.elapsedRealtime() } returns 1234 every { SystemClock.elapsedRealtime() } returns 1234
givenVerificationService() givenVerificationService()
fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
} }
private fun givenVerificationService(): FakeVerificationService { private fun givenVerificationService(): FakeVerificationService {

View File

@ -22,6 +22,7 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MavericksTestRule import com.airbnb.mvrx.test.MavericksTestRule
import im.vector.app.features.settings.devices.v2.DeviceFullInfo 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.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.notification.NotificationsStatus
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase 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.FakePendingAuthHandler
import im.vector.app.test.fakes.FakeSignoutSessionsUseCase import im.vector.app.test.fakes.FakeSignoutSessionsUseCase
import im.vector.app.test.fakes.FakeTogglePushNotificationUseCase 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.fakes.FakeVerificationService
import im.vector.app.test.test import im.vector.app.test.test
import im.vector.app.test.testDispatcher import im.vector.app.test.testDispatcher
@ -77,6 +79,8 @@ class SessionOverviewViewModelTest {
private val togglePushNotificationUseCase = FakeTogglePushNotificationUseCase() private val togglePushNotificationUseCase = FakeTogglePushNotificationUseCase()
private val fakeGetNotificationsStatusUseCase = FakeGetNotificationsStatusUseCase() private val fakeGetNotificationsStatusUseCase = FakeGetNotificationsStatusUseCase()
private val notificationsStatus = NotificationsStatus.ENABLED private val notificationsStatus = NotificationsStatus.ENABLED
private val fakeVectorPreferences = FakeVectorPreferences()
private val toggleIpAddressVisibilityUseCase = mockk<ToggleIpAddressVisibilityUseCase>()
private fun createViewModel() = SessionOverviewViewModel( private fun createViewModel() = SessionOverviewViewModel(
initialState = SessionOverviewViewState(args), initialState = SessionOverviewViewState(args),
@ -89,6 +93,8 @@ class SessionOverviewViewModelTest {
refreshDevicesUseCase = refreshDevicesUseCase, refreshDevicesUseCase = refreshDevicesUseCase,
togglePushNotificationUseCase = togglePushNotificationUseCase.instance, togglePushNotificationUseCase = togglePushNotificationUseCase.instance,
getNotificationsStatusUseCase = fakeGetNotificationsStatusUseCase.instance, getNotificationsStatusUseCase = fakeGetNotificationsStatusUseCase.instance,
vectorPreferences = fakeVectorPreferences.instance,
toggleIpAddressVisibilityUseCase = toggleIpAddressVisibilityUseCase,
) )
@Before @Before
@ -103,6 +109,7 @@ class SessionOverviewViewModelTest {
A_SESSION_ID_1, A_SESSION_ID_1,
notificationsStatus notificationsStatus
) )
fakeVectorPreferences.givenSessionManagerShowIpAddress(false)
} }
private fun givenVerificationService(): FakeVerificationService { private fun givenVerificationService(): FakeVerificationService {

View File

@ -52,4 +52,8 @@ class FakeVectorPreferences {
fun verifySetNotificationEnabledForDevice(enabled: Boolean, inverse: Boolean = false) { fun verifySetNotificationEnabledForDevice(enabled: Boolean, inverse: Boolean = false) {
verify(inverse = inverse) { instance.setNotificationEnabledForDevice(enabled) } verify(inverse = inverse) { instance.setNotificationEnabledForDevice(enabled) }
} }
fun givenSessionManagerShowIpAddress(showIpAddress: Boolean) {
every { instance.showIpAddressInSessionManagerScreens() } returns showIpAddress
}
} }