diff --git a/changelog.d/7694.feature b/changelog.d/7694.feature
new file mode 100644
index 0000000000..408925974e
--- /dev/null
+++ b/changelog.d/7694.feature
@@ -0,0 +1 @@
+Remind unverified sessions with a banner once a week
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 62928caa75..df12d49da6 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -2653,8 +2653,12 @@
Encrypted by an unverified device
Encrypted by a deleted device
The authenticity of this encrypted message can\'t be guaranteed on this device.
- Review where you’re logged in
- Verify all your sessions to ensure your account & messages are safe
+
+ Review where you’re logged in
+
+ Verify all your sessions to ensure your account & messages are safe
+ You have unverified sessions
+ Review to ensure your account is safe
Verify the new login accessing your account: %1$s
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index 5c497c24ec..2134c8cf2c 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -88,6 +88,9 @@ class DebugVectorFeatures(
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
?: vectorFeatures.isVoiceBroadcastEnabled()
+ override fun isUnverifiedSessionsAlertEnabled(): Boolean = read(DebugFeatureKeys.unverifiedSessionsAlertEnabled)
+ ?: vectorFeatures.isUnverifiedSessionsAlertEnabled()
+
fun override(value: T?, key: Preferences.Key) = updatePreferences {
if (value == null) {
it.remove(key)
@@ -151,4 +154,5 @@ object DebugFeatureKeys {
val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
+ val unverifiedSessionsAlertEnabled = booleanPreferencesKey("unverified-sessions-alert-enabled")
}
diff --git a/vector-config/src/main/java/im/vector/app/config/Config.kt b/vector-config/src/main/java/im/vector/app/config/Config.kt
index a9bae626a4..9102e938f8 100644
--- a/vector-config/src/main/java/im/vector/app/config/Config.kt
+++ b/vector-config/src/main/java/im/vector/app/config/Config.kt
@@ -16,6 +16,8 @@
package im.vector.app.config
+import kotlin.time.Duration.Companion.days
+
/**
* Set of flags to configure the application.
*/
@@ -95,4 +97,6 @@ object Config {
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "NIGHTLY")
val ER_NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "element-r")
val ER_DEBUG_ANALYTICS_CONFIG = DEBUG_ANALYTICS_CONFIG.copy(sentryEnvironment = "element-r")
+
+ val SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS = 7.days.inWholeMilliseconds // 1 Week
}
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index 95cf272abd..99abc15f81 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -44,6 +44,7 @@ interface VectorFeatures {
fun isQrCodeLoginForAllServers(): Boolean
fun isReciprocateQrCodeLogin(): Boolean
fun isVoiceBroadcastEnabled(): Boolean
+ fun isUnverifiedSessionsAlertEnabled(): Boolean
}
class DefaultVectorFeatures : VectorFeatures {
@@ -63,4 +64,5 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isQrCodeLoginForAllServers(): Boolean = false
override fun isReciprocateQrCodeLogin(): Boolean = false
override fun isVoiceBroadcastEnabled(): Boolean = true
+ override fun isUnverifiedSessionsAlertEnabled(): Boolean = true
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index e824dc1820..69abeed424 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -239,12 +239,12 @@ class HomeDetailFragment :
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
}
unknownDeviceDetectorSharedViewModel.handle(
- UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
+ UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
dismissedAction = Runnable {
unknownDeviceDetectorSharedViewModel.handle(
- UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
+ UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
}
@@ -256,8 +256,8 @@ class HomeDetailFragment :
alertManager.postVectorAlert(
VerificationVectorAlert(
uid = uid,
- title = getString(R.string.review_logins),
- description = getString(R.string.verify_other_sessions),
+ title = getString(R.string.review_unverified_sessions_title),
+ description = getString(R.string.review_unverified_sessions_description),
iconId = R.drawable.ic_shield_warning
).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
diff --git a/vector/src/main/java/im/vector/app/features/home/IsNewLoginAlertShownUseCase.kt b/vector/src/main/java/im/vector/app/features/home/IsNewLoginAlertShownUseCase.kt
new file mode 100644
index 0000000000..5a0d4743dc
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/IsNewLoginAlertShownUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.home
+
+import im.vector.app.features.settings.VectorPreferences
+import javax.inject.Inject
+
+class IsNewLoginAlertShownUseCase @Inject constructor(
+ private val vectorPreferences: VectorPreferences,
+) {
+
+ fun execute(deviceId: String?): Boolean {
+ deviceId ?: return false
+
+ return vectorPreferences.isNewLoginAlertShownForDevice(deviceId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
index 5956646eab..ccd5a7e84b 100644
--- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
@@ -253,12 +253,12 @@ class NewHomeDetailFragment :
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
}
unknownDeviceDetectorSharedViewModel.handle(
- UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
+ UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
dismissedAction = Runnable {
unknownDeviceDetectorSharedViewModel.handle(
- UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
+ UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
}
@@ -270,8 +270,8 @@ class NewHomeDetailFragment :
alertManager.postVectorAlert(
VerificationVectorAlert(
uid = uid,
- title = getString(R.string.review_logins),
- description = getString(R.string.verify_other_sessions),
+ title = getString(R.string.review_unverified_sessions_title),
+ description = getString(R.string.review_unverified_sessions_description),
iconId = R.drawable.ic_shield_warning
).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
diff --git a/vector/src/main/java/im/vector/app/features/home/SetNewLoginAlertShownUseCase.kt b/vector/src/main/java/im/vector/app/features/home/SetNewLoginAlertShownUseCase.kt
new file mode 100644
index 0000000000..d313f93043
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/SetNewLoginAlertShownUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.home
+
+import im.vector.app.features.settings.VectorPreferences
+import javax.inject.Inject
+
+class SetNewLoginAlertShownUseCase @Inject constructor(
+ private val vectorPreferences: VectorPreferences,
+) {
+
+ fun execute(deviceIds: List) {
+ deviceIds.forEach {
+ vectorPreferences.setNewLoginAlertShownForDevice(it)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt b/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt
new file mode 100644
index 0000000000..4580ac0f31
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.home
+
+import im.vector.app.core.time.Clock
+import im.vector.app.features.settings.VectorPreferences
+import javax.inject.Inject
+
+class SetUnverifiedSessionsAlertShownUseCase @Inject constructor(
+ private val vectorPreferences: VectorPreferences,
+ private val clock: Clock,
+) {
+
+ fun execute(deviceIds: List) {
+ val epochMillis = clock.epochMillis()
+ deviceIds.forEach {
+ vectorPreferences.setUnverifiedSessionsAlertLastShownMillis(it, epochMillis)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt b/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt
new file mode 100644
index 0000000000..18c7ed9689
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.home
+
+import im.vector.app.config.Config
+import im.vector.app.core.time.Clock
+import im.vector.app.features.VectorFeatures
+import im.vector.app.features.settings.VectorPreferences
+import javax.inject.Inject
+
+class ShouldShowUnverifiedSessionsAlertUseCase @Inject constructor(
+ private val vectorFeatures: VectorFeatures,
+ private val vectorPreferences: VectorPreferences,
+ private val clock: Clock,
+) {
+
+ fun execute(deviceId: String?): Boolean {
+ deviceId ?: return false
+
+ val isUnverifiedSessionsAlertEnabled = vectorFeatures.isUnverifiedSessionsAlertEnabled()
+ val unverifiedSessionsAlertLastShownMillis = vectorPreferences.getUnverifiedSessionsAlertLastShownMillis(deviceId)
+ return isUnverifiedSessionsAlertEnabled &&
+ clock.epochMillis() - unverifiedSessionsAlertLastShownMillis >= Config.SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
index e56405638c..ef80b1b0d1 100644
--- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.home
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory
-import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
@@ -33,7 +32,6 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.core.time.Clock
-import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
@@ -61,14 +59,19 @@ data class DeviceDetectionInfo(
val currentSessionTrust: Boolean
)
-class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
- session: Session,
- private val vectorPreferences: VectorPreferences,
- private val clock: Clock,) :
- VectorViewModel(initialState) {
+class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
+ @Assisted initialState: UnknownDevicesState,
+ session: Session,
+ clock: Clock,
+ private val shouldShowUnverifiedSessionsAlertUseCase: ShouldShowUnverifiedSessionsAlertUseCase,
+ private val setUnverifiedSessionsAlertShownUseCase: SetUnverifiedSessionsAlertShownUseCase,
+ private val isNewLoginAlertShownUseCase: IsNewLoginAlertShownUseCase,
+ private val setNewLoginAlertShownUseCase: SetNewLoginAlertShownUseCase,
+) : VectorViewModel(initialState) {
sealed class Action : VectorViewModelAction {
data class IgnoreDevice(val deviceIds: List) : Action()
+ data class IgnoreNewLogin(val deviceIds: List) : Action()
}
@AssistedFactory
@@ -86,37 +89,35 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
}
}
- private val ignoredDeviceList = ArrayList()
-
init {
- ignoredDeviceList.addAll(
- vectorPreferences.getUnknownDeviceDismissedList().also {
- Timber.v("## Detector - Remembered ignored list $it")
- }
- )
+ val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
+ .firstOrNull { it.deviceId == session.sessionParams.deviceId }
+ ?.firstTimeSeenLocalTs
+ ?: clock.epochMillis()
+ Timber.v("## Detector - Current Session first time seen $currentSessionTs")
combine(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningPrivateKeys(),
- session.firstTimeDeviceSeen(),
- ) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
+ ) { cryptoList, infoList, pInfo ->
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
- Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
infoList
.filter { info ->
// filter verified session, by checking the crypto device info
cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse()
}
// filter out ignored devices
- .filter { !ignoredDeviceList.contains(it.deviceId) }
+ .filter { shouldShowUnverifiedSessionsAlertUseCase.execute(it.deviceId) }
.sortedByDescending { it.lastSeenTs }
.map { deviceInfo ->
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
+ val isNew = isNewLoginAlertShownUseCase.execute(deviceInfo.deviceId).not() && deviceKnownSince > currentSessionTs
+
DeviceDetectionInfo(
deviceInfo,
- deviceKnownSince > firstTimeDeviceSeen + 60_000, // short window to avoid false positive,
+ isNew,
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
)
}
@@ -148,30 +149,11 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
override fun handle(action: Action) {
when (action) {
is Action.IgnoreDevice -> {
- ignoredDeviceList.addAll(action.deviceIds)
- // local echo
- withState { state ->
- state.unknownSessions.invoke()?.let { detectedSessions ->
- val updated = detectedSessions.filter { !action.deviceIds.contains(it.deviceInfo.deviceId) }
- setState {
- copy(unknownSessions = Success(updated))
- }
- }
- }
+ setUnverifiedSessionsAlertShownUseCase.execute(action.deviceIds)
+ }
+ is Action.IgnoreNewLogin -> {
+ setNewLoginAlertShownUseCase.execute(action.deviceIds)
}
}
}
-
- override fun onCleared() {
- vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
- super.onCleared()
- }
-
- private fun Session.firstTimeDeviceSeen() = flow {
- val value = cryptoService().getCryptoDeviceInfoList(myUserId)
- .firstOrNull { it.deviceId == sessionParams.deviceId }
- ?.firstTimeSeenLocalTs
- ?: clock.epochMillis()
- emit(value)
- }
}
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 f8f78332c0..696895d705 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
@@ -228,8 +228,6 @@ class VectorPreferences @Inject constructor(
private const val MEDIA_SAVING_1_MONTH = 2
private const val MEDIA_SAVING_FOREVER = 3
- private const val SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST = "SETTINGS_UNKNWON_DEVICE_DISMISSED_LIST"
-
private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
@@ -246,6 +244,9 @@ class VectorPreferences @Inject constructor(
// This key will be used to enable user for displaying live user info or not.
const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
+ const val SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS = "SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS_"
+ const val SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE = "SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE_"
+
// Possible values for TAKE_PHOTO_VIDEO_MODE
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
@@ -521,18 +522,6 @@ class VectorPreferences @Inject constructor(
return defaultPrefs.getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
}
- fun storeUnknownDeviceDismissedList(deviceIds: List) {
- defaultPrefs.edit(true) {
- putStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, deviceIds.toSet())
- }
- }
-
- fun getUnknownDeviceDismissedList(): List {
- return tryOrNull {
- defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList()
- }.orEmpty()
- }
-
/**
* Update the notification ringtone.
*
@@ -1249,7 +1238,27 @@ class VectorPreferences @Inject constructor(
fun setIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
defaultPrefs.edit {
- putBoolean(VectorPreferences.SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, isVisible)
+ putBoolean(SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, isVisible)
+ }
+ }
+
+ fun getUnverifiedSessionsAlertLastShownMillis(deviceId: String): Long {
+ return defaultPrefs.getLong(SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS + deviceId, 0)
+ }
+
+ fun setUnverifiedSessionsAlertLastShownMillis(deviceId: String, lastShownMillis: Long) {
+ defaultPrefs.edit {
+ putLong(SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS + deviceId, lastShownMillis)
+ }
+ }
+
+ fun isNewLoginAlertShownForDevice(deviceId: String): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE + deviceId, false)
+ }
+
+ fun setNewLoginAlertShownForDevice(deviceId: String) {
+ defaultPrefs.edit {
+ putBoolean(SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE + deviceId, true)
}
}
}
diff --git a/vector/src/test/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCaseTest.kt
new file mode 100644
index 0000000000..5d08499e32
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCaseTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.home
+
+import im.vector.app.config.Config
+import im.vector.app.test.fakes.FakeClock
+import im.vector.app.test.fakes.FakeVectorFeatures
+import im.vector.app.test.fakes.FakeVectorPreferences
+import org.amshove.kluent.shouldBe
+import org.junit.Test
+
+private val AN_EPOCH = Config.SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS.toLong()
+private const val A_DEVICE_ID = "A_DEVICE_ID"
+
+class ShouldShowUnverifiedSessionsAlertUseCaseTest {
+
+ private val fakeVectorFeatures = FakeVectorFeatures()
+ private val fakeVectorPreferences = FakeVectorPreferences()
+ private val fakeClock = FakeClock()
+
+ private val shouldShowUnverifiedSessionsAlertUseCase = ShouldShowUnverifiedSessionsAlertUseCase(
+ vectorFeatures = fakeVectorFeatures,
+ vectorPreferences = fakeVectorPreferences.instance,
+ clock = fakeClock,
+ )
+
+ @Test
+ fun `given the feature is disabled then the use case returns false`() {
+ fakeVectorFeatures.givenUnverifiedSessionsAlertEnabled(false)
+ fakeVectorPreferences.givenUnverifiedSessionsAlertLastShownMillis(0L)
+
+ shouldShowUnverifiedSessionsAlertUseCase.execute(A_DEVICE_ID) shouldBe false
+ }
+
+ @Test
+ fun `given the feature in enabled and there is not a saved preference then the use case returns true`() {
+ fakeVectorFeatures.givenUnverifiedSessionsAlertEnabled(true)
+ fakeVectorPreferences.givenUnverifiedSessionsAlertLastShownMillis(0L)
+ fakeClock.givenEpoch(AN_EPOCH + 1)
+
+ shouldShowUnverifiedSessionsAlertUseCase.execute(A_DEVICE_ID) shouldBe true
+ }
+
+ @Test
+ fun `given the feature in enabled and last shown is a long time ago then the use case returns true`() {
+ fakeVectorFeatures.givenUnverifiedSessionsAlertEnabled(true)
+ fakeVectorPreferences.givenUnverifiedSessionsAlertLastShownMillis(AN_EPOCH)
+ fakeClock.givenEpoch(AN_EPOCH * 2 + 1)
+
+ shouldShowUnverifiedSessionsAlertUseCase.execute(A_DEVICE_ID) shouldBe true
+ }
+
+ @Test
+ fun `given the feature in enabled and last shown is not a long time ago then the use case returns false`() {
+ fakeVectorFeatures.givenUnverifiedSessionsAlertEnabled(true)
+ fakeVectorPreferences.givenUnverifiedSessionsAlertLastShownMillis(AN_EPOCH)
+ fakeClock.givenEpoch(AN_EPOCH + 1)
+
+ shouldShowUnverifiedSessionsAlertUseCase.execute(A_DEVICE_ID) shouldBe false
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt
index d989abc214..c3c2fa684f 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt
@@ -50,4 +50,8 @@ class FakeVectorFeatures : VectorFeatures by spyk() {
fun givenVoiceBroadcast(isEnabled: Boolean) {
every { isVoiceBroadcastEnabled() } returns isEnabled
}
+
+ fun givenUnverifiedSessionsAlertEnabled(isEnabled: Boolean) {
+ every { isUnverifiedSessionsAlertEnabled() } returns isEnabled
+ }
}
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 d89764a77e..77df3ffc7a 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
@@ -56,4 +56,8 @@ class FakeVectorPreferences {
fun givenSessionManagerShowIpAddress(showIpAddress: Boolean) {
every { instance.showIpAddressInSessionManagerScreens() } returns showIpAddress
}
+
+ fun givenUnverifiedSessionsAlertLastShownMillis(lastShownMillis: Long) {
+ every { instance.getUnverifiedSessionsAlertLastShownMillis(any()) } returns lastShownMillis
+ }
}