diff --git a/app/src/main/kotlin/com/infomaniak/auth/data/preferences/PermissionPreferences.kt b/app/src/main/kotlin/com/infomaniak/auth/data/preferences/PermissionPreferences.kt new file mode 100644 index 00000000..09cf5a65 --- /dev/null +++ b/app/src/main/kotlin/com/infomaniak/auth/data/preferences/PermissionPreferences.kt @@ -0,0 +1,34 @@ +/* + * Infomaniak Authenticator - Android + * Copyright (C) 2026 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +@file:OptIn(ExperimentalSplittiesApi::class) + +package com.infomaniak.auth.data.preferences + +import kotlinx.coroutines.flow.Flow +import splitties.experimental.ExperimentalSplittiesApi +import splitties.preferences.Preferences +import splitties.preferences.SuspendPrefsAccessor + +class PermissionPreferences private constructor(): Preferences(name = "PermissionPreferences") { + companion object : SuspendPrefsAccessor(::PermissionPreferences) + + val hasTriggeredNotificationPermissionFlow : Flow + var hasTriggeredNotificationPermission by boolPref(key = "HasTriggeredNotificationPermission", defaultValue = false).also { + hasTriggeredNotificationPermissionFlow = it.valueFlow() + } +} diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt index 1dc76079..c8f51a66 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt @@ -24,10 +24,8 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntryDecorator @@ -68,26 +66,21 @@ fun MainScreen( val notificationPermissionState: PermissionState? = if (SDK_INT >= 33) { rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS) } else null - - var notificationPermissionScreenShown by rememberSaveable { mutableStateOf(false) } + val hasTriggeredNotificationPermission by viewModel.hasTriggeredNotificationPermission.collectAsStateWithLifecycle() LaunchedEffect(viewModel.appStatus) { viewModel.appStatus.collect { val permissionStatus = notificationPermissionState?.status - val isPermanentlyDenied = (permissionStatus as? PermissionStatus.Denied)?.shouldShowRationale == false - val isPermissionNotRequired = with(permissionStatus) { - (this == null || this == PermissionStatus.Granted || !isPermanentlyDenied) - } - val skipPermission = notificationPermissionScreenShown && isPermissionNotRequired + val shouldShowRationale = (permissionStatus as? PermissionStatus.Denied)?.shouldShowRationale == true + val showNotificationPermissionScreen = notificationPermissionState != null && + permissionStatus != PermissionStatus.Granted && + (!hasTriggeredNotificationPermission || shouldShowRationale) handleAppStatus( appStatus = it, currentDestination = currentDestination, backStack = backStack, - skipPermission = skipPermission, - onPermissionAsked = { - notificationPermissionScreenShown = true - } + showNotificationPermissionScreen = showNotificationPermissionScreen, ) } } @@ -101,8 +94,7 @@ private fun handleAppStatus( appStatus: AppStatus, currentDestination: NavKey, backStack: NavBackStack, - skipPermission: Boolean, - onPermissionAsked: () -> Unit, + showNotificationPermissionScreen: Boolean, ) { val targetDestination = when (appStatus) { is AppStatus.LoginRequired.NotMigrating -> NavDestination.Onboarding.Start @@ -111,11 +103,10 @@ private fun handleAppStatus( is AppStatus.LoggingIn -> NavDestination.SecuringAccount is AppStatus.EverythingReady -> NavDestination.Onboarding.Complete is AppStatus.SetupComplete -> { - if (skipPermission) { - NavDestination.Home - } else { - onPermissionAsked() + if (showNotificationPermissionScreen) { NavDestination.Permission.Notification + } else { + NavDestination.Home } } is AppStatus.AddingAnAccount -> NavDestination.Onboarding.Start diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt index 501f0496..d490a806 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt @@ -18,10 +18,18 @@ package com.infomaniak.auth.ui.screen.main import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.infomaniak.auth.data.preferences.PermissionPreferences import com.infomaniak.auth.lib.AuthenticatorFacade import com.infomaniak.auth.lib.repository.AppSettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -32,4 +40,13 @@ class MainViewModel @Inject constructor( val appStatus = authenticatorFacade.appStatus val isAppLocked = appSettingsRepository.getSettings().mapNotNull { it?.isAppLockEnabled } + val hasTriggeredNotificationPermission: StateFlow = flow { + emitAll(PermissionPreferences().hasTriggeredNotificationPermissionFlow) + }.stateIn(viewModelScope, SharingStarted.Lazily, false) + + fun onNotificationPermissionTriggered() { + viewModelScope.launch { + PermissionPreferences().hasTriggeredNotificationPermission = true + } + } } diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/permission/NotificationPermissionScreen.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/permission/NotificationPermissionScreen.kt index 3c8f15a6..10a19fce 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/permission/NotificationPermissionScreen.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/permission/NotificationPermissionScreen.kt @@ -18,7 +18,6 @@ package com.infomaniak.auth.ui.screen.permission import android.Manifest -import android.annotation.SuppressLint import android.os.Build.VERSION.SDK_INT import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -35,6 +34,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.PermissionStatus @@ -48,31 +49,52 @@ import com.infomaniak.auth.ui.components.LargeButton import com.infomaniak.auth.ui.components.TitleAndDescription import com.infomaniak.auth.ui.images.AppImages import com.infomaniak.auth.ui.images.illus.bannerNotification.BannerNotification +import com.infomaniak.auth.ui.screen.main.MainViewModel import com.infomaniak.auth.ui.theme.AuthenticatorTheme import com.infomaniak.core.ui.compose.bottomstickybuttonscaffolds.BottomStickyButtonScaffold import com.infomaniak.core.ui.compose.margin.Margin import com.infomaniak.core.ui.compose.preview.PreviewSmallWindow @OptIn(ExperimentalPermissionsApi::class) -@SuppressLint("ComposeModifierMissing") @Composable fun NotificationPermissionScreen( - navigateToHome: () -> Unit + navigateToHome: () -> Unit, + viewModel: MainViewModel = hiltViewModel() ) { - var permissionAsked by remember { mutableStateOf(false) } + val hasTriggeredNotificationPermission by viewModel.hasTriggeredNotificationPermission.collectAsStateWithLifecycle() + val notificationPermissionState: PermissionState? = if (SDK_INT >= 33) { rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS) } else null + var permissionAsked by remember { mutableStateOf(false) } + LaunchedEffect(notificationPermissionState?.status) { if (notificationPermissionState == null || notificationPermissionState.status == PermissionStatus.Granted || - notificationPermissionState.status == PermissionStatus.Denied(false) || notificationPermissionState.status is PermissionStatus.Denied && permissionAsked) { + if (!hasTriggeredNotificationPermission) { + viewModel.onNotificationPermissionTriggered() + } navigateToHome() } } + NotificationPermissionScreen( + navigateToHome = navigateToHome, + onPermissionAsked = { + notificationPermissionState?.launchPermissionRequest() + permissionAsked = true + }, + ) +} + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +private fun NotificationPermissionScreen( + navigateToHome: () -> Unit, + onPermissionAsked: () -> Unit, +) { BottomStickyButtonScaffold( topBar = { InfomaniakAuthenticatorTopAppBar() @@ -82,10 +104,7 @@ fun NotificationPermissionScreen( LargeButton( modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.onboardingNotificationsAuthorisationButton), - onClick = { - notificationPermissionState?.launchPermissionRequest() - permissionAsked = true - } + onClick = onPermissionAsked ) LargeButton( modifier = Modifier.fillMaxWidth(), @@ -120,6 +139,7 @@ private fun NotificationPermissionScreenPreview() { AuthenticatorTheme { NotificationPermissionScreen( navigateToHome = {}, + onPermissionAsked = {}, ) } }