First Commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.permissions.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
implementation(libs.accompanist.permission)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.troubleshoot.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
api(projects.libraries.permissions.api)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.troubleshoot.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalPermissionsApi::class)
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
|
||||
interface ComposablePermissionStateProvider {
|
||||
@Composable
|
||||
fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class AccompanistPermissionStateProvider : ComposablePermissionStateProvider {
|
||||
@Composable
|
||||
override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState {
|
||||
return rememberPermissionState(
|
||||
permission = permission,
|
||||
onPermissionResult = onPermissionResult
|
||||
)
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.api.PermissionsStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPermissionStateProvider(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val permissionsStore: PermissionsStore,
|
||||
) : PermissionStateProvider {
|
||||
override fun isPermissionGranted(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
permission,
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
override suspend fun setPermissionDenied(permission: String, value: Boolean) = permissionsStore.setPermissionDenied(permission, value)
|
||||
|
||||
override fun isPermissionDenied(permission: String): Flow<Boolean> = permissionsStore.isPermissionDenied(permission)
|
||||
|
||||
override suspend fun setPermissionAsked(permission: String, value: Boolean) = permissionsStore.setPermissionAsked(permission, value)
|
||||
|
||||
override fun isPermissionAsked(permission: String): Flow<Boolean> = permissionsStore.isPermissionAsked(permission)
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.shouldShowRationale
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.permissions.api.PermissionsEvents
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.api.PermissionsState
|
||||
import io.element.android.libraries.permissions.api.PermissionsStore
|
||||
import io.element.android.libraries.permissions.impl.action.PermissionActions
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
private val loggerTag = LoggerTag("DefaultPermissionsPresenter")
|
||||
|
||||
@AssistedInject
|
||||
class DefaultPermissionsPresenter(
|
||||
@Assisted val permission: String,
|
||||
private val permissionsStore: PermissionsStore,
|
||||
private val composablePermissionStateProvider: ComposablePermissionStateProvider,
|
||||
private val permissionActions: PermissionActions,
|
||||
) : PermissionsPresenter {
|
||||
@AssistedFactory
|
||||
@ContributesBinding(AppScope::class)
|
||||
interface Factory : PermissionsPresenter.Factory {
|
||||
override fun create(permission: String): DefaultPermissionsPresenter
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@SuppressLint("InlinedApi")
|
||||
@Composable
|
||||
override fun present(): PermissionsState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
// To reset the store: ResetStore()
|
||||
|
||||
val isAlreadyDenied: Boolean by remember {
|
||||
permissionsStore.isPermissionDenied(permission)
|
||||
}.collectAsState(initial = false)
|
||||
|
||||
val isAlreadyAsked: Boolean by remember {
|
||||
permissionsStore.isPermissionAsked(permission)
|
||||
}.collectAsState(initial = false)
|
||||
|
||||
var permissionState: PermissionState? = null
|
||||
|
||||
fun onPermissionResult(result: Boolean) {
|
||||
Timber.tag(loggerTag.value).d("onPermissionResult: $result")
|
||||
localCoroutineScope.launch {
|
||||
permissionsStore.setPermissionAsked(permission, true)
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// Should show rational true -> denied.
|
||||
if (permissionState?.status?.shouldShowRationale == true) {
|
||||
Timber.tag(loggerTag.value).d("onPermissionResult: setPermissionDenied to true")
|
||||
localCoroutineScope.launch {
|
||||
permissionsStore.setPermissionDenied(permission, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
permissionState = composablePermissionStateProvider.provide(
|
||||
permission = permission,
|
||||
onPermissionResult = ::onPermissionResult
|
||||
)
|
||||
|
||||
LaunchedEffect(this) {
|
||||
if (permissionState.status.isGranted) {
|
||||
// User may have granted permission from the settings, so reset the store regarding this permission
|
||||
permissionsStore.resetPermission(permission)
|
||||
}
|
||||
}
|
||||
|
||||
val showDialog = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
fun handleEvent(event: PermissionsEvents) {
|
||||
when (event) {
|
||||
PermissionsEvents.CloseDialog -> {
|
||||
showDialog.value = false
|
||||
}
|
||||
PermissionsEvents.RequestPermissions -> {
|
||||
if (permissionState.status !is PermissionStatus.Granted && isAlreadyDenied) {
|
||||
showDialog.value = true
|
||||
} else {
|
||||
permissionState.launchPermissionRequest()
|
||||
}
|
||||
}
|
||||
PermissionsEvents.OpenSystemSettingAndCloseDialog -> {
|
||||
permissionActions.openSettings()
|
||||
showDialog.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PermissionsState(
|
||||
permission = permissionState.permission,
|
||||
permissionGranted = permissionState.status.isGranted,
|
||||
shouldShowRationale = permissionState.status.shouldShowRationale,
|
||||
showDialog = showDialog.value,
|
||||
permissionAlreadyAsked = isAlreadyAsked,
|
||||
permissionAlreadyDenied = isAlreadyDenied,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
@Composable
|
||||
private fun ResetStore() {
|
||||
LaunchedEffect(this@DefaultPermissionsPresenter) {
|
||||
launch {
|
||||
permissionsStore.resetStore()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.permissions.api.PermissionsStore
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPermissionsStore(
|
||||
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : PermissionsStore {
|
||||
private val store = preferenceDataStoreFactory.create("permissions_store")
|
||||
|
||||
override suspend fun setPermissionDenied(permission: String, value: Boolean) {
|
||||
store.edit { prefs ->
|
||||
prefs[getDeniedPreferenceKey(permission)] = value
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPermissionDenied(permission: String): Flow<Boolean> {
|
||||
return store.data.map {
|
||||
it[getDeniedPreferenceKey(permission)].orFalse()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setPermissionAsked(permission: String, value: Boolean) {
|
||||
store.edit { prefs ->
|
||||
prefs[getAskedPreferenceKey(permission)] = value
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPermissionAsked(permission: String): Flow<Boolean> {
|
||||
return store.data.map {
|
||||
it[getAskedPreferenceKey(permission)].orFalse()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun resetPermission(permission: String) {
|
||||
setPermissionAsked(permission, false)
|
||||
setPermissionDenied(permission, false)
|
||||
}
|
||||
|
||||
override suspend fun resetStore() {
|
||||
store.edit { it.clear() }
|
||||
}
|
||||
|
||||
private fun getDeniedPreferenceKey(permission: String) = booleanPreferencesKey("${permission}_denied")
|
||||
private fun getAskedPreferenceKey(permission: String) = booleanPreferencesKey("${permission}_asked")
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl.action
|
||||
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class AndroidPermissionActions(
|
||||
@ApplicationContext private val context: Context
|
||||
) : PermissionActions {
|
||||
override fun openSettings() {
|
||||
context.startNotificationSettingsIntent()
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl.action
|
||||
|
||||
interface PermissionActions {
|
||||
fun openSettings()
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl.troubleshoot
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesIntoSet
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.impl.R
|
||||
import io.element.android.libraries.permissions.impl.action.PermissionActions
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@ContributesIntoSet(AppScope::class)
|
||||
@Inject
|
||||
class NotificationTroubleshootCheckPermissionTest(
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val sdkVersionProvider: BuildVersionSdkIntProvider,
|
||||
private val permissionActions: PermissionActions,
|
||||
stringProvider: StringProvider,
|
||||
) : NotificationTroubleshootTest {
|
||||
override val order: Int = 0
|
||||
|
||||
private val delegate = NotificationTroubleshootTestDelegate(
|
||||
defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_title),
|
||||
defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_description),
|
||||
hasQuickFix = true,
|
||||
fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY,
|
||||
)
|
||||
|
||||
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
|
||||
|
||||
override suspend fun run(coroutineScope: CoroutineScope) {
|
||||
delegate.start()
|
||||
val result = if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
permissionStateProvider.isPermissionGranted(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
delegate.done(result)
|
||||
}
|
||||
|
||||
override suspend fun reset() = delegate.reset()
|
||||
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
// Do not bother about asking the permission inline, just lead the user to the settings
|
||||
permissionActions.openSettings()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Пераканайцеся, што праграма можа паказваць апавяшчэнні."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Праверце дазволы"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Проверка дали приложението може да показва известия."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Проверка на разрешенията"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Ujistěte se, že aplikace může zobrazovat oznámení."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Kontrola oprávnění"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Gwiriwch y gall y rhaglen ddangos hysbysiadau."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Gwirio caniatâd"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Kontroller, at applikationen kan vise underretninger."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Kontroller tilladelser"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Prüfe, dass die Anwendung Benachrichtigungen anzeigen kann."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Berechtigungen überprüfen"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Έλεγξε ότι η εφαρμογή μπορεί να εμφανίζει ειδοποιήσεις."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Έλεγχος δικαιωμάτων"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Verificar que la aplicación pueda mostrar notificaciones."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Verificar permisos"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Kontrolli, kas rakendus võib kuvada teavitusi."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Täpsusta õigusi"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Egiaztatu aplikazioak jakinarazpenak erakutsi ditzakeela."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Egiaztatu baimenak"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"بررسی اجازهها"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Tarkistaa, että sovellus voi näyttää ilmoituksia."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Lupien tarkistus"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Vérifie que l’application peut afficher des notifications."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Vérifier les autorisations"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Engedélyek ellenőrzése"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Pastikan aplikasi dapat menampilkan notifikasi."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Periksa izin"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Verifica che l\'applicazione possa mostrare le notifiche."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Controlla autorizzazioni"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"შეამოწმეთ რომ აპლიკაციას შეტყობინებების ჩვენება შეუძლია."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"ნებართვების შემოწმება"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"애플리케이션에서 알림을 표시할 수 있는지 확인하세요."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"권한 확인"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Kontroller at programmet kan vise varsler."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Sjekk tillatelser"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Controleren of de applicatie meldingen kan weergeven."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Controleer machtigingen"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Sprawdź, czy aplikacja może wyświetlać powiadomienia."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Sprawdź uprawnienia"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Verifique se o aplicativo pode mostrar notificações."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Verifique as permissões"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Verificar se a aplicação consegue mostrar notificações."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Verificar permissões"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Verificați dacă aplicația poate afișa notificări."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Verificați permisiunile"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Убедитесь, что приложение может показывать уведомления."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Проверка разрешений"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Uistite sa, že aplikácia dokáže zobrazovať upozornenia."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Skontrolovať povolenia"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Kontrollera att applikationen kan visa aviseringar."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Kontrollera behörigheter"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Uygulamanın bildirimleri gösterebildiğini kontrol edin."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"İzinleri kontrol et"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Перевірте, чи може застосунок показувати сповіщення."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Перевірте дозволи"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"پڑتال کریں کہ اطلاقیہ اطلاعات دکھا سکتا ہے"</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"اجازتوں کی پڑتال کریں"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Ilova bildirishnomalarni ko‘rsata olishini tekshiring."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Ruxsatlarni tekshiring"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"檢查應用程式是否可以顯示通知。"</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"檢查權限"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"检查应用程序是否可以显示通知。"</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"检查权限"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Check that the application can show notifications."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Check permissions"</string>
|
||||
</resources>
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalPermissionsApi::class)
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.permissions.api.PermissionsEvents
|
||||
import io.element.android.libraries.permissions.impl.action.FakePermissionActions
|
||||
import io.element.android.libraries.permissions.test.InMemoryPermissionsStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
const val A_PERMISSION = "A_PERMISSION"
|
||||
|
||||
class DefaultPermissionsPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore()
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Granted
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.permission).isEqualTo(A_PERMISSION)
|
||||
assertThat(initialState.permissionGranted).isTrue()
|
||||
assertThat(initialState.shouldShowRationale).isFalse()
|
||||
assertThat(initialState.permissionAlreadyAsked).isFalse()
|
||||
assertThat(initialState.permissionAlreadyDenied).isFalse()
|
||||
assertThat(initialState.showDialog).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user closes dialog`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore(
|
||||
permissionDenied = true,
|
||||
permissionAsked = true
|
||||
)
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = false)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
val withDialogState = awaitItem()
|
||||
assertThat(withDialogState.showDialog).isTrue()
|
||||
withDialogState.eventSink.invoke(PermissionsEvents.CloseDialog)
|
||||
assertThat(awaitItem().showDialog).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user open settings`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore(
|
||||
permissionDenied = true,
|
||||
permissionAsked = true
|
||||
)
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = false)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val permissionActions = FakePermissionActions()
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
permissionActions,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
val withDialogState = awaitItem()
|
||||
assertThat(withDialogState.showDialog).isTrue()
|
||||
assertThat(permissionActions.openSettingsCalled).isFalse()
|
||||
withDialogState.eventSink.invoke(PermissionsEvents.OpenSystemSettingAndCloseDialog)
|
||||
assertThat(awaitItem().showDialog).isFalse()
|
||||
assertThat(permissionActions.openSettingsCalled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user does not grant permission`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore()
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = false)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showDialog).isFalse()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
|
||||
// User does not grant permission
|
||||
permissionStateProvider.userGiveAnswer(answer = false, firstTime = true)
|
||||
skipItems(1)
|
||||
val state = awaitItem()
|
||||
assertThat(state.permissionGranted).isFalse()
|
||||
assertThat(state.showDialog).isFalse()
|
||||
assertThat(state.permissionAlreadyDenied).isFalse()
|
||||
assertThat(state.permissionAlreadyAsked).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user does not grant permission second time`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore()
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = true)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showDialog).isFalse()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
|
||||
// User does not grant permission
|
||||
permissionStateProvider.userGiveAnswer(answer = false, firstTime = false)
|
||||
skipItems(2)
|
||||
val state = awaitItem()
|
||||
assertThat(state.permissionGranted).isFalse()
|
||||
assertThat(state.showDialog).isFalse()
|
||||
assertThat(state.permissionAlreadyDenied).isTrue()
|
||||
assertThat(state.permissionAlreadyAsked).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user does not grant permission third time`() = runTest {
|
||||
val permissionsStore =
|
||||
InMemoryPermissionsStore(
|
||||
permissionDenied = true,
|
||||
permissionAsked = true
|
||||
)
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = false)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
val withDialogState = awaitItem()
|
||||
assertThat(withDialogState.showDialog).isTrue()
|
||||
assertThat(withDialogState.permissionGranted).isFalse()
|
||||
assertThat(withDialogState.permissionAlreadyDenied).isTrue()
|
||||
assertThat(withDialogState.permissionAlreadyAsked).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - user grants permission`() = runTest {
|
||||
val permissionsStore = InMemoryPermissionsStore()
|
||||
val permissionState = FakePermissionState(
|
||||
A_PERMISSION,
|
||||
PermissionStatus.Denied(shouldShowRationale = false)
|
||||
)
|
||||
val permissionStateProvider =
|
||||
FakeComposablePermissionStateProvider(
|
||||
permissionState
|
||||
)
|
||||
val presenter = DefaultPermissionsPresenter(
|
||||
A_PERMISSION,
|
||||
permissionsStore,
|
||||
permissionStateProvider,
|
||||
FakePermissionActions(),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showDialog).isFalse()
|
||||
initialState.eventSink.invoke(PermissionsEvents.RequestPermissions)
|
||||
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
|
||||
// User grants permission
|
||||
permissionStateProvider.userGiveAnswer(answer = true, firstTime = true)
|
||||
skipItems(1)
|
||||
val state = awaitItem()
|
||||
assertThat(state.permissionGranted).isTrue()
|
||||
assertThat(state.showDialog).isFalse()
|
||||
assertThat(state.permissionAlreadyDenied).isFalse()
|
||||
assertThat(state.permissionAlreadyAsked).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalPermissionsApi::class)
|
||||
|
||||
package io.element.android.libraries.permissions.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
|
||||
class FakeComposablePermissionStateProvider(
|
||||
private val permissionState: FakePermissionState
|
||||
) : ComposablePermissionStateProvider {
|
||||
private lateinit var onPermissionResult: (Boolean) -> Unit
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState {
|
||||
this.onPermissionResult = onPermissionResult
|
||||
return permissionState
|
||||
}
|
||||
|
||||
fun userGiveAnswer(answer: Boolean, firstTime: Boolean) {
|
||||
onPermissionResult.invoke(answer)
|
||||
permissionState.givenPermissionStatus(answer, firstTime)
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class FakePermissionState(
|
||||
override val permission: String,
|
||||
initialStatus: PermissionStatus,
|
||||
) : PermissionState {
|
||||
override var status: PermissionStatus by mutableStateOf(initialStatus)
|
||||
|
||||
var launchPermissionRequestCalled = false
|
||||
private set
|
||||
|
||||
override fun launchPermissionRequest() {
|
||||
launchPermissionRequestCalled = true
|
||||
}
|
||||
|
||||
fun givenPermissionStatus(hasPermission: Boolean, shouldShowRationale: Boolean) {
|
||||
status = if (hasPermission) PermissionStatus.Granted else PermissionStatus.Denied(shouldShowRationale)
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl.action
|
||||
|
||||
class FakePermissionActions(
|
||||
val openSettingsAction: () -> Unit = {}
|
||||
) : PermissionActions {
|
||||
var openSettingsCalled = false
|
||||
private set
|
||||
|
||||
override fun openSettings() {
|
||||
openSettingsAction()
|
||||
openSettingsCalled = true
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.permissions.impl.troubleshoot
|
||||
|
||||
import android.os.Build
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.permissions.impl.action.FakePermissionActions
|
||||
import io.element.android.libraries.permissions.test.FakePermissionStateProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class NotificationTroubleshootCheckPermissionTestTest {
|
||||
@Test
|
||||
fun `test NotificationTroubleshootCheckPermissionTest below TIRAMISU success`() = runTest {
|
||||
val sut = NotificationTroubleshootCheckPermissionTest(
|
||||
permissionStateProvider = FakePermissionStateProvider(),
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU - 1),
|
||||
permissionActions = FakePermissionActions(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU success`() = runTest {
|
||||
val sut = NotificationTroubleshootCheckPermissionTest(
|
||||
permissionStateProvider = FakePermissionStateProvider(),
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU),
|
||||
permissionActions = FakePermissionActions(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU error`() = runTest {
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
permissionGranted = false
|
||||
)
|
||||
val actions = FakePermissionActions(
|
||||
openSettingsAction = {
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
}
|
||||
)
|
||||
val sut = NotificationTroubleshootCheckPermissionTest(
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU),
|
||||
permissionActions = actions,
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
// Quick fix
|
||||
backgroundScope.launch {
|
||||
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
|
||||
// Run the test again (IRL it will be done thanks to the resuming of the application)
|
||||
sut.run(this)
|
||||
}
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test NotificationTroubleshootCheckPermissionTest error and reset`() = runTest {
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
permissionGranted = false
|
||||
)
|
||||
val actions = FakePermissionActions(
|
||||
openSettingsAction = {
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
}
|
||||
)
|
||||
val sut = NotificationTroubleshootCheckPermissionTest(
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU),
|
||||
permissionActions = actions,
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user