From 6400e4fead0570288622d4b582be1886d90bf774 Mon Sep 17 00:00:00 2001 From: Dinar Khakimov <85668474+mdrlzy@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:24:04 +0500 Subject: [PATCH] Check notification permission on new pair alert (#118) * Check notification permission on new pair alert * fix frequent sorting * Ask notification permission immediately if pair alerts already exist --- .../NotificationPermissionHelper.kt | 23 +++++++++ .../domain/usecase/CalcFrequentCurrUseCase.kt | 2 +- .../pairalert/PairAlertConditionScreen.kt | 47 +++++++++++++++---- .../pairalert/PairAlertViewModel.kt | 25 ++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/dev/arkbuilders/rate/data/permission/NotificationPermissionHelper.kt diff --git a/app/src/main/java/dev/arkbuilders/rate/data/permission/NotificationPermissionHelper.kt b/app/src/main/java/dev/arkbuilders/rate/data/permission/NotificationPermissionHelper.kt new file mode 100644 index 000000000..24cc82141 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/permission/NotificationPermissionHelper.kt @@ -0,0 +1,23 @@ +package dev.arkbuilders.rate.data.permission + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NotificationPermissionHelper @Inject constructor( + private val ctx: Context, +) { + fun isGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) == + PackageManager.PERMISSION_GRANTED + } else { + true + } + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/domain/usecase/CalcFrequentCurrUseCase.kt b/app/src/main/java/dev/arkbuilders/rate/domain/usecase/CalcFrequentCurrUseCase.kt index cc3cd61c8..53b726f29 100644 --- a/app/src/main/java/dev/arkbuilders/rate/domain/usecase/CalcFrequentCurrUseCase.kt +++ b/app/src/main/java/dev/arkbuilders/rate/domain/usecase/CalcFrequentCurrUseCase.kt @@ -32,7 +32,7 @@ class CalcFrequentCurrUseCase @Inject constructor( private fun mapToSortRating(list: List): List> { val now = OffsetDateTime.now() return list.map { stat -> - val daysPassed = Duration.between(now, stat.lastUsedDate).toDays() + val daysPassed = Duration.between(stat.lastUsedDate, now).toDays() val timeFactor = daysPassed * 0.5 stat.code to stat.count.toDouble() - timeFactor }.toList() diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt index 59d4d2a3b..66f5e34f1 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt @@ -2,6 +2,10 @@ package dev.arkbuilders.rate.presentation.pairalert +import android.Manifest +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -69,17 +73,41 @@ import timber.log.Timber @Destination @Composable fun PairAlertConditionScreen(navigator: DestinationsNavigator) { + val ctx = LocalContext.current + val notificationPermissionLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission(), + ) { isGranted -> + if (isGranted.not()) { + Toast.makeText( + ctx, + ctx.getString(R.string.alert_post_notification_permission_explanation), + Toast.LENGTH_SHORT, + ).show() + } + } + val viewModel: PairAlertViewModel = viewModel(factory = DIManager.component.pairAlertVMFactory()) val state by viewModel.collectAsState() val snackState = remember { SnackbarHostState() } - val ctx = LocalContext.current val isEmpty = state.pages.isEmpty() viewModel.collectSideEffect { effect -> when (effect) { + is PairAlertEffect.NavigateToAdd -> + navigator.navigate( + AddPairAlertScreenDestination( + pairAlertId = effect.pairId, + ), + ) + + PairAlertEffect.AskNotificationPermission -> { + notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + is PairAlertEffect.ShowSnackbarAdded -> snackState.showSnackbar(effect.visuals) @@ -118,9 +146,7 @@ fun PairAlertConditionScreen(navigator: DestinationsNavigator) { contentColor = Color.White, containerColor = ArkColor.Secondary, shape = CircleShape, - onClick = { - navigator.navigate(AddPairAlertScreenDestination()) - }, + onClick = viewModel::onNewPair, ) { Icon(Icons.Default.Add, contentDescription = "") } @@ -137,13 +163,13 @@ fun PairAlertConditionScreen(navigator: DestinationsNavigator) { when { state.noInternet -> NoInternetScreen(viewModel::onRefreshClick) state.initialized.not() -> LoadingScreen() - isEmpty -> Empty(navigator) + isEmpty -> Empty(navigator, onNewPair = viewModel::onNewPair) else -> Content( state, onDelete = viewModel::onDelete, onClick = { pair -> - navigator.navigate(AddPairAlertScreenDestination(pair.id)) + viewModel.onNewPair(pair.id) }, onEnableToggle = viewModel::onEnableToggle, ) @@ -337,7 +363,10 @@ private fun PairAlertItem( } @Composable -private fun Empty(navigator: DestinationsNavigator) { +private fun Empty( + navigator: DestinationsNavigator, + onNewPair: () -> Unit, +) { Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.align(Alignment.Center), @@ -365,9 +394,7 @@ private fun Empty(navigator: DestinationsNavigator) { ) AppButton( modifier = Modifier.padding(top = 24.dp), - onClick = { - navigator.navigate(AddPairAlertScreenDestination()) - }, + onClick = { onNewPair() }, ) { Icon( painter = painterResource(id = R.drawable.ic_add), diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertViewModel.kt index c9aa61bd1..930d45255 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertViewModel.kt @@ -3,6 +3,7 @@ package dev.arkbuilders.rate.presentation.pairalert import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import dev.arkbuilders.rate.data.permission.NotificationPermissionHelper import dev.arkbuilders.rate.domain.model.PairAlert import dev.arkbuilders.rate.domain.repo.AnalyticsManager import dev.arkbuilders.rate.domain.repo.CurrencyRepo @@ -33,6 +34,10 @@ data class PairAlertScreenState( ) sealed class PairAlertEffect { + data class NavigateToAdd(val pairId: Long? = null) : PairAlertEffect() + + data object AskNotificationPermission : PairAlertEffect() + data class ShowSnackbarAdded( val visuals: NotifyAddedSnackbarVisuals, ) : PairAlertEffect() @@ -44,6 +49,7 @@ class PairAlertViewModel( private val pairAlertRepo: PairAlertRepo, private val currencyRepo: CurrencyRepo, private val analyticsManager: AnalyticsManager, + private val notificationPermissionHelper: NotificationPermissionHelper, ) : ViewModel(), ContainerHost { override val container: Container = container( @@ -53,6 +59,14 @@ class PairAlertViewModel( init { analyticsManager.trackScreen("PairAlertScreen") + intent { + if (pairAlertRepo.getAll().isNotEmpty() && + notificationPermissionHelper.isGranted().not() + ) { + postSideEffect(PairAlertEffect.AskNotificationPermission) + } + } + intent { if (currencyRepo.isRatesAvailable().not()) { reduce { @@ -91,6 +105,15 @@ class PairAlertViewModel( }.launchIn(viewModelScope) } + fun onNewPair(pairId: Long? = null) = + intent { + if (notificationPermissionHelper.isGranted()) { + postSideEffect(PairAlertEffect.NavigateToAdd(pairId)) + } else { + postSideEffect(PairAlertEffect.AskNotificationPermission) + } + } + fun onRefreshClick() = intent { reduce { state.copy(noInternet = false) } @@ -127,12 +150,14 @@ class PairAlertViewModelFactory @Inject constructor( private val pairAlertRepo: PairAlertRepo, private val currencyRepo: CurrencyRepo, private val analyticsManager: AnalyticsManager, + private val notificationPermissionHelper: NotificationPermissionHelper, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return PairAlertViewModel( pairAlertRepo, currencyRepo, analyticsManager, + notificationPermissionHelper, ) as T } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7492cde00..4289bc332 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ No Alerts at the Moment Stay updated! We\'ll post any important notifications or changes in exchange rates here. New Alert + Application requires permission to post pair alert notifications. Alert for %1$s has been created You’ll get notified when %1$s price is %2$s %3$s %4$s Alert for %1$s has been deleted