diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt index d93e4fabfc..43a2c2b485 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt @@ -17,5 +17,5 @@ package io.element.android.appnav.loggedin sealed interface LoggedInEvents { - data object CloseErrorDialog : LoggedInEvents + data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvents } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index b28e33dc16..e70a44aa2b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.UserProperties import io.element.android.features.networkmonitor.api.NetworkMonitor @@ -41,6 +42,7 @@ import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.RegistrationFailure import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -56,9 +58,13 @@ class LoggedInPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): LoggedInState { + val coroutineScope = rememberCoroutineScope() val isVerified by remember { sessionVerificationService.sessionVerifiedStatus.map { it == SessionVerifiedStatus.Verified } }.collectAsState(initial = false) + val ignoreRegistrationError by remember { + pushService.ignoreRegistrationError(matrixClient.sessionId) + }.collectAsState(initial = false) val pusherRegistrationState = remember>> { mutableStateOf(AsyncData.Uninitialized) } if (isVerified) { LaunchedEffect(Unit) { @@ -84,13 +90,21 @@ class LoggedInPresenter @Inject constructor( fun handleEvent(event: LoggedInEvents) { when (event) { - LoggedInEvents.CloseErrorDialog -> pusherRegistrationState.value = AsyncData.Uninitialized + is LoggedInEvents.CloseErrorDialog -> { + pusherRegistrationState.value = AsyncData.Uninitialized + if (event.doNotShowAgain) { + coroutineScope.launch { + pushService.setIgnoreRegistrationError(matrixClient.sessionId, true) + } + } + } } } return LoggedInState( showSyncSpinner = showSyncSpinner, pusherRegistrationState = pusherRegistrationState.value, + ignoreRegistrationError = ignoreRegistrationError, eventSink = ::handleEvent ) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index fabaff786e..87f70511bc 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -21,5 +21,6 @@ import io.element.android.libraries.architecture.AsyncData data class LoggedInState( val showSyncSpinner: Boolean, val pusherRegistrationState: AsyncData, + val ignoreRegistrationError: Boolean, val eventSink: (LoggedInEvents) -> Unit, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt index bdb0aaacf0..d98271bef0 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt @@ -34,5 +34,6 @@ fun aLoggedInState( ) = LoggedInState( showSyncSpinner = showSyncSpinner, pusherRegistrationState = pusherRegistrationState, + ignoreRegistrationError = false, eventSink = {}, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index b997bc0b42..8be46d5743 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogWithDoNotShowAgain import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.exception.isNetworkError @@ -52,11 +52,12 @@ fun LoggedInView( is AsyncData.Success -> Unit is AsyncData.Failure -> { state.pusherRegistrationState.errorOrNull() + ?.takeIf { !state.ignoreRegistrationError } ?.getReason() ?.let { reason -> - ErrorDialog( + ErrorDialogWithDoNotShowAgain( content = stringResource(id = CommonStrings.common_error_registering_pusher_android, reason), - onDismiss = { state.eventSink(LoggedInEvents.CloseErrorDialog) }, + onDismiss = { state.eventSink(LoggedInEvents.CloseErrorDialog(it)) }, ) } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index aa2f720343..191efd70ec 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -64,6 +64,7 @@ class LoggedInPresenterTest { val initialState = awaitItem() assertThat(initialState.showSyncSpinner).isFalse() assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue() + assertThat(initialState.ignoreRegistrationError).isFalse() skipItems(1) } } @@ -356,6 +357,12 @@ class LoggedInPresenterTest { .isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java) lambda.assertions() .isNeverCalled() + // Reset the error and do not show again + finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = true)) + skipItems(1) + val lastState = awaitItem() + assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue() + assertThat(lastState.ignoreRegistrationError).isTrue() } } @@ -390,7 +397,7 @@ class LoggedInPresenterTest { lambda.assertions() .isNeverCalled() // Reset the error - finalState.eventSink(LoggedInEvents.CloseErrorDialog) + finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = false)) val lastState = awaitItem() assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue() } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 9227eef364..effbe158d2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.api.PushService import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject @@ -47,6 +48,7 @@ class DefaultClearCacheUseCase @Inject constructor( private val okHttpClient: Provider, private val ftueService: FtueService, private val migrationScreenStore: MigrationScreenStore, + private val pushService: PushService, ) : ClearCacheUseCase { override suspend fun invoke() = withContext(coroutineDispatchers.io) { // Clear Matrix cache @@ -66,5 +68,7 @@ class DefaultClearCacheUseCase @Inject constructor( migrationScreenStore.reset() // Ensure the app is restarted defaultCacheIndexProvider.onClearedCache(matrixClient.sessionId) + // Ensure any error will be displayed again + pushService.setIgnoreRegistrationError(matrixClient.sessionId, false) } } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt index ce27acb7b3..7212f0534a 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt @@ -17,8 +17,10 @@ package io.element.android.libraries.push.api import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider +import kotlinx.coroutines.flow.Flow interface PushService { /** @@ -43,6 +45,9 @@ interface PushService { distributor: Distributor, ): Result + fun ignoreRegistrationError(sessionId: SessionId): Flow + suspend fun setIgnoreRegistrationError(sessionId: SessionId, ignore: Boolean) + /** * Return false in case of early error. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt index 7600b695f1..b6efaa2c71 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt @@ -19,12 +19,14 @@ package io.element.android.libraries.push.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.impl.test.TestPush import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.UserPushStoreFactory +import kotlinx.coroutines.flow.Flow import timber.log.Timber import javax.inject.Inject @@ -72,6 +74,14 @@ class DefaultPushService @Inject constructor( return pushProvider.registerWith(matrixClient, distributor) } + override fun ignoreRegistrationError(sessionId: SessionId): Flow { + return userPushStoreFactory.getOrCreate(sessionId).ignoreRegistrationError() + } + + override suspend fun setIgnoreRegistrationError(sessionId: SessionId, ignore: Boolean) { + userPushStoreFactory.getOrCreate(sessionId).setIgnoreRegistrationError(ignore) + } + override suspend fun testPush(): Boolean { val pushProvider = getCurrentPushProvider() ?: return false val config = pushProvider.getCurrentUserPushConfig() ?: return false diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt index 0bf2da54ad..d9e5f0d2b6 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -17,10 +17,13 @@ package io.element.android.libraries.push.test import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow class FakePushService( private val testPushBlock: suspend () -> Boolean = { true }, @@ -53,6 +56,16 @@ class FakePushService( } } + private val ignoreRegistrationError = MutableStateFlow(false) + + override fun ignoreRegistrationError(sessionId: SessionId): Flow { + return ignoreRegistrationError + } + + override suspend fun setIgnoreRegistrationError(sessionId: SessionId, ignore: Boolean) { + ignoreRegistrationError.value = ignore + } + override suspend fun testPush(): Boolean = simulateLongTask { testPushBlock() } diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt index d24cc5ff5e..3828ffba2b 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt @@ -29,6 +29,9 @@ interface UserPushStore { fun getNotificationEnabledForDevice(): Flow suspend fun setNotificationEnabledForDevice(enabled: Boolean) + fun ignoreRegistrationError(): Flow + suspend fun setIgnoreRegistrationError(ignore: Boolean) + /** * Return true if Pin code is disabled, or if user set the settings to see full notification content. */ diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt index cfcc9e3da4..8dc9ef9d59 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt @@ -25,6 +25,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStoreFile import io.element.android.libraries.androidutils.hash.hash +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushstore.api.UserPushStore @@ -61,6 +62,7 @@ class UserPushStoreDataStore( private val pushProviderName = stringPreferencesKey("pushProviderName") private val currentPushKey = stringPreferencesKey("currentPushKey") private val notificationEnabled = booleanPreferencesKey("notificationEnabled") + private val ignoreRegistrationError = booleanPreferencesKey("ignoreRegistrationError") override suspend fun getPushProviderName(): String? { return context.dataStore.data.first()[pushProviderName] @@ -100,6 +102,16 @@ class UserPushStoreDataStore( return true } + override fun ignoreRegistrationError(): Flow { + return context.dataStore.data.map { it[ignoreRegistrationError].orFalse() } + } + + override suspend fun setIgnoreRegistrationError(ignore: Boolean) { + context.dataStore.edit { + it[ignoreRegistrationError] = ignore + } + } + override suspend fun reset() { context.dataStore.edit { it.clear() diff --git a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt index 112a752368..35dc83040c 100644 --- a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt +++ b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt @@ -25,6 +25,7 @@ class FakeUserPushStore( ) : UserPushStore { private var currentRegisteredPushKey: String? = null private val notificationEnabledForDevice = MutableStateFlow(true) + private val ignoreRegistrationError = MutableStateFlow(false) override suspend fun getPushProviderName(): String? { return pushProviderName } @@ -53,6 +54,14 @@ class FakeUserPushStore( return true } + override fun ignoreRegistrationError(): Flow { + return ignoreRegistrationError + } + + override suspend fun setIgnoreRegistrationError(ignore: Boolean) { + ignoreRegistrationError.value = ignore + } + override suspend fun reset() { } }