Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-07 10:37:12 +03:00
commit 9488319315
29 changed files with 110 additions and 366 deletions

View file

@ -1,8 +1,6 @@
package com.tangem.tap.common.url
import android.content.Context
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY
import android.net.Uri
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
@ -36,9 +34,6 @@ internal class CustomTabsUrlOpener : UrlOpener {
)
.build()
// Open CustomTabsActivity as new task without saving into the stack
customTabsIntent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY)
customTabsIntent.launchUrl(context, Uri.parse(url))
}
}

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

@ -161,6 +161,8 @@ internal class CardSettingsViewModel @Inject constructor(
if (scanResponse.cardTypesResolver.isTangemTwins()) {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
cardSettingsInteractor.clear()
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
} else {
val card = scanResponse.card

View file

@ -6,14 +6,13 @@ import android.net.Uri
[REDACTED_AUTHOR]
*/
interface Disclaimer {
fun type(): DisclaimerType
fun getUri(): Uri
suspend fun accept()
suspend fun isAccepted(): Boolean
}
abstract class BaseDisclaimer(
protected val dataProvider: DisclaimerDataProvider,
private val dataProvider: DisclaimerDataProvider,
) : Disclaimer {
val baseUrl = "https://tangem.com"
@ -26,46 +25,11 @@ abstract class BaseDisclaimer(
}
class DummyDisclaimer : Disclaimer {
override fun type(): DisclaimerType = DisclaimerType.Tangem
override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html")
override suspend fun accept() {}
override suspend fun isAccepted(): Boolean = false
}
class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
override fun type(): DisclaimerType = DisclaimerType.Tangem
override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html")
}
class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
override fun type(): DisclaimerType = DisclaimerType.Start2Coin
override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion()))
@Suppress("ComplexMethod")
private fun filename(languageCode: String, regionCode: String?): String {
return when {
languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html"
languageCode == "de" && regionCode == "ch" -> "start2coin-de-ch-tangem.html"
languageCode == "en" && regionCode == "ch" -> "start2coin-en-ch-tangem.html"
languageCode == "it" && regionCode == "ch" -> "start2coin-it-ch-tangem.html"
languageCode == "fr" && regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
languageCode == "de" && regionCode == "at" -> "start2coin-de-at-tangem.html"
regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
regionCode == "ch" -> "start2coin-en-ch-tangem.html"
regionCode == "at" -> "start2coin-de-at-tangem.html"
else -> "start2coin-fr-fr-tangem.html"
}
}
private fun getRegion(): String? {
val cardId = dataProvider.getCardId()
if (cardId.isEmpty()) return null
return when (cardId[1]) {
'0' -> "fr"
'1' -> "ch"
'2' -> "at"
else -> null
}
}
}

View file

@ -1,58 +1,28 @@
package com.tangem.tap.features.disclaimer
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
enum class DisclaimerType {
Tangem,
Start2Coin,
;
companion object {
fun get(cardDTO: CardDTO): DisclaimerType {
return when {
cardDTO.isStart2Coin -> Start2Coin
else -> Tangem
}
}
}
fun CardDTO.createDisclaimer(): Disclaimer {
val dataProvider = provideDisclaimerDataProvider(cardId)
return TangemDisclaimer(dataProvider)
}
fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer {
val dataProvider = provideDisclaimerDataProvider(cardDTO.cardId, this)
return when (this) {
DisclaimerType.Tangem -> TangemDisclaimer(dataProvider)
DisclaimerType.Start2Coin -> Start2CoinDisclaimer(dataProvider)
}
}
fun CardDTO.createDisclaimer(): Disclaimer = DisclaimerType.get(this).createDisclaimer(this)
private fun provideDisclaimerDataProvider(cardId: String, disclaimerType: DisclaimerType): DisclaimerDataProvider {
private fun provideDisclaimerDataProvider(cardId: String): DisclaimerDataProvider {
val cardRepository = store.inject(DaggerGraphState::cardRepository)
return object : DisclaimerDataProvider {
override fun getLanguage(): String = Locale.getDefault().language
override fun getCardId(): String = cardId
override suspend fun accept() {
when (disclaimerType) {
DisclaimerType.Tangem -> cardRepository.acceptTangemTOS()
DisclaimerType.Start2Coin -> cardRepository.acceptStart2CoinTOS(cardId)
}
cardRepository.acceptTangemTOS()
}
override suspend fun isAccepted(): Boolean {
return when (disclaimerType) {
DisclaimerType.Tangem -> cardRepository.isTangemTOSAccepted()
DisclaimerType.Start2Coin -> cardRepository.isStart2CoinTOSAccepted(cardId)
}
return cardRepository.isTangemTOSAccepted()
}
}
}

View file

@ -65,9 +65,9 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
fun getScanResponse(): ScanResponse {
return when (val mode = twinCardsState.mode) {
return when (twinCardsState.mode) {
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
is CreateTwinWalletMode.RecreateWallet -> mode.scanResponse
is CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
} ?: throw NullPointerException("ScanResponse can't be NULL")
}
@ -105,6 +105,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
mainScope.launch {
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch
if (twinCardsState.mode is CreateTwinWalletMode.RecreateWallet) {
store.dispatch(GlobalAction.SaveScanResponse(twinCardsState.mode.scanResponse))
}
val scanResponse = getScanResponse()
onboardingManager?.apply {
if (!isActivationStarted(scanResponse.card.cardId)) {
@ -368,8 +372,7 @@ private fun getPopBackScreen(): KClass<out AppRoute> {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
return if (userWalletsListManager.hasUserWallets) {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false }
if (isLocked) {
AppRoute.Welcome::class

View file

@ -35,6 +35,7 @@ import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.wallet.R
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@ -44,6 +45,8 @@ object OnboardingWalletMiddleware {
val handler = onboardingWalletMiddleware
}
private const val HIDE_PROGRESS_DELAY = 400L
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
@ -161,17 +164,13 @@ private fun handleWalletAction(action: Action) {
store.dispatch(GlobalAction.Onboarding.Stop)
if (scanResponse == null) {
store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(HomeAction.ReadCard(scope = action.scope))
action.scope.launch {
readCard { newScanResponse ->
handleFinishOnboardind(newScanResponse)
}
}
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
scanResponse = updatedScanResponse,
accessCode = backupState.accessCode,
backupCardsIds = backupState.backupCardIds,
hasBackupError = backupState.hasBackupError,
)
handleFinishOnboardind(scanResponse)
}
}
is OnboardingWalletAction.ResumeBackup -> {
@ -196,6 +195,43 @@ private fun handleWalletAction(action: Action) {
}
}
private fun handleFinishOnboardind(scanResponse: ScanResponse) {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
scanResponse = updatedScanResponse,
accessCode = backupState.accessCode,
backupCardsIds = backupState.backupCardIds,
hasBackupError = backupState.hasBackupError,
)
}
private suspend fun readCard(onSuccess: (ScanResponse) -> Unit) {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
store.inject(DaggerGraphState::scanCardProcessor).scan(
analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Intro,
onProgressStateChange = { showProgress ->
if (showProgress) {
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
} else {
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
}
},
onFailure = {
Timber.e(it, "Unable to scan card")
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
},
onSuccess = onSuccess,
)
}
private suspend fun loadArtworkForUnfinishedBackup(
cardId: String,
cardPublicKey: ByteArray,

View file

@ -4,7 +4,10 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
@ -123,14 +126,14 @@ internal fun HasMoreItem(moreCount: Int) {
) {
Text(
modifier = Modifier
.padding(TangemTheme.dimens.spacing4)
.align(Alignment.Center)
.drawWithContent { if (readyToDraw) drawContent() },
text = "+$count",
style = textStyle,
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Clip,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.didOverflowHeight) {
if (textLayoutResult.hasVisualOverflow) {
textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9)
} else {
readyToDraw = true

View file

@ -35,14 +35,7 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class Disclaimer(
val isTosAccepted: Boolean,
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
companion object {
const val IS_TOS_ACCEPTED_KEY = "isTosAccepted"
}
}
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
@Serializable
data object OnboardingNote : AppRoute(path = "/onboarding/note")

View file

@ -95,8 +95,6 @@ object PreferencesKeys {
booleanPreferencesKey(name = "isTokenSwapPromoOkxShown")
}
fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region")
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

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

@ -80,24 +80,10 @@ internal class DefaultCardRepository(
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, default = false)
}
override suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)),
default = false,
)
}
override suspend fun acceptTangemTOS() {
return appPreferencesStore.store(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, true)
}
override suspend fun acceptStart2CoinTOS(cardId: String) {
appPreferencesStore.store(
key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)),
value = true,
)
}
private suspend fun AppPreferencesStore.editUsedCards(cardId: String, update: (UsedCardInfo) -> UsedCardInfo) {
editData { mutablePreferences ->
val usedCards = mutablePreferences.getUsedCards()
@ -127,16 +113,5 @@ internal class DefaultCardRepository(
.firstOrNull { it.cardId == cardId }
}
private fun getRegion(cardId: String): String? {
if (cardId.isEmpty()) return null
return when (cardId[1]) {
'0' -> "fr"
'1' -> "ch"
'2' -> "at"
else -> null
}
}
private fun createDefaultUsedCardInfo(cardId: String) = UsedCardInfo(cardId = cardId)
}

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

@ -26,10 +26,5 @@ interface CardRepository {
@Throws
suspend fun isTangemTOSAccepted(): Boolean
@Throws
suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean
suspend fun acceptTangemTOS()
suspend fun acceptStart2CoinTOS(cardId: String)
}

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

@ -7,7 +7,6 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.disclaimer.impl.model.DisclaimerModel
import com.tangem.features.disclaimer.impl.ui.DisclaimerScreen
@ -18,7 +17,6 @@ import dagger.assisted.AssistedInject
internal class DefaultDisclaimerComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: DisclaimerComponent.Params,
private val appFinisher: AppFinisher,
) : DisclaimerComponent, AppComponentContext by context {
private val model: DisclaimerModel = getOrCreateModel(params)
@ -27,13 +25,7 @@ internal class DefaultDisclaimerComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
BackHandler {
if (params.isTosAccepted) {
state.popBack()
} else {
appFinisher.finish()
}
}
BackHandler(onBack = state.popBack)
DisclaimerScreen(state = state)
}

View file

@ -5,6 +5,7 @@ import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
@ -17,12 +18,14 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
@Suppress("LongParameterList")
internal class DisclaimerModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val cardRepository: CardRepository,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val appFinisher: AppFinisher,
paramsContainer: ParamsContainer,
) : Model() {
@ -33,23 +36,34 @@ internal class DisclaimerModel @Inject constructor(
onAccept = ::onAccept,
url = DISCLAIMER_URL,
isTosAccepted = params.isTosAccepted,
popBack = router::pop,
popBack = ::popBack,
),
)
private fun onAccept(shouldAskPushPermission: Boolean) {
modelScope.launch {
private fun onAccept(shouldAskPushPermission: Boolean) = modelScope.launch {
if (params.isTosAccepted) {
router.pop()
} else {
cardRepository.acceptTangemTOS()
if (shouldAskPushPermission) {
router.push(AppRoute.PushNotification)
} else {
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
router.push(AppRoute.Home)
router.replaceAll(AppRoute.Home)
}
}
}
private fun popBack() {
if (params.isTosAccepted) {
router.pop()
} else {
appFinisher.finish()
}
}
private companion object {
const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html"
}

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,8 @@ 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 +16,9 @@ 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,18 +26,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)
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
@ -65,11 +55,9 @@ internal class PushNotificationViewModel @Inject constructor(
PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false),
)
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(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)
}
}