Render an error dialog in case registering a pusher fails.
This commit is contained in:
parent
f7b8e0c931
commit
e6f6e82ce2
12 changed files with 131 additions and 9 deletions
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 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 io.element.android.appnav.loggedin
|
||||||
|
|
||||||
|
sealed interface LoggedInEvents {
|
||||||
|
data object CloseErrorDialog : LoggedInEvents
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import io.element.android.libraries.push.api.PushService
|
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 io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -81,9 +82,16 @@ class LoggedInPresenter @Inject constructor(
|
||||||
reportCryptoStatusToAnalytics(verificationState, recoveryState)
|
reportCryptoStatusToAnalytics(verificationState, recoveryState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleEvent(event: LoggedInEvents) {
|
||||||
|
when (event) {
|
||||||
|
LoggedInEvents.CloseErrorDialog -> pusherRegistrationState.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return LoggedInState(
|
return LoggedInState(
|
||||||
showSyncSpinner = showSyncSpinner,
|
showSyncSpinner = showSyncSpinner,
|
||||||
pusherRegistrationState = pusherRegistrationState.value,
|
pusherRegistrationState = pusherRegistrationState.value,
|
||||||
|
eventSink = ::handleEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +130,13 @@ class LoggedInPresenter @Inject constructor(
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
Timber.tag(pusherTag.value).e(it, "Failed to register pusher")
|
Timber.tag(pusherTag.value).e(it, "Failed to register pusher")
|
||||||
pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.RegistrationFailure(it))
|
if (it is RegistrationFailure) {
|
||||||
|
pusherRegistrationState.value = AsyncData.Failure(
|
||||||
|
PusherRegistrationFailure.RegistrationFailure(it.clientException, it.isRegisteringAgain)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
pusherRegistrationState.value = AsyncData.Failure(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ import io.element.android.libraries.architecture.AsyncData
|
||||||
data class LoggedInState(
|
data class LoggedInState(
|
||||||
val showSyncSpinner: Boolean,
|
val showSyncSpinner: Boolean,
|
||||||
val pusherRegistrationState: AsyncData<Unit>,
|
val pusherRegistrationState: AsyncData<Unit>,
|
||||||
|
val eventSink: (LoggedInEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,17 @@ import io.element.android.libraries.architecture.AsyncData
|
||||||
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||||
override val values: Sequence<LoggedInState>
|
override val values: Sequence<LoggedInState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aLoggedInState(false),
|
aLoggedInState(),
|
||||||
aLoggedInState(true),
|
aLoggedInState(showSyncSpinner = true),
|
||||||
// Add other state here
|
aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoProvidersAvailable())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aLoggedInState(
|
fun aLoggedInState(
|
||||||
showSyncSpinner: Boolean = true,
|
showSyncSpinner: Boolean = false,
|
||||||
|
pusherRegistrationState: AsyncData<Unit> = AsyncData.Uninitialized,
|
||||||
) = LoggedInState(
|
) = LoggedInState(
|
||||||
showSyncSpinner = showSyncSpinner,
|
showSyncSpinner = showSyncSpinner,
|
||||||
pusherRegistrationState = AsyncData.Uninitialized,
|
pusherRegistrationState = pusherRegistrationState,
|
||||||
|
eventSink = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,14 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
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.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.matrix.api.exception.isNetworkError
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoggedInView(
|
fun LoggedInView(
|
||||||
|
|
@ -41,6 +46,38 @@ fun LoggedInView(
|
||||||
isVisible = state.showSyncSpinner,
|
isVisible = state.showSyncSpinner,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
when (state.pusherRegistrationState) {
|
||||||
|
is AsyncData.Uninitialized,
|
||||||
|
is AsyncData.Loading,
|
||||||
|
is AsyncData.Success -> Unit
|
||||||
|
is AsyncData.Failure -> {
|
||||||
|
state.pusherRegistrationState.errorOrNull()
|
||||||
|
?.getReason()
|
||||||
|
?.let { reason ->
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(id = CommonStrings.common_error_registering_pusher_android, reason),
|
||||||
|
onDismiss = { state.eventSink(LoggedInEvents.CloseErrorDialog) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Throwable.getReason(): String? {
|
||||||
|
return when (this) {
|
||||||
|
is PusherRegistrationFailure.RegistrationFailure -> {
|
||||||
|
if (isRegisteringAgain && clientException.isNetworkError()) {
|
||||||
|
// When registering again, ignore network error
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
clientException.message ?: "Unknown error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PusherRegistrationFailure.AccountNotVerified -> null
|
||||||
|
is PusherRegistrationFailure.NoDistributorsAvailable -> "No distributors available"
|
||||||
|
is PusherRegistrationFailure.NoProvidersAvailable -> "No providers available"
|
||||||
|
else -> "Other error"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,19 @@
|
||||||
|
|
||||||
package io.element.android.appnav.loggedin
|
package io.element.android.appnav.loggedin
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||||
|
|
||||||
sealed class PusherRegistrationFailure : Exception() {
|
sealed class PusherRegistrationFailure : Exception() {
|
||||||
class AccountNotVerified : PusherRegistrationFailure()
|
class AccountNotVerified : PusherRegistrationFailure()
|
||||||
class NoProvidersAvailable : PusherRegistrationFailure()
|
class NoProvidersAvailable : PusherRegistrationFailure()
|
||||||
class NoDistributorsAvailable : PusherRegistrationFailure()
|
class NoDistributorsAvailable : PusherRegistrationFailure()
|
||||||
class RegistrationFailure(val failure: Throwable) : PusherRegistrationFailure()
|
|
||||||
|
/**
|
||||||
|
* @param clientException the failure that occurred.
|
||||||
|
* @param isRegisteringAgain true if the server should already have a the same pusher registered.
|
||||||
|
*/
|
||||||
|
class RegistrationFailure(
|
||||||
|
val clientException: ClientException,
|
||||||
|
val isRegisteringAgain: Boolean,
|
||||||
|
) : PusherRegistrationFailure()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,10 @@ class LoggedInPresenterTest {
|
||||||
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
|
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
|
||||||
lambda.assertions()
|
lambda.assertions()
|
||||||
.isNeverCalled()
|
.isNeverCalled()
|
||||||
|
// Reset the error
|
||||||
|
finalState.eventSink(LoggedInEvents.CloseErrorDialog)
|
||||||
|
val lastState = awaitItem()
|
||||||
|
assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,7 @@ sealed class ClientException(message: String) : Exception(message) {
|
||||||
class Generic(message: String) : ClientException(message)
|
class Generic(message: String) : ClientException(message)
|
||||||
class Other(message: String) : ClientException(message)
|
class Other(message: String) : ClientException(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ClientException.isNetworkError(): Boolean {
|
||||||
|
return this is ClientException.Generic && message?.contains("error sending request for url", ignoreCase = true) == true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@
|
||||||
package io.element.android.libraries.matrix.impl.pushers
|
package io.element.android.libraries.matrix.impl.pushers
|
||||||
|
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.core.extensions.mapFailure
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||||
|
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.Client
|
import org.matrix.rustcomponents.sdk.Client
|
||||||
import org.matrix.rustcomponents.sdk.HttpPusherData
|
import org.matrix.rustcomponents.sdk.HttpPusherData
|
||||||
|
|
@ -52,6 +54,7 @@ class RustPushersService(
|
||||||
lang = setHttpPusherData.lang
|
lang = setHttpPusherData.lang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.mapFailure { it.mapClientException() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,17 @@ package io.element.android.libraries.push.impl
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.appconfig.PushConfig
|
import io.element.android.appconfig.PushConfig
|
||||||
|
import io.element.android.libraries.core.extensions.mapFailure
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
|
import io.element.android.libraries.pushproviders.api.RegistrationFailure
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -50,7 +53,8 @@ class DefaultPusherSubscriber @Inject constructor(
|
||||||
gateway: String,
|
gateway: String,
|
||||||
): Result<Unit> {
|
): Result<Unit> {
|
||||||
val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||||
if (userDataStore.getCurrentRegisteredPushKey() == pushKey) {
|
val isRegisteringAgain = userDataStore.getCurrentRegisteredPushKey() == pushKey
|
||||||
|
if (isRegisteringAgain) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.d("Unnecessary to register again the same pusher, but do it in case the pusher has been removed from the server")
|
.d("Unnecessary to register again the same pusher, but do it in case the pusher has been removed from the server")
|
||||||
}
|
}
|
||||||
|
|
@ -61,8 +65,14 @@ class DefaultPusherSubscriber @Inject constructor(
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
userDataStore.setCurrentRegisteredPushKey(pushKey)
|
userDataStore.setCurrentRegisteredPushKey(pushKey)
|
||||||
}
|
}
|
||||||
.onFailure { throwable ->
|
.mapFailure { throwable ->
|
||||||
Timber.tag(loggerTag.value).e(throwable, "Unable to register the pusher")
|
Timber.tag(loggerTag.value).e(throwable, "Unable to register the pusher")
|
||||||
|
if (throwable is ClientException) {
|
||||||
|
// It should always be the case.
|
||||||
|
RegistrationFailure(throwable, isRegisteringAgain = isRegisteringAgain)
|
||||||
|
} else {
|
||||||
|
throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,21 @@
|
||||||
package io.element.android.libraries.pushproviders.api
|
package io.element.android.libraries.pushproviders.api
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||||
|
|
||||||
interface PusherSubscriber {
|
interface PusherSubscriber {
|
||||||
|
/**
|
||||||
|
* Register a pusher. Note that failure will be a [RegistrationFailure].
|
||||||
|
*/
|
||||||
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit>
|
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a pusher.
|
||||||
|
*/
|
||||||
suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit>
|
suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RegistrationFailure(
|
||||||
|
val clientException: ClientException,
|
||||||
|
val isRegisteringAgain: Boolean
|
||||||
|
) : Exception(clientException)
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@
|
||||||
<string name="common_encryption_enabled">"Encryption enabled"</string>
|
<string name="common_encryption_enabled">"Encryption enabled"</string>
|
||||||
<string name="common_enter_your_pin">"Enter your PIN"</string>
|
<string name="common_enter_your_pin">"Enter your PIN"</string>
|
||||||
<string name="common_error">"Error"</string>
|
<string name="common_error">"Error"</string>
|
||||||
|
<string name="common_error_registering_pusher_android">"An error occurred, you may not receive notifications for new messages. Please troubleshoot notifications from the settings.
|
||||||
|
|
||||||
|
Reason: %1$s."</string>
|
||||||
<string name="common_everyone">"Everyone"</string>
|
<string name="common_everyone">"Everyone"</string>
|
||||||
<string name="common_failed">"Failed"</string>
|
<string name="common_failed">"Failed"</string>
|
||||||
<string name="common_favourite">"Favourite"</string>
|
<string name="common_favourite">"Favourite"</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue