Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-01 15:40:30 +05:00
parent 07d5ede58c
commit e1b50efbe0
16 changed files with 21 additions and 222 deletions

View file

@ -184,30 +184,6 @@ internal object SettingsDomainModule {
return NeverToInitiallyAskPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideIsFirstTimeAskingPermissionUseCase(
permissionRepository: PermissionRepository,
): IsFirstTimeAskingPermissionUseCase {
return IsFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideSetFirstTimeAskingPushPermissionUseCase(
permissionRepository: PermissionRepository,
): SetFirstTimeAskingPermissionUseCase {
return SetFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideDelayPermissionRequestUseCase(
permissionRepository: PermissionRepository,
): DelayPermissionRequestUseCase {
return DelayPermissionRequestUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideShouldAskPermissionUseCase(permissionRepository: PermissionRepository): ShouldAskPermissionUseCase {

View file

@ -6,7 +6,6 @@ import androidx.compose.runtime.MutableState
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
/**
* Returns push permission requester.
@ -16,21 +15,13 @@ import com.google.accompanist.permissions.shouldShowRationale
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun requestPushPermission(
isFirstTimeAsking: Boolean,
pushPermission: String?,
isClicked: MutableState<Boolean>,
onAllow: () -> Unit,
onDeny: () -> Unit,
onOpenSettings: () -> Unit,
): () -> Unit {
val permissionState = pushPermission?.let { permission ->
val tempPermissionState = rememberPermissionState(permission = permission)
rememberPermissionState(permission = permission) {
val isGranted = tempPermissionState.status.isGranted
val shouldShowRationale = tempPermissionState.status.shouldShowRationale
if (isGranted && !shouldShowRationale && !isFirstTimeAsking) onOpenSettings()
}
rememberPermissionState(permission = permission)
}
// Check if user granted permission and close bottom sheet
@ -44,7 +35,7 @@ fun requestPushPermission(
}
return if (permissionState == null) {
onOpenSettings
{}
} else {
permissionState::launchPermissionRequest
}

View file

@ -1,11 +1,6 @@
package com.tangem.data.settings
import android.os.SystemClock
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.PreferencesKeys.getIsFirstTimeAskingPermission
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionDaysCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionLaunchCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowInitialPermissionScreen
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowPermission
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
@ -30,47 +25,11 @@ internal class DefaultPermissionRepository(
)
}
override suspend fun isFirstTimeAskingPermission(permission: String): Boolean =
appPreferencesStore.getSyncOrDefault(
key = getIsFirstTimeAskingPermission(permission),
default = true,
)
override suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) {
appPreferencesStore.store(
key = getIsFirstTimeAskingPermission(permission),
value = value,
)
}
override suspend fun shouldAskPermission(permission: String): Boolean {
val shouldAskPermission = appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
val delayedLaunches = appPreferencesStore.getSyncOrDefault(getPermissionLaunchCount(permission), 0)
val delayedDays = appPreferencesStore.getSyncOrDefault(getPermissionDaysCount(permission), 0)
val currentLaunchCounter = appPreferencesStore.getSyncOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
val isDaysDelayed = delayedDays < nowMillis
val isLaunchesDelayed = delayedLaunches < currentLaunchCounter
return shouldAskPermission && isDaysDelayed && isLaunchesDelayed
return appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
}
override suspend fun neverAskPermission(permission: String) {
appPreferencesStore.store(key = getShouldShowPermission(permission), value = false)
}
override suspend fun delayPermissionAsking(permission: String) {
appPreferencesStore.editData {
val appLaunchCounter = it.getOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
it[getPermissionLaunchCount(permission)] = appLaunchCounter + DELAY_LAUNCH_COUNT
it[getPermissionDaysCount(permission)] = nowMillis + DELAY_DAYS_COUNT
}
}
private companion object {
const val DELAY_LAUNCH_COUNT = 5
const val DELAY_DAYS_COUNT = 3L * 24 * 3600 * 1000 // 3 days in millis
}
}

View file

@ -1,13 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class DelayPermissionRequestUseCase(
private val repository: PermissionRepository,
) {
suspend operator fun invoke(permission: String): Either<Throwable, Unit> = Either.catch {
repository.delayPermissionAsking(permission)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class IsFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) {
suspend operator fun invoke(permission: String): Either<Throwable, Boolean> = Either.catch {
repository.isFirstTimeAskingPermission(permission)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class SetFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) {
suspend operator fun invoke(permission: String): Either<Throwable, Unit> = Either.catch {
repository.setFirstTimeAskingPermission(permission, false)
}
}

View file

@ -13,18 +13,6 @@ interface PermissionRepository {
*/
suspend fun neverInitiallyShowPermissionScreen(permission: String)
/**
* Indicates which time [permission] was asked via platform dialog.
* NOTE: Use this method to indicate either reroute to settings or display platform dialog.
*/
suspend fun isFirstTimeAskingPermission(permission: String): Boolean
/**
* Sets value indicating that [permission] was asked via platform dialog.
* NOTE: Use this method to indicate either reroute to settings or display platform dialog.
*/
suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean)
/**
* Is clear to ask [permission].
* User could already granted or permanently denied permission
@ -36,9 +24,4 @@ interface PermissionRepository {
* Permanently deny [permission] and never request again
*/
suspend fun neverAskPermission(permission: String)
/**
* Delay next [permission] request for some time or active sessions
*/
suspend fun delayPermissionAsking(permission: String)
}

View file

@ -17,19 +17,15 @@ sealed class PushNotificationAnalyticEvents(
),
)
data class ButtonLater(
data class ButtonCancel(
val source: AnalyticsParam.ScreensSources,
) : PushNotificationAnalyticEvents(
event = "Button - Later",
event = "Button - Cancel",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
)
data object ButtonCancel : PushNotificationAnalyticEvents(
event = "Button - Cancel",
)
data class PermissionStatus(
val isAllowed: Boolean,
) : PushNotificationAnalyticEvents(

View file

@ -26,10 +26,9 @@ internal class PushNotificationsFragment : ComposeFragment() {
NavigationBar3ButtonsScrim()
PushNotificationsScreen(
onRequest = viewModel::onRequest,
onRequestLater = viewModel::onRequestLater,
onNeverRequest = viewModel::onNeverRequest,
onAllowPermission = viewModel::onAllowPermission,
onDenyPermission = viewModel::onDenyPermission,
onOpenSettings = viewModel::openSettings,
)
}

View file

@ -17,18 +17,15 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun PushNotificationsScreen(
onRequest: () -> Unit,
onRequestLater: () -> Unit,
onNeverRequest: () -> Unit,
onAllowPermission: () -> Unit,
onDenyPermission: () -> Unit,
onOpenSettings: () -> Unit,
) {
val isClicked = remember { mutableStateOf(false) }
val requestPushPermission = requestPushPermission(
isFirstTimeAsking = true,
isClicked = isClicked,
onAllow = onAllowPermission,
onDeny = onDenyPermission,
onOpenSettings = onOpenSettings,
pushPermission = getPushPermissionOrNull(),
)
@ -54,8 +51,8 @@ internal fun PushNotificationsScreen(
},
),
secondaryButton = ShowcaseButtonModel(
buttonText = resourceReference(R.string.common_later),
onClick = onRequestLater,
buttonText = resourceReference(R.string.common_cancel),
onClick = onNeverRequest,
),
modifier = Modifier.systemBarsPadding(),
)

View file

@ -4,11 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.domain.settings.DelayPermissionRequestUseCase
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter
@ -19,12 +15,8 @@ import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class PushNotificationViewModel @Inject constructor(
private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase,
private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
private val router: DefaultPushNotificationsRouter,
private val settingsManager: SettingsManager,
private val analyticHandler: AnalyticsEventHandler,
) : ViewModel(), PushNotificationsClickIntents {
@ -32,19 +24,14 @@ internal class PushNotificationViewModel @Inject constructor(
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories),
)
viewModelScope.launch {
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
}
}
override fun onRequestLater() {
override fun onNeverRequest() {
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories),
PushNotificationAnalyticEvents.ButtonCancel(AnalyticsParam.ScreensSources.Stories),
)
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
}
@ -55,7 +42,6 @@ internal class PushNotificationViewModel @Inject constructor(
)
viewModelScope.launch {
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
}
@ -65,11 +51,8 @@ internal class PushNotificationViewModel @Inject constructor(
PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false),
)
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
}
override fun openSettings() = settingsManager.openSettings()
}

View file

@ -3,11 +3,9 @@ package com.tangem.features.pushnotifications.impl.presentation.viewmodel
internal interface PushNotificationsClickIntents {
fun onRequest()
fun onRequestLater()
fun onNeverRequest()
fun onAllowPermission()
fun onDenyPermission()
fun openSettings()
}

View file

@ -3,11 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
data class PushNotificationsBottomSheetConfig(
val isFirstTimeRequested: Boolean,
val wasInitiallyAsk: Boolean,
val onRequest: () -> Unit,
val onRequestLater: () -> Unit,
val onNeverRequest: () -> Unit,
val onAllow: () -> Unit,
val onDeny: () -> Unit,
val openSettings: () -> Unit,
) : TangemBottomSheetConfigContent

View file

@ -39,7 +39,6 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC
val isClicked = remember { mutableStateOf(false) }
val requestPushPermission = requestPushPermission(
pushPermission = getPushPermissionOrNull(),
isFirstTimeAsking = content.isFirstTimeRequested,
isClicked = isClicked,
onAllow = {
content.onAllow()
@ -49,7 +48,6 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC
content.onDeny()
onDismiss()
},
onOpenSettings = content.openSettings,
)
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
@ -78,13 +76,9 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC
),
) {
SecondaryButton(
text = if (content.wasInitiallyAsk) {
stringResource(R.string.common_later)
} else {
stringResource(R.string.common_cancel)
},
text = stringResource(R.string.common_cancel),
onClick = {
content.onRequestLater()
content.onNeverRequest()
onDismiss()
},
modifier = Modifier.weight(1f),
@ -110,13 +104,10 @@ private fun PushNotificationsSheetContent_Preview() {
TangemThemePreview {
PushNotificationsSheetContent(
PushNotificationsBottomSheetConfig(
isFirstTimeRequested = false,
wasInitiallyAsk = false,
onRequest = {},
onRequestLater = {},
onNeverRequest = {},
onAllow = {},
onDeny = {},
openSettings = {},
),
onDismiss = {},
)

View file

@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
@ -63,11 +62,8 @@ internal class WalletViewModel @Inject constructor(
private val walletDeepLinksHandler: WalletDeepLinksHandler,
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase,
private val isFirstTimeAskingPermissionUseCase: IsFirstTimeAskingPermissionUseCase,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
private val settingsManager: SettingsManager,
analyticsEventsHandler: AnalyticsEventHandler,
) : ViewModel() {
@ -157,26 +153,14 @@ internal class WalletViewModel @Inject constructor(
delay(timeMillis = 1_800)
val isFirstTimeRequested = isFirstTimeAskingPermissionUseCase(PUSH_PERMISSION).getOrElse { true }
val wasInitiallyAsk = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { true }
val onRequestLater: (Boolean) -> Unit = { isUserDismissed ->
if (wasInitiallyAsk) {
clickIntents.onDelayAskPushPermission(isUserDismissed)
} else {
clickIntents.onNeverAskPushPermission(isUserDismissed)
}
}
stateHolder.showBottomSheet(
content = PushNotificationsBottomSheetConfig(
isFirstTimeRequested = isFirstTimeRequested,
wasInitiallyAsk = wasInitiallyAsk,
onRequest = clickIntents::onRequestPushPermission,
onRequestLater = { onRequestLater(false) },
onNeverRequest = { clickIntents.onNeverAskPushPermission(false) },
onAllow = clickIntents::onAllowPushPermission,
onDeny = clickIntents::onDenyPushPermission,
openSettings = settingsManager::openSettings,
),
onDismiss = { onRequestLater(true) },
onDismiss = { clickIntents.onNeverAskPushPermission(true) },
)
}
}

View file

@ -2,9 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.settings.DelayPermissionRequestUseCase
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import dagger.hilt.android.scopes.ViewModelScoped
@ -15,8 +13,6 @@ internal interface WalletPushPermissionClickIntents {
fun onRequestPushPermission()
fun onDelayAskPushPermission(isUserDismissed: Boolean)
fun onNeverAskPushPermission(isUserDismissed: Boolean)
fun onDenyPushPermission()
@ -26,9 +22,7 @@ internal interface WalletPushPermissionClickIntents {
@ViewModelScoped
internal class WalletPushPermissionClickIntentsImplementor @Inject constructor(
private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BaseWalletClickIntents(), WalletPushPermissionClickIntents {
@ -38,27 +32,13 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor(
analyticsEventHandler.send(
PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Main),
)
viewModelScope.launch {
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
}
}
override fun onDelayAskPushPermission(isUserDismissed: Boolean) {
if (!isUserDismissedDialog) return
isUserDismissedDialog = isUserDismissed
viewModelScope.launch {
analyticsEventHandler.send(
PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Main),
)
delayPermissionRequestUseCase(PUSH_PERMISSION)
}
}
override fun onNeverAskPushPermission(isUserDismissed: Boolean) {
if (!isUserDismissedDialog) return
isUserDismissedDialog = isUserDismissed
viewModelScope.launch {
analyticsEventHandler.send(PushNotificationAnalyticEvents.ButtonCancel)
PushNotificationAnalyticEvents.ButtonCancel(AnalyticsParam.ScreensSources.Main)
neverRequestPermissionUseCase(PUSH_PERMISSION)
}
}