Merge pull request #3035 from element-hq/feature/bma/fixFdroidNotification
Feature/bma/fix fdroid notification
This commit is contained in:
commit
e4bfc5ef8f
43 changed files with 1060 additions and 106 deletions
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.libraries.architecture.appyx
|
||||
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
|
||||
fun <T : Any> BackStack<T>.canPop(): Boolean {
|
||||
val elements = elements.value
|
||||
return elements.any { it.targetState == BackStack.State.ACTIVE } &&
|
||||
elements.any { it.targetState == BackStack.State.STASHED }
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.libraries.designsystem.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
||||
import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ErrorDialogWithDoNotShowAgain(
|
||||
content: String,
|
||||
onDismiss: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String = ErrorDialogDefaults.title,
|
||||
submitText: String = ErrorDialogDefaults.submitText,
|
||||
cancelText: String? = null,
|
||||
onCancel: () -> Unit = {},
|
||||
) {
|
||||
var doNotShowAgain by remember { mutableStateOf(false) }
|
||||
BasicAlertDialog(
|
||||
modifier = modifier,
|
||||
onDismissRequest = { onDismiss(doNotShowAgain) }
|
||||
) {
|
||||
SimpleAlertDialogContent(
|
||||
title = title,
|
||||
submitText = submitText,
|
||||
cancelText = cancelText,
|
||||
onSubmitClick = { onDismiss(doNotShowAgain) },
|
||||
onCancelClick = onCancel,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = content,
|
||||
style = ElementTheme.materialTypography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = doNotShowAgain, onCheckedChange = { doNotShowAgain = it })
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.common_do_not_show_this_again),
|
||||
style = ElementTheme.materialTypography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ErrorDialogWithDoNotShowAgainPreview() = ElementPreview {
|
||||
ErrorDialogWithDoNotShowAgain(
|
||||
content = "Content",
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -20,3 +20,7 @@ sealed class ClientException(message: String) : Exception(message) {
|
|||
class Generic(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
|
||||
|
||||
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.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.HttpPusherData
|
||||
|
|
@ -52,6 +54,7 @@ class RustPushersService(
|
|||
lang = setHttpPusherData.lang
|
||||
)
|
||||
}
|
||||
.mapFailure { it.mapClientException() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakeSessionVerificationService : SessionVerificationService {
|
||||
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
|
||||
class FakeSessionVerificationService(
|
||||
initialSessionVerifiedStatus: SessionVerifiedStatus = SessionVerifiedStatus.Unknown,
|
||||
) : SessionVerificationService {
|
||||
private val _sessionVerifiedStatus = MutableStateFlow(initialSessionVerifiedStatus)
|
||||
private var _verificationFlowState = MutableStateFlow<VerificationFlowState>(VerificationFlowState.Initial)
|
||||
private var _needsSessionVerification = MutableStateFlow(true)
|
||||
var shouldFail = false
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
/**
|
||||
|
|
@ -27,8 +29,7 @@ interface PushService {
|
|||
suspend fun getCurrentPushProvider(): PushProvider?
|
||||
|
||||
/**
|
||||
* Return the list of push providers, available at compile time, and
|
||||
* available at runtime, sorted by index.
|
||||
* Return the list of push providers, available at compile time, sorted by index.
|
||||
*/
|
||||
fun getAvailablePushProviders(): List<PushProvider>
|
||||
|
||||
|
|
@ -43,6 +44,18 @@ interface PushService {
|
|||
distributor: Distributor,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Store the given push provider as the current one, but do not register.
|
||||
* To be used when there is no distributor available.
|
||||
*/
|
||||
suspend fun selectPushProvider(
|
||||
matrixClient: MatrixClient,
|
||||
pushProvider: PushProvider,
|
||||
)
|
||||
|
||||
fun ignoreRegistrationError(sessionId: SessionId): Flow<Boolean>
|
||||
suspend fun setIgnoreRegistrationError(sessionId: SessionId, ignore: Boolean)
|
||||
|
||||
/**
|
||||
* Return false in case of early error.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -42,7 +44,6 @@ class DefaultPushService @Inject constructor(
|
|||
|
||||
override fun getAvailablePushProviders(): List<PushProvider> {
|
||||
return pushProviders
|
||||
.filter { it.isAvailable() }
|
||||
.sortedBy { it.index }
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ class DefaultPushService @Inject constructor(
|
|||
pushProvider: PushProvider,
|
||||
distributor: Distributor,
|
||||
): Result<Unit> {
|
||||
Timber.d("Registering with ${pushProvider.name}/${distributor.name}}")
|
||||
Timber.d("Registering with ${pushProvider.name}/${distributor.name}")
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
val currentPushProviderName = userPushStore.getPushProviderName()
|
||||
val currentPushProvider = pushProviders.find { it.name == currentPushProviderName }
|
||||
|
|
@ -72,6 +73,23 @@ class DefaultPushService @Inject constructor(
|
|||
return pushProvider.registerWith(matrixClient, distributor)
|
||||
}
|
||||
|
||||
override suspend fun selectPushProvider(
|
||||
matrixClient: MatrixClient,
|
||||
pushProvider: PushProvider,
|
||||
) {
|
||||
Timber.d("Select ${pushProvider.name}")
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
userPushStore.setPushProviderName(pushProvider.name)
|
||||
}
|
||||
|
||||
override fun ignoreRegistrationError(sessionId: SessionId): Flow<Boolean> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -18,14 +18,17 @@ package io.element.android.libraries.push.impl
|
|||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
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.meta.BuildMeta
|
||||
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.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
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.clientsecret.PushClientSecret
|
||||
import timber.log.Timber
|
||||
|
|
@ -50,7 +53,8 @@ class DefaultPusherSubscriber @Inject constructor(
|
|||
gateway: String,
|
||||
): Result<Unit> {
|
||||
val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
if (userDataStore.getCurrentRegisteredPushKey() == pushKey) {
|
||||
val isRegisteringAgain = userDataStore.getCurrentRegisteredPushKey() == pushKey
|
||||
if (isRegisteringAgain) {
|
||||
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")
|
||||
}
|
||||
|
|
@ -61,8 +65,14 @@ class DefaultPusherSubscriber @Inject constructor(
|
|||
.onSuccess {
|
||||
userDataStore.setCurrentRegisteredPushKey(pushKey)
|
||||
}
|
||||
.onFailure { throwable ->
|
||||
.mapFailure { throwable ->
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.impl.test.FakeTestPush
|
||||
|
|
@ -33,6 +34,7 @@ import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushSto
|
|||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -205,6 +207,20 @@ class DefaultPushServiceTest {
|
|||
assertThat(result).containsExactly(aPushProvider1, aPushProvider2, aPushProvider3).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test setIgnoreRegistrationError is sent to the store`() = runTest {
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
}
|
||||
val defaultPushService = createDefaultPushService(
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
assertThat(defaultPushService.ignoreRegistrationError(A_SESSION_ID).first()).isFalse()
|
||||
defaultPushService.setIgnoreRegistrationError(A_SESSION_ID, true)
|
||||
assertThat(defaultPushService.ignoreRegistrationError(A_SESSION_ID).first()).isTrue()
|
||||
}
|
||||
|
||||
private fun createDefaultPushService(
|
||||
testPush: TestPush = FakeTestPush(),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@
|
|||
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.lambda.lambdaError
|
||||
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 },
|
||||
|
|
@ -28,9 +32,12 @@ class FakePushService(
|
|||
private val registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
private val currentPushProvider: () -> PushProvider? = { availablePushProviders.firstOrNull() },
|
||||
private val selectPushProviderLambda: suspend (MatrixClient, PushProvider) -> Unit = { _, _ -> lambdaError() },
|
||||
private val setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() },
|
||||
) : PushService {
|
||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||
return registeredPushProvider ?: availablePushProviders.firstOrNull()
|
||||
return registeredPushProvider ?: currentPushProvider()
|
||||
}
|
||||
|
||||
override fun getAvailablePushProviders(): List<PushProvider> {
|
||||
|
|
@ -52,6 +59,21 @@ class FakePushService(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun selectPushProvider(matrixClient: MatrixClient, pushProvider: PushProvider) {
|
||||
selectPushProviderLambda(matrixClient, pushProvider)
|
||||
}
|
||||
|
||||
private val ignoreRegistrationError = MutableStateFlow(false)
|
||||
|
||||
override fun ignoreRegistrationError(sessionId: SessionId): Flow<Boolean> {
|
||||
return ignoreRegistrationError
|
||||
}
|
||||
|
||||
override suspend fun setIgnoreRegistrationError(sessionId: SessionId, ignore: Boolean) {
|
||||
ignoreRegistrationError.value = ignore
|
||||
setIgnoreRegistrationErrorLambda(sessionId, ignore)
|
||||
}
|
||||
|
||||
override suspend fun testPush(): Boolean = simulateLongTask {
|
||||
testPushBlock()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ interface PushProvider {
|
|||
val name: String
|
||||
|
||||
/**
|
||||
* Return true if the push provider is available on this device.
|
||||
* Return the list of available distributors.
|
||||
*/
|
||||
fun isAvailable(): Boolean
|
||||
|
||||
fun getDistributors(): List<Distributor>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,8 +17,21 @@
|
|||
package io.element.android.libraries.pushproviders.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
|
||||
interface PusherSubscriber {
|
||||
/**
|
||||
* Register a pusher. Note that failure will be a [RegistrationFailure].
|
||||
*/
|
||||
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>
|
||||
}
|
||||
|
||||
class RegistrationFailure(
|
||||
val clientException: ClientException,
|
||||
val isRegisteringAgain: Boolean
|
||||
) : Exception(clientException)
|
||||
|
|
|
|||
|
|
@ -38,12 +38,10 @@ class FirebasePushProvider @Inject constructor(
|
|||
override val index = FirebaseConfig.INDEX
|
||||
override val name = FirebaseConfig.NAME
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
return isPlayServiceAvailable.isAvailable()
|
||||
}
|
||||
|
||||
override fun getDistributors(): List<Distributor> {
|
||||
return listOf(firebaseDistributor)
|
||||
return listOfNotNull(
|
||||
firebaseDistributor.takeIf { isPlayServiceAvailable.isAvailable() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor): Result<Unit> {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,23 @@ class FirebasePushProviderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `getDistributors return the unique distributor`() {
|
||||
val firebasePushProvider = createFirebasePushProvider()
|
||||
fun `getDistributors return the unique distributor if available`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = true)
|
||||
)
|
||||
val result = firebasePushProvider.getDistributors()
|
||||
assertThat(result).containsExactly(Distributor("Firebase", "Firebase"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDistributors return empty list if service is not available`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = false)
|
||||
)
|
||||
val result = firebasePushProvider.getDistributors()
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentDistributor always return the unique distributor`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider()
|
||||
|
|
@ -51,22 +62,6 @@ class FirebasePushProviderTest {
|
|||
assertThat(result).isEqualTo(Distributor("Firebase", "Firebase"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isAvailable true`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = true)
|
||||
)
|
||||
assertThat(firebasePushProvider.isAvailable()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isAvailable false`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = false)
|
||||
)
|
||||
assertThat(firebasePushProvider.isAvailable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ok`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
|
|
|
|||
|
|
@ -25,14 +25,12 @@ import io.element.android.tests.testutils.lambda.lambdaError
|
|||
class FakePushProvider(
|
||||
override val index: Int = 0,
|
||||
override val name: String = "aFakePushProvider",
|
||||
private val isAvailable: Boolean = true,
|
||||
private val distributors: List<Distributor> = listOf(Distributor("aDistributorValue", "aDistributorName")),
|
||||
private val currentDistributor: () -> Distributor? = { distributors.firstOrNull() },
|
||||
private val currentUserPushConfig: CurrentUserPushConfig? = null,
|
||||
private val registerWithResult: (MatrixClient, Distributor) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val unregisterWithResult: (MatrixClient) -> Result<Unit> = { lambdaError() },
|
||||
) : PushProvider {
|
||||
override fun isAvailable(): Boolean = isAvailable
|
||||
|
||||
override fun getDistributors(): List<Distributor> = distributors
|
||||
|
||||
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor): Result<Unit> {
|
||||
|
|
@ -40,7 +38,7 @@ class FakePushProvider(
|
|||
}
|
||||
|
||||
override suspend fun getCurrentDistributor(matrixClient: MatrixClient): Distributor? {
|
||||
return distributors.firstOrNull()
|
||||
return currentDistributor()
|
||||
}
|
||||
|
||||
override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.element.android.libraries.pushproviders.unifiedpush
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesMultibinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
|
|
@ -26,11 +25,8 @@ import io.element.android.libraries.pushproviders.api.PushProvider
|
|||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import io.element.android.services.appnavstate.api.currentSessionId
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("UnifiedPushProvider", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesMultibinding(AppScope::class)
|
||||
class UnifiedPushProvider @Inject constructor(
|
||||
private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider,
|
||||
|
|
@ -43,17 +39,6 @@ class UnifiedPushProvider @Inject constructor(
|
|||
override val index = UnifiedPushConfig.INDEX
|
||||
override val name = UnifiedPushConfig.NAME
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
val isAvailable = getDistributors().isNotEmpty()
|
||||
return if (isAvailable) {
|
||||
Timber.tag(loggerTag.value).d("UnifiedPush is available")
|
||||
true
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).w("UnifiedPush is not available")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDistributors(): List<Distributor> {
|
||||
return unifiedPushDistributorProvider.getDistributors()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ class UnifiedPushProviderTest {
|
|||
)
|
||||
val result = unifiedPushProvider.getDistributors()
|
||||
assertThat(result).containsExactly(Distributor("value", "Name"))
|
||||
assertThat(unifiedPushProvider.isAvailable()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -70,7 +69,6 @@ class UnifiedPushProviderTest {
|
|||
)
|
||||
val result = unifiedPushProvider.getDistributors()
|
||||
assertThat(result).isEmpty()
|
||||
assertThat(unifiedPushProvider.isAvailable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ interface UserPushStore {
|
|||
fun getNotificationEnabledForDevice(): Flow<Boolean>
|
||||
suspend fun setNotificationEnabledForDevice(enabled: Boolean)
|
||||
|
||||
fun ignoreRegistrationError(): Flow<Boolean>
|
||||
suspend fun setIgnoreRegistrationError(ignore: Boolean)
|
||||
|
||||
/**
|
||||
* Return true if Pin code is disabled, or if user set the settings to see full notification content.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ dependencies {
|
|||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -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<Boolean> {
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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.libraries.pushstore.impl
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class UserPushStoreDataStoreTest {
|
||||
@Test
|
||||
fun `test getPushProviderName`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
assertThat(sut.getPushProviderName()).isNull()
|
||||
sut.setPushProviderName("name")
|
||||
assertThat(sut.getPushProviderName()).isEqualTo("name")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test getCurrentRegisteredPushKey`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
assertThat(sut.getCurrentRegisteredPushKey()).isNull()
|
||||
sut.setCurrentRegisteredPushKey("aKey")
|
||||
assertThat(sut.getCurrentRegisteredPushKey()).isEqualTo("aKey")
|
||||
sut.setCurrentRegisteredPushKey(null)
|
||||
assertThat(sut.getCurrentRegisteredPushKey()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test getNotificationEnabledForDevice`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
assertThat(sut.getNotificationEnabledForDevice().first()).isTrue()
|
||||
sut.setNotificationEnabledForDevice(false)
|
||||
assertThat(sut.getNotificationEnabledForDevice().first()).isFalse()
|
||||
sut.setNotificationEnabledForDevice(true)
|
||||
assertThat(sut.getNotificationEnabledForDevice().first()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test useCompleteNotificationFormat`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
assertThat(sut.useCompleteNotificationFormat()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ignoreRegistrationError`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
assertThat(sut.ignoreRegistrationError().first()).isFalse()
|
||||
sut.setIgnoreRegistrationError(true)
|
||||
assertThat(sut.ignoreRegistrationError().first()).isTrue()
|
||||
sut.setIgnoreRegistrationError(false)
|
||||
assertThat(sut.ignoreRegistrationError().first()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test reset`() = runTest {
|
||||
val sut = createUserPushStoreDataStore()
|
||||
sut.setPushProviderName("name")
|
||||
sut.setCurrentRegisteredPushKey("aKey")
|
||||
sut.setNotificationEnabledForDevice(false)
|
||||
sut.setIgnoreRegistrationError(true)
|
||||
sut.reset()
|
||||
assertThat(sut.getPushProviderName()).isNull()
|
||||
assertThat(sut.getCurrentRegisteredPushKey()).isNull()
|
||||
assertThat(sut.getNotificationEnabledForDevice().first()).isTrue()
|
||||
assertThat(sut.ignoreRegistrationError().first()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure a store is created per session`() = runTest {
|
||||
val sut1 = createUserPushStoreDataStore()
|
||||
sut1.setPushProviderName("name")
|
||||
val sut2 = createUserPushStoreDataStore(A_SESSION_ID_2)
|
||||
assertThat(sut1.getPushProviderName()).isEqualTo("name")
|
||||
assertThat(sut2.getPushProviderName()).isNull()
|
||||
}
|
||||
|
||||
private fun createUserPushStoreDataStore(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
) = UserPushStoreDataStore(
|
||||
context = InstrumentationRegistry.getInstrumentation().context,
|
||||
userId = sessionId,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<Boolean> {
|
||||
return ignoreRegistrationError
|
||||
}
|
||||
|
||||
override suspend fun setIgnoreRegistrationError(ignore: Boolean) {
|
||||
ignoreRegistrationError.value = ignore
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,12 +129,16 @@
|
|||
<string name="common_decryption_error">"Decryption error"</string>
|
||||
<string name="common_developer_options">"Developer options"</string>
|
||||
<string name="common_direct_chat">"Direct chat"</string>
|
||||
<string name="common_do_not_show_this_again">"Do not show this again"</string>
|
||||
<string name="common_edited_suffix">"(edited)"</string>
|
||||
<string name="common_editing">"Editing"</string>
|
||||
<string name="common_emote">"* %1$s %2$s"</string>
|
||||
<string name="common_encryption_enabled">"Encryption enabled"</string>
|
||||
<string name="common_enter_your_pin">"Enter your PIN"</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_failed">"Failed"</string>
|
||||
<string name="common_favourite">"Favourite"</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue