From 9ef9542750ebb5275b210ee91f1b51824d7c48a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Jul 2025 15:09:21 +0400 Subject: [PATCH 01/11] Updated on 2026-08-14 --- .../FirebasePushNotificationsTokenProvider.kt | 8 +- .../tangem/tap/routing/utils/ChildFactory.kt | 6 +- .../local/preferences/PreferencesKeys.kt | 12 ++ .../core/ui/components/showcase/Showcase.kt | 2 +- .../DefaultNotificationsRepository.kt | 32 +++++ .../repository/NotificationsRepository.kt | 10 ++ ...letsForAutomaticallyPushEnablingUseCase.kt | 27 ++++ features/disclaimer/impl/build.gradle.kts | 1 + .../disclaimer/impl/entity/DisclaimerUM.kt | 2 +- .../disclaimer/impl/model/DisclaimerModel.kt | 6 +- .../disclaimer/impl/ui/DisclaimerScreen.kt | 12 +- .../PushNotificationsBottomSheetComponent.kt | 9 ++ .../api/PushNotificationsComponent.kt | 2 +- .../api/PushNotificationsModelCallbacks.kt | 17 +++ .../api/PushNotificationsParams.kt | 6 + .../push-notifications/impl/build.gradle.kts | 3 + ...ltPushNotificationsBottomSheetComponent.kt | 62 +++++++++ .../impl/DefaultPushNotificationsComponent.kt | 12 +- .../impl/di/PushNotificationsModule.kt | 7 ++ .../model/PushNotificationsClickIntents.kt | 4 +- .../impl/model/PushNotificationsModel.kt | 37 +++++- .../ui/PushNotificationsBottomSheet.kt | 118 ++++++++++++++++++ .../ui/PushNotificationsScreen.kt | 10 +- .../model/WalletSettingsModel.kt | 3 + features/wallet/impl/build.gradle.kts | 2 + .../wallet/child/wallet/WalletComponent.kt | 10 ++ .../wallet/child/wallet/model/WalletModel.kt | 73 +++++++++-- .../wallet/state/model/WalletDialogConfig.kt | 3 + .../presentation/wallet/ui/WalletScreen.kt | 2 - .../PushNotificationsBottomSheet.kt | 111 ---------------- 30 files changed, 452 insertions(+), 157 deletions(-) create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsForAutomaticallyPushEnablingUseCase.kt create mode 100644 features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsBottomSheetComponent.kt create mode 100644 features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsModelCallbacks.kt create mode 100644 features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt create mode 100644 features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsBottomSheetComponent.kt create mode 100644 features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt diff --git a/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt index 6167cea5a0..358bdd3d61 100644 --- a/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt +++ b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt @@ -3,10 +3,16 @@ package com.tangem.tap.data import com.google.firebase.messaging.FirebaseMessaging import com.tangem.utils.notifications.PushNotificationsTokenProvider import kotlinx.coroutines.tasks.await +import timber.log.Timber import javax.inject.Inject internal class FirebasePushNotificationsTokenProvider @Inject constructor() : PushNotificationsTokenProvider { override suspend fun getToken(): String { - return FirebaseMessaging.getInstance().token.await() + return try { + FirebaseMessaging.getInstance().token.await() + } catch (ex: Exception) { + Timber.e(ex) + "" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 825a388842..88711a8d52 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -22,6 +22,8 @@ import com.tangem.features.nft.component.NFTComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacksStub +import com.tangem.features.pushnotifications.api.PushNotificationsParams import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.staking.api.StakingComponent @@ -370,7 +372,9 @@ internal class ChildFactory @Inject constructor( is AppRoute.PushNotification -> { createComponentChild( context = context, - params = Unit, + params = PushNotificationsParams( + modelCallbacks = PushNotificationsModelCallbacksStub(), + ), componentFactory = pushNotificationsComponentFactory, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 874a358083..d1b906f30e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -134,6 +134,18 @@ object PreferencesKeys { val NOTIFICATIONS_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "notificationsEnabledStates") } + val NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY by lazy { + stringPreferencesKey( + name = "notificationsAutomaticallyEnabledStates", + ) + } + + val NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY by lazy { + booleanPreferencesKey( + name = "userAllowSendAddresses", + ) + } + val TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY by lazy { intPreferencesKey(name = "tronNetworkFeeNotificationShowCount") } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt index 8112024b54..48f79e027f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt @@ -66,7 +66,7 @@ fun Showcase( } @Composable -private fun ShowcaseButtons( +fun ShowcaseButtons( primaryButtonText: TextReference, secondaryButtonText: TextReference, onPrimaryClick: () -> Unit, diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index d1f92e8262..45d62bced2 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -82,4 +82,36 @@ internal class DefaultNotificationsRepository @Inject constructor( NotificationsEligibleNetworkConverter.convert(it) } } + + override suspend fun shouldShowSubscribeOnNotificationsAfterUpdate(): Boolean { + return appPreferencesStore.getSyncOrNull( + key = PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY, + ) == null + } + + override suspend fun isUserAllowToSubscribeOnPushNotifications(): Boolean { + return appPreferencesStore.getSyncOrDefault( + key = PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY, + default = false, + ) + } + + override suspend fun setUserAllowToSubscribeOnPushNotifications(value: Boolean) { + appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY, value) + } + + override suspend fun getWalletAutomaticallyEnabledList(): List = appPreferencesStore + .getObjectMapSync(PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY).map { + it.key + } + + override suspend fun setNotificationsWasEnabledAutomatically(userWalletId: String) { + appPreferencesStore.editData { + it.setObjectMap( + key = PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY, + value = it.getObjectMap(PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY) + .plus(userWalletId to true), + ) + } + } } \ No newline at end of file diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index a8c46854df..59f5e6e58b 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -21,4 +21,14 @@ interface NotificationsRepository { @Throws suspend fun getEligibleNetworks(): List + + suspend fun shouldShowSubscribeOnNotificationsAfterUpdate(): Boolean + + suspend fun isUserAllowToSubscribeOnPushNotifications(): Boolean + + suspend fun setUserAllowToSubscribeOnPushNotifications(value: Boolean) + + suspend fun getWalletAutomaticallyEnabledList(): List + + suspend fun setNotificationsWasEnabledAutomatically(userWalletId: String) } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsForAutomaticallyPushEnablingUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsForAutomaticallyPushEnablingUseCase.kt new file mode 100644 index 0000000000..9ba38eabe0 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsForAutomaticallyPushEnablingUseCase.kt @@ -0,0 +1,27 @@ +package com.tangem.domain.wallets.usecase + +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * Use case for retrieving wallets where automatically enabling push notifications was not applied. + * * This use case filters out wallets that have already had push notifications automatically enabled + * from the complete list of user wallets, returning only those wallets that still need to have + * push notifications automatically enabled. + * * @property userWalletsListManager Manager for user wallets list operations + * @property dispatchers Coroutine dispatcher provider for background operations + */ +class GetWalletsForAutomaticallyPushEnablingUseCase @Inject constructor( + private val userWalletsListManager: UserWalletsListManager, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(walletsListWherePushWasEnabled: List): List = + withContext(dispatchers.default) { + val allLocalWallets = userWalletsListManager.userWalletsSync.map { it.walletId } + allLocalWallets - walletsListWherePushWasEnabled.toSet() + } +} \ No newline at end of file diff --git a/features/disclaimer/impl/build.gradle.kts b/features/disclaimer/impl/build.gradle.kts index 9144b3fbb0..3454262136 100644 --- a/features/disclaimer/impl/build.gradle.kts +++ b/features/disclaimer/impl/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.card) implementation(projects.domain.settings) + implementation(projects.domain.notifications) /** Feature modules */ implementation(projects.features.disclaimer.api) diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/entity/DisclaimerUM.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/entity/DisclaimerUM.kt index 155a91269b..4e15c1b3dd 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/entity/DisclaimerUM.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/entity/DisclaimerUM.kt @@ -3,7 +3,7 @@ package com.tangem.features.disclaimer.impl.entity internal data class DisclaimerUM( val url: String, val isTosAccepted: Boolean, - val onAccept: (Boolean) -> Unit, + val onAccept: () -> Unit, val popBack: () -> Unit, ) diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index f96cb66625..39b9bde62b 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -7,6 +7,7 @@ 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.notifications.repository.NotificationsRepository import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase import com.tangem.features.disclaimer.api.components.DisclaimerComponent @@ -26,6 +27,7 @@ internal class DisclaimerModel @Inject constructor( private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val appFinisher: AppFinisher, + private val notificationsRepository: NotificationsRepository, paramsContainer: ParamsContainer, ) : Model() { @@ -40,12 +42,12 @@ internal class DisclaimerModel @Inject constructor( ), ) - private fun onAccept(shouldAskPushPermission: Boolean) = modelScope.launch { + private fun onAccept() = modelScope.launch { if (params.isTosAccepted) { router.pop() } else { cardRepository.acceptTangemTOS() - + val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() if (shouldAskPushPermission) { router.push(AppRoute.PushNotification) } else { diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt index 1174e7d634..ef34536252 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt @@ -15,9 +15,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.isGranted -import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.web.WebView import com.google.accompanist.web.WebViewNavigator import com.google.accompanist.web.rememberWebViewNavigator @@ -39,7 +36,6 @@ import com.tangem.features.disclaimer.impl.R import com.tangem.features.disclaimer.impl.entity.DisclaimerUM import com.tangem.features.disclaimer.impl.entity.DummyDisclaimer import com.tangem.features.disclaimer.impl.local.localTermsOfServices -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.withDebounce import java.nio.charset.StandardCharsets @@ -167,15 +163,11 @@ private fun WebViewNavigator.loadLocalToS() { ) } -@OptIn(ExperimentalPermissionsApi::class) @Composable -private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) { - val isPermissionGranted = getPushPermissionOrNull()?.let { permission -> - rememberPermissionState(permission = permission).status.isGranted - } ?: true +private fun BoxScope.DisclaimerButton(onAccept: () -> Unit) { PrimaryButton( text = stringResourceSafe(id = R.string.common_accept), - onClick = { onAccept(!isPermissionGranted) }, + onClick = onAccept, colors = ButtonColors( containerColor = TangemColorPalette.Light4, contentColor = TangemColorPalette.Dark6, diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsBottomSheetComponent.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsBottomSheetComponent.kt new file mode 100644 index 0000000000..b72b8c418c --- /dev/null +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsBottomSheetComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.features.pushnotifications.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +interface PushNotificationsBottomSheetComponent : ComposableBottomSheetComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt index 515492abe3..a514eb89a5 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt @@ -5,5 +5,5 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface PushNotificationsComponent : ComposableContentComponent { - interface Factory : ComponentFactory + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsModelCallbacks.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsModelCallbacks.kt new file mode 100644 index 0000000000..60582b60df --- /dev/null +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsModelCallbacks.kt @@ -0,0 +1,17 @@ +package com.tangem.features.pushnotifications.api + +interface PushNotificationsModelCallbacks { + fun onAllowSystemPermission() + fun onDenySystemPermission() + fun onDismiss() +} + +class PushNotificationsModelCallbacksStub( + val onAllowSystemPermission: () -> Unit = {}, + val onDenySystemPermission: () -> Unit = {}, + val onDismiss: () -> Unit = {}, +) : PushNotificationsModelCallbacks { + override fun onAllowSystemPermission() = onAllowSystemPermission.invoke() + override fun onDenySystemPermission() = onDenySystemPermission.invoke() + override fun onDismiss() = onDismiss.invoke() +} \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt new file mode 100644 index 0000000000..27bc4d7131 --- /dev/null +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt @@ -0,0 +1,6 @@ +package com.tangem.features.pushnotifications.api + +data class PushNotificationsParams( + val isBottomSheet: Boolean = false, + val modelCallbacks: PushNotificationsModelCallbacks, +) \ No newline at end of file diff --git a/features/push-notifications/impl/build.gradle.kts b/features/push-notifications/impl/build.gradle.kts index 68fc33bd13..2f2c875648 100644 --- a/features/push-notifications/impl/build.gradle.kts +++ b/features/push-notifications/impl/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { implementation(deps.androidx.activity.compose) /** Compose */ + implementation(deps.compose.material3) + implementation(deps.compose.ui.tooling) implementation(deps.compose.foundation) implementation(deps.compose.accompanist.systemUiController) implementation(deps.compose.accompanist.permission) @@ -39,6 +41,7 @@ dependencies { /** Domain module */ implementation(projects.domain.settings) implementation(projects.domain.notifications.toggles) + implementation(projects.domain.notifications) /** Feature modules */ implementation(projects.features.pushNotifications.api) diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsBottomSheetComponent.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsBottomSheetComponent.kt new file mode 100644 index 0000000000..259b0bacf0 --- /dev/null +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsBottomSheetComponent.kt @@ -0,0 +1,62 @@ +package com.tangem.features.pushnotifications.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent +import com.tangem.features.pushnotifications.api.PushNotificationsParams +import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel +import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsBottomSheet +import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultPushNotificationsBottomSheetComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: PushNotificationsParams, +) : PushNotificationsBottomSheetComponent, AppComponentContext by appComponentContext { + + private val model: PushNotificationsModel = getOrCreateModel(params) + + @AssistedFactory + interface Factory : PushNotificationsBottomSheetComponent.Factory { + override fun create( + context: AppComponentContext, + params: PushNotificationsParams, + ): DefaultPushNotificationsBottomSheetComponent + } + + override fun dismiss() { + params.modelCallbacks.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsState() + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + PushNotificationsBottomSheet( + config = bottomSheetConfig, + ) { + PushNotificationsContent( + onAllowClick = model::onAllowClick, + onLaterClick = model::onLaterClick, + onAllowPermission = model::onAllowPermission, + onDenyPermission = model::onDenyPermission, + showNotificationsInfo = state.showInfoAboutNotifications, + ) + } + } +} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt index 07278cd29d..458d977d4e 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt @@ -11,6 +11,7 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.NavigationBar3ButtonsScrim import com.tangem.core.ui.utils.findActivity import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.api.PushNotificationsParams import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsScreen import dagger.assisted.Assisted @@ -19,7 +20,7 @@ import dagger.assisted.AssistedInject internal class DefaultPushNotificationsComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - @Assisted params: Unit, + @Assisted private val params: PushNotificationsParams, ) : PushNotificationsComponent, AppComponentContext by appComponentContext { private val model: PushNotificationsModel = getOrCreateModel(params) @@ -31,8 +32,8 @@ internal class DefaultPushNotificationsComponent @AssistedInject constructor( BackHandler(onBack = { activity.finish() }) NavigationBar3ButtonsScrim() PushNotificationsScreen( - onRequest = model::onRequest, - onNeverRequest = model::onNeverRequest, + onAllowClick = model::onAllowClick, + onLaterClick = model::onLaterClick, onAllowPermission = model::onAllowPermission, onDenyPermission = model::onDenyPermission, showNotificationsInfo = state.showInfoAboutNotifications, @@ -41,6 +42,9 @@ internal class DefaultPushNotificationsComponent @AssistedInject constructor( @AssistedFactory interface Factory : PushNotificationsComponent.Factory { - override fun create(context: AppComponentContext, params: Unit): DefaultPushNotificationsComponent + override fun create( + context: AppComponentContext, + params: PushNotificationsParams, + ): DefaultPushNotificationsComponent } } \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt index edbd3dc188..06d4376447 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt @@ -1,7 +1,9 @@ package com.tangem.features.pushnotifications.impl.di import com.tangem.core.decompose.model.Model +import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsBottomSheetComponent import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsComponent import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel import dagger.Binds @@ -15,6 +17,11 @@ import dagger.multibindings.IntoMap @InstallIn(SingletonComponent::class) internal interface PushNotificationsModule { + @Binds + fun bindBottomSheetComponentFactory( + impl: DefaultPushNotificationsBottomSheetComponent.Factory, + ): PushNotificationsBottomSheetComponent.Factory + @Binds fun bindComponentFactory(impl: DefaultPushNotificationsComponent.Factory): PushNotificationsComponent.Factory diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt index 99b02a379c..ad7b69bc71 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt @@ -1,9 +1,9 @@ package com.tangem.features.pushnotifications.impl.model internal interface PushNotificationsClickIntents { - fun onRequest() + fun onAllowClick() - fun onNeverRequest() + fun onLaterClick() fun onAllowPermission() diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index de2d442132..ebf625c1ee 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -7,9 +7,12 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase +import com.tangem.features.pushnotifications.api.PushNotificationsParams import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.impl.presentation.state.PushNotificationsUM @@ -19,17 +22,22 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject +@Suppress("LongParameterList") @Stable @ModelScoped internal class PushNotificationsModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, private val appRouter: AppRouter, private val analyticHandler: AnalyticsEventHandler, private val notificationsFeatureToggles: NotificationsFeatureToggles, + private val notificationsRepository: NotificationsRepository, ) : Model(), PushNotificationsClickIntents { + val params: PushNotificationsParams = paramsContainer.require() + private val _state = MutableStateFlow( PushNotificationsUM( showInfoAboutNotifications = notificationsFeatureToggles.isNotificationsEnabled, @@ -38,20 +46,33 @@ internal class PushNotificationsModel @Inject constructor( val state = _state.asStateFlow() - override fun onRequest() { + override fun onAllowClick() { + if (notificationsFeatureToggles.isNotificationsEnabled) { + modelScope.launch { + notificationsRepository.setUserAllowToSubscribeOnPushNotifications(true) + } + } analyticHandler.send( PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories), ) } - override fun onNeverRequest() { + override fun onLaterClick() { + if (notificationsFeatureToggles.isNotificationsEnabled) { + modelScope.launch { + notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false) + } + } analyticHandler.send( PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories), ) modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - appRouter.push(AppRoute.Home) + params.modelCallbacks.onDenySystemPermission() + if (!params.isBottomSheet) { + appRouter.push(AppRoute.Home) + } } } @@ -62,7 +83,10 @@ internal class PushNotificationsModel @Inject constructor( modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - appRouter.push(AppRoute.Home) + params.modelCallbacks.onAllowSystemPermission() + if (!params.isBottomSheet) { + appRouter.push(AppRoute.Home) + } } } @@ -73,7 +97,10 @@ internal class PushNotificationsModel @Inject constructor( modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - appRouter.push(AppRoute.Home) + params.modelCallbacks.onDenySystemPermission() + if (!params.isBottomSheet) { + appRouter.push(AppRoute.Home) + } } } } \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt new file mode 100644 index 0000000000..bd30c68bc5 --- /dev/null +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt @@ -0,0 +1,118 @@ +package com.tangem.features.pushnotifications.impl.presentation.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerH28 +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.components.showcase.ShowcaseButtons +import com.tangem.core.ui.components.showcase.ShowcaseContent +import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.requestPushPermission +import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { + TangemModalBottomSheet( + config = config, + title = { + TangemModalBottomSheetTitle( + endIconRes = R.drawable.ic_close_24, + onEndClick = config.onDismissRequest, + ) + }, + ) { + content() + } +} + +@Composable +internal fun PushNotificationsContent( + onAllowClick: () -> Unit, + onLaterClick: () -> Unit, + onAllowPermission: () -> Unit, + onDenyPermission: () -> Unit, + showNotificationsInfo: Boolean, +) { + val requestPushPermission = requestPushPermission( + onAllow = onAllowPermission, + onDeny = onDenyPermission, + pushPermission = getPushPermissionOrNull(), + ) + + Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { + ShowcaseContent( + headerIconRes = R.drawable.ic_notifications_unread_24, + headerText = resourceReference(R.string.user_push_notification_agreement_header), + showcaseItems = persistentListOf( + ShowcaseItemModel( + R.drawable.ic_rocket_launch_24, + resourceReference(R.string.user_push_notification_agreement_argument_one), + ), + ShowcaseItemModel( + R.drawable.ic_storefront_24, + resourceReference(R.string.user_push_notification_agreement_argument_two), + ), + ).let { baseItems -> + if (showNotificationsInfo) { + baseItems.add( + ShowcaseItemModel( + R.drawable.ic_notifications_24, + resourceReference(R.string.user_push_notification_agreement_argument_three), + ), + ) + } else { + baseItems + } + }, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing40), + ) + SpacerH28() + ShowcaseButtons( + primaryButtonText = resourceReference(R.string.common_allow), + onPrimaryClick = { + onAllowClick() + requestPushPermission() + }, + secondaryButtonText = resourceReference(R.string.common_later), + onSecondaryClick = { + onLaterClick() + }, + ) + } +} + +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_PushNotificationsBottomSheet() { + TangemThemePreview { + PushNotificationsBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + ) { + PushNotificationsContent( + onAllowClick = {}, + onLaterClick = {}, + onAllowPermission = {}, + onDenyPermission = {}, + showNotificationsInfo = true, + ) + } + } +} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt index 4d6a0e3406..207ff60b54 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt @@ -14,8 +14,8 @@ import kotlinx.collections.immutable.persistentListOf @Composable internal fun PushNotificationsScreen( - onRequest: () -> Unit, - onNeverRequest: () -> Unit, + onAllowClick: () -> Unit, + onLaterClick: () -> Unit, onAllowPermission: () -> Unit, onDenyPermission: () -> Unit, showNotificationsInfo: Boolean, @@ -53,13 +53,15 @@ internal fun PushNotificationsScreen( primaryButton = ShowcaseButtonModel( buttonText = resourceReference(R.string.common_allow), onClick = { - onRequest() + onAllowClick() requestPushPermission() }, ), secondaryButton = ShowcaseButtonModel( buttonText = resourceReference(R.string.common_later), - onClick = onNeverRequest, + onClick = { + onLaterClick() + }, ), modifier = Modifier.systemBarsPadding(), ) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index b85bb4de06..4b62a5e4d7 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -24,6 +24,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase +import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.wallets.models.UserWallet @@ -72,6 +73,7 @@ internal class WalletSettingsModel @Inject constructor( private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, private val settingsManager: SettingsManager, private val permissionsRepository: PermissionRepository, + private val notificationsRepository: NotificationsRepository, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -258,6 +260,7 @@ internal class WalletSettingsModel @Inject constructor( if (isGranted) { modelScope.launch { setNotificationsEnabledUseCase(params.userWalletId, true).onRight { + notificationsRepository.setNotificationsWasEnabledAutomatically(params.userWalletId.stringValue) analyticsEventHandler.send(PushNotificationAnalyticEvents.NotificationsEnabled(true)) } } diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index a81c3a07e1..df82732dcb 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -96,6 +96,8 @@ dependencies { implementation(projects.domain.visa) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.notifications) + implementation(projects.domain.notifications.toggles) /** Feature Apis */ implementation(projects.features.details.api) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt index 21400eff17..07a629a880 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -21,6 +21,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen import com.tangem.feature.walletsettings.component.RenameWalletComponent import com.tangem.features.biometry.AskBiometryComponent import com.tangem.features.markets.entry.MarketsEntryComponent +import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent +import com.tangem.features.pushnotifications.api.PushNotificationsParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -32,6 +34,7 @@ internal class WalletComponent @AssistedInject constructor( private val renameWalletComponentFactory: RenameWalletComponent.Factory, private val marketsEntryComponentFactory: MarketsEntryComponent.Factory, private val askBiometryComponentFactory: AskBiometryComponent.Factory, + private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory, ) : ComposableContentComponent, AppComponentContext by appComponentContext { private val model: WalletModel = getOrCreateModel() @@ -66,6 +69,13 @@ internal class WalletComponent @AssistedInject constructor( ), ) } + WalletDialogConfig.AskForPushNotifications -> pushNotificationsBottomSheetComponent.create( + context = childByContext(componentContext), + params = PushNotificationsParams( + isBottomSheet = true, + modelCallbacks = model.askForPushNotificationsModelCallbacks, + ), + ) } }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index fe3e58ff9e..ed54e43c1b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -17,6 +17,8 @@ import com.tangem.core.deeplink.global.ReferralDeepLink import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase +import com.tangem.domain.notifications.repository.NotificationsRepository +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.* import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase @@ -25,6 +27,8 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.isMultiCurrency import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.domain.wallets.usecase.GetWalletsForAutomaticallyPushEnablingUseCase +import com.tangem.domain.wallets.usecase.SetNotificationsEnabledUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -36,7 +40,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction @@ -45,6 +48,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import com.tangem.features.biometry.AskBiometryComponent +import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.utils.Provider @@ -86,11 +90,16 @@ internal class WalletModel @Inject constructor( private val appRouter: AppRouter, private val routingFeatureToggle: RoutingFeatureToggle, private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase, + private val notificationsRepository: NotificationsRepository, + private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase, + private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, + private val notificationsFeatureToggles: NotificationsFeatureToggles, val screenLifecycleProvider: ScreenLifecycleProvider, val innerWalletRouter: InnerWalletRouter, ) : Model() { val askBiometryModelCallbacks = AskBiometryModelCallbacks() + val askForPushNotificationsModelCallbacks = AskForPushNotificationsCallbacks() val uiState: StateFlow = stateHolder.uiState private val walletsUpdateJobHolder = JobHolder() @@ -113,6 +122,7 @@ internal class WalletModel @Inject constructor( subscribeOnSelectedWalletFlow() subscribeToScreenBackgroundState() subscribeOnPushNotificationsPermission() + enableNotificationsIfNeeded() clickIntents.initialize(innerWalletRouter, modelScope) } @@ -196,20 +206,20 @@ internal class WalletModel @Inject constructor( private fun subscribeOnPushNotificationsPermission() { modelScope.launch { - val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) - val isPushPermissionAvailable = getPushPermissionOrNull() != null - if (!shouldRequestPush || !isPushPermissionAvailable) return@launch + val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION) + val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() + val shouldShowBottomSheet = if (notificationsFeatureToggles.isNotificationsEnabled) { + (shouldAskPermission || afterUpdate) && !shouldShowSaveWalletScreenUseCase() + } else { + val isPushPermissionAvailable = getPushPermissionOrNull() != null + shouldAskPermission && isPushPermissionAvailable + } + if (!shouldShowBottomSheet) return@launch delay(timeMillis = 1_800) - stateHolder.showBottomSheet( - content = PushNotificationsBottomSheetConfig( - onRequest = clickIntents::onRequestPushPermission, - onNeverRequest = { clickIntents.onNeverAskPushPermission(false) }, - onAllow = clickIntents::onAllowPushPermission, - onDeny = clickIntents::onDenyPushPermission, - ), - onDismiss = { clickIntents.onNeverAskPushPermission(true) }, + innerWalletRouter.dialogNavigation.activate( + configuration = WalletDialogConfig.AskForPushNotifications, ) } } @@ -433,6 +443,8 @@ internal class WalletModel @Inject constructor( it.copy(selectedWalletIndex = action.selectedWalletIndex) } } + + enableNotificationsIfNeeded() } private fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { @@ -526,6 +538,26 @@ internal class WalletModel @Inject constructor( } } + private fun enableNotificationsIfNeeded() { + if (!notificationsFeatureToggles.isNotificationsEnabled) return + modelScope.launch { + val isUserAllowToEnableNotifications = notificationsRepository.isUserAllowToSubscribeOnPushNotifications() + if (isUserAllowToEnableNotifications) { + val alreadyEnabledWallets = notificationsRepository.getWalletAutomaticallyEnabledList().map { + UserWalletId(it) + } + val walletsListWhichShouldBeEnabled = getWalletsListForEnablingUseCase(alreadyEnabledWallets) + walletsListWhichShouldBeEnabled.forEach { userWalletId -> + setNotificationsEnabledUseCase(userWalletId, true).onRight { + notificationsRepository.setNotificationsWasEnabledAutomatically(userWalletId.stringValue) + }.onLeft { + Timber.e(it) + } + } + } + } + } + inner class AskBiometryModelCallbacks : AskBiometryComponent.ModelCallbacks { override fun onAllowed() { analyticsEventsHandler.send(MainScreenAnalyticsEvent.EnableBiometrics(AnalyticsParam.OnOffState.On)) @@ -538,6 +570,23 @@ internal class WalletModel @Inject constructor( } } + inner class AskForPushNotificationsCallbacks : PushNotificationsModelCallbacks { + + override fun onAllowSystemPermission() { + innerWalletRouter.dialogNavigation.dismiss() + enableNotificationsIfNeeded() + } + + override fun onDenySystemPermission() { + innerWalletRouter.dialogNavigation.dismiss() + enableNotificationsIfNeeded() + } + + override fun onDismiss() { + innerWalletRouter.dialogNavigation.dismiss() + } + } + private companion object { const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L const val EXPRESS_STATUS_UPDATE_DELAY = 10000L diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt index 5ce5ac2dbd..77b86fba54 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt @@ -16,4 +16,7 @@ internal sealed interface WalletDialogConfig { @Serializable data object AskForBiometry : WalletDialogConfig + + @Serializable + data object AskForPushNotifications : WalletDialogConfig } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 7989bb0be0..1139c5b729 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -74,7 +74,6 @@ import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder -import com.tangem.feature.wallet.presentation.wallet.ui.components.PushNotificationsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.common.* @@ -706,7 +705,6 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) - is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig) is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt deleted file mode 100644 index e61ceddcbf..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.components.showcase.ShowcaseContent -import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.requestPushPermission -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull -import kotlinx.collections.immutable.persistentListOf - -@Composable -internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet(config = config) { - PushNotificationsSheetContent(content = it, onDismiss = config.onDismissRequest) - } -} - -@Composable -private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetConfig, onDismiss: () -> Unit) { - val requestPushPermission = requestPushPermission( - pushPermission = getPushPermissionOrNull(), - onAllow = { - content.onAllow() - onDismiss() - }, - onDeny = { - content.onDeny() - onDismiss() - }, - ) - - Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { - ShowcaseContent( - headerIconRes = R.drawable.ic_notifications_unread_24, - headerText = resourceReference(R.string.user_push_notification_agreement_header), - showcaseItems = persistentListOf( - ShowcaseItemModel( - iconRes = R.drawable.ic_rocket_launch_24, - text = resourceReference(R.string.user_push_notification_agreement_argument_one), - ), - ShowcaseItemModel( - iconRes = R.drawable.ic_storefront_24, - text = resourceReference(R.string.user_push_notification_agreement_argument_two), - ), - ), - modifier = Modifier.padding(top = TangemTheme.dimens.spacing40), - ) - Row( - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - modifier = Modifier.padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - top = TangemTheme.dimens.spacing40, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - SecondaryButton( - text = stringResourceSafe(R.string.common_later), - onClick = { - content.onNeverRequest() - onDismiss() - }, - modifier = Modifier.weight(1f), - ) - PrimaryButton( - text = stringResourceSafe(R.string.common_allow), - onClick = { - content.onRequest() - requestPushPermission() - }, - modifier = Modifier.weight(1f), - ) - } - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun PushNotificationsSheetContent_Preview() { - TangemThemePreview { - PushNotificationsSheetContent( - PushNotificationsBottomSheetConfig( - onRequest = {}, - onNeverRequest = {}, - onAllow = {}, - onDeny = {}, - ), - onDismiss = {}, - ) - } -} -// endregion \ No newline at end of file From 436a5a115f0de4b0747479e53804688c8b9e7b7d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Jul 2025 18:07:15 +0400 Subject: [PATCH 02/11] Updated on 2026-08-14 --- .../datasource/info/AndroidAppInfoProvider.kt | 3 +++ core/res/src/main/res/values-fr/strings.xml | 2 ++ core/res/src/main/res/values-ja/strings.xml | 14 +++++++++++++ core/res/src/main/res/values-ru/strings.xml | 3 +++ .../src/main/res/values-uk-rUA/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 13 ++++++++++++ .../com/tangem/utils/info/AppInfoProvider.kt | 1 + .../ShouldShowHuaweiWarningUseCase.kt | 15 +++++++++++++ .../model/WalletSettingsModel.kt | 21 +++++++++++++++++++ 9 files changed, 74 insertions(+) create mode 100644 domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt index 0bc5462596..d33089ff1b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt @@ -21,4 +21,7 @@ internal class AndroidAppInfoProvider @Inject constructor( get() = TimeZone.getDefault().id override val appVersion: String get() = appVersionProvider.versionName + override val isHuaweiDevice: Boolean + get() = Build.MANUFACTURER.equals("HUAWEI", ignoreCase = true) || + Build.BRAND.equals("HUAWEI", ignoreCase = true) } \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 5d857cb793..075f05f465 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1087,6 +1087,8 @@ de : %s à : %s validateur : %s + Les notifications sont activées, mais elles ne fonctionneront pas tant que vous n\'aurez pas autorisé les notifications dans les paramètres de votre appareil. + Notifications de transaction Minimum %s Le montant minimum pour effectuer cette transaction est %1$s. Réessayez diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7af3b7a089..af6731c66e 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -61,6 +61,13 @@ システムのデフォルト テーマ アプリ設定 + ウォレットを追加 + ログインするウォレットを選択する + お帰りなさい! + + %d 枚のカード + + スマートフォンウォレット ウォレットのバックアップが正常に完了しました。 これらの単語は紛失した場合、復元できません。必ず安全な場所に保管してください。 バックアップが完了しました @@ -846,6 +853,7 @@ %1$s 、 %2$s 宛先タグ アドレスを入力 + ENS名またはアドレスを入力 アドレスはウォレットアドレスと同じです 最低額は%sです 最小の変更は%sです @@ -927,11 +935,15 @@ 無効な金額 手数料が残高を超えています 合計金額が残高を超えています + トークンを変更してもよろしいですか? 変更後、以前のデータはリセットされます。 + トークンの変更 スワップして送信 トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。 受信者は受け取ります 受信者に送信されます 受取金額 + 変換をキャンセルしてもよろしいですか? 変更後、以前のデータはリセットされます。 + キャンセルの確認 スワップして送信 取引が送信されました 設定したいカードまたはリングをスキャンするために準備してください。 @@ -1428,6 +1440,8 @@ 取引リクエスト 取引リクエスト 無制限 + 各ペアリング試行で、新しくユニークなURIが使用されていることを確認します + URIはすでに使用されています ウォレットコネクト 破棄 バックアップが中断されました。再開しますか? diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 071f03c35f..5a430645a7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1105,6 +1105,8 @@ от: %s на: %s валидатор: %s + Уведомления включены, но не будут работать, пока вы не разрешите их в настройках устройства. + Уведомления о транзакциях Минимум %s Минимальная сумма транзакции равна %1$s. Комиссии сети Tron для популярных токенов могут быть выше. Стейкинг TRX может помочь снизить расходы на транзакции. @@ -1182,6 +1184,7 @@ Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. Откройте Tangem Wallet Получайте уведомления о входящих транзакциях в кошельке и обновлениях Tangem. + В настоящий момент push-уведомления могут не работать на устройствах Huawei. Мы уже работаем над решением этой проблемы и планируем исправление в ближайших обновлениях. Спасибо за понимание! Уведомления о транзакциях Настройки кошелька Tangem diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index fe103942ab..9b3e3dfe52 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1105,6 +1105,8 @@ від: %s до: %s валідатор: %s + Сповіщення ввімкнені, але не працюватимуть, доки ви не дозволите їх у налаштуваннях пристрою. + Сповіщення про транзакції Мінімум %s Мінімальна сума транзакції становить %1$s. Комісії мережі Tron для популярних токенів можуть бути вищими. Стейкінг TRX може допомогти знизити транзакційні витрати. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2ee799d6f3..a8961f599c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -64,6 +64,10 @@ Add Wallet Select a wallet to log in Welcome back! + + %d card + %d cards + Phone Wallet You successfully backed up your wallet. These words can’t be recovered if lost. Make sure to keep it somewhere secure. @@ -426,6 +430,7 @@ To complete setup, back up your wallet and secure app access with a Access Code. Finish Now Finish Wallet Activation + To complete setup, secure app access with Access Code. Recover an existing wallet stored in your Google Drive backup Google Drive Backup Go to backup @@ -866,6 +871,7 @@ %1$s, %2$s Destination Tag Enter address + Enter ENS name or address Address is the same as wallet address Minimum amount is %s Minimum change is %s @@ -947,11 +953,15 @@ Invalid amount Fee exceeds balance Total amount exceeds balance + Are you sure you want to change the token? After changing, previous data will be reset. + Changing token Swap and send Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly. Recipient will receive Will be sent a recipient Amount to receive + Are you sure you want to cancel the conversion? After changing, previous data will be reset. + Confirm cancelation Send with swap Transaction sent Prepare to scan card or ring you want to set up. @@ -1341,6 +1351,7 @@ Secret code to protect this wallet. Used for login and signatures. Set/Change Access Code Stay notified on wallet incoming transactions and Tangem updates. + Push notifications may currently not work on Huawei devices. We\'re actively working on a solution and will release a fix in an upcoming update. Thank you for your understanding! Transaction Notifications Wallet settings Tangem @@ -1497,6 +1508,8 @@ Transaction request Transaction request Unlimited Amount + Ensure that each pairing attempt uses a fresh and unique URI + URI already used Wallet connect Discard You have an interrupted backup. Do you want to resume? diff --git a/core/utils/src/main/java/com/tangem/utils/info/AppInfoProvider.kt b/core/utils/src/main/java/com/tangem/utils/info/AppInfoProvider.kt index 0ab3e32398..7337a4f114 100644 --- a/core/utils/src/main/java/com/tangem/utils/info/AppInfoProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/info/AppInfoProvider.kt @@ -7,4 +7,5 @@ interface AppInfoProvider { val language: String val timezone: String val appVersion: String + val isHuaweiDevice: Boolean } \ No newline at end of file diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt new file mode 100644 index 0000000000..2e7c8ffbde --- /dev/null +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.notifications + +import com.tangem.utils.info.AppInfoProvider +import com.tangem.utils.notifications.PushNotificationsTokenProvider +import javax.inject.Inject + +class ShouldShowHuaweiWarningUseCase @Inject constructor( + private val appInfoProvider: AppInfoProvider, + private val pushNotificationsTokenProvider: PushNotificationsTokenProvider, +) { + + suspend operator fun invoke(): Boolean { + return appInfoProvider.isHuaweiDevice && pushNotificationsTokenProvider.getToken().isEmpty() + } +} \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 4b62a5e4d7..f2ca439c51 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -24,6 +24,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase +import com.tangem.domain.notifications.ShouldShowHuaweiWarningUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.repositories.PermissionRepository @@ -74,6 +75,7 @@ internal class WalletSettingsModel @Inject constructor( private val settingsManager: SettingsManager, private val permissionsRepository: PermissionRepository, private val notificationsRepository: NotificationsRepository, + private val shouldShowHuaweiWarningUseCase: ShouldShowHuaweiWarningUseCase, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -230,6 +232,10 @@ internal class WalletSettingsModel @Inject constructor( private fun onCheckedNotificationsChange(isChecked: Boolean) { modelScope.launch { if (isChecked) { + if (shouldShowHuaweiWarningUseCase()) { + showHuaweiDialog() + return@launch + } state.update { value -> value.copy( requestPushNotificationsPermission = true, @@ -242,6 +248,21 @@ internal class WalletSettingsModel @Inject constructor( } } + private fun showHuaweiDialog() { + val message = DialogMessage( + message = resourceReference(R.string.wallet_settings_push_notifications_huawei_warning), + firstActionBuilder = { + EventMessageAction( + title = resourceReference(R.string.common_ok), + warning = true, + onClick = {}, + ) + }, + ) + + messageSender.send(message) + } + private fun onNotificationsDescriptionClick() { bottomSheetNavigation.activate(NetworksAvailableForNotificationBSConfig) } From 67da274df211ac8d8efb4386cf77b9449701f7dc Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Jul 2025 17:11:53 +0300 Subject: [PATCH 03/11] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 02dca1e527..3ac868e93f 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 02dca1e527f2695b69a9a1d01fc88880a0661486 +Subproject commit 3ac868e93f88498258867d457f8b8c4577b40f98 From b0b416c9c3b2bbea7646eb6c5cc4eaf3eb0188a2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 11:42:17 +0300 Subject: [PATCH 04/11] Updated on 2026-08-14 --- .../tangem/core/ui/utils/RequestPermission.kt | 43 +++++++++++++++++++ .../core/ui/utils/RequestPushPermission.kt | 33 -------------- .../ui/PushNotificationsBottomSheet.kt | 8 ++-- .../ui/PushNotificationsScreen.kt | 10 ++--- .../walletsettings/ui/WalletSettingsScreen.kt | 13 +++--- 5 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/RequestPermission.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPermission.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPermission.kt new file mode 100644 index 0000000000..9e0910e239 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPermission.kt @@ -0,0 +1,43 @@ +package com.tangem.core.ui.utils + +import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast +import androidx.compose.runtime.Composable +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberPermissionState + +/** + * Returns push permission requester. + * Handles granting permission from app settings. + */ +@Suppress("LongParameterList") +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun requestPermission(permission: String, onAllow: () -> Unit, onDeny: () -> Unit): () -> Unit { + val permissionState = rememberPermissionState( + permission = permission, + onPermissionResult = { isGranted -> + if (isGranted) { + onAllow() + } else { + onDeny() + } + }, + ) + return when { + permissionState.status.isGranted == false -> { + if (isRequirePushPermission) { + permissionState::launchPermissionRequest + } else { + onDeny // on versions below Tiramisu call onDeny directly and then open settings + } + } + else -> { + onAllow + } + } +} + +@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) +private val isRequirePushPermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt deleted file mode 100644 index 4f6cc6d0df..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.core.ui.utils - -import androidx.compose.runtime.Composable -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.rememberPermissionState - -/** - * Returns push permission requester. - * Handles granting permission from app settings. - */ -@Suppress("LongParameterList") -@OptIn(ExperimentalPermissionsApi::class) -@Composable -fun requestPushPermission(pushPermission: String?, onAllow: () -> Unit, onDeny: () -> Unit): () -> Unit { - val permissionState = pushPermission?.let { permission -> - rememberPermissionState( - permission = permission, - onPermissionResult = { isGranted -> - if (isGranted) { - onAllow() - } else { - onDeny() - } - }, - ) - } - - return if (permissionState == null) { - {} - } else { - permissionState::launchPermissionRequest - } -} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt index bd30c68bc5..fa78628411 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt @@ -19,8 +19,8 @@ import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.requestPushPermission -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull +import com.tangem.core.ui.utils.requestPermission +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import kotlinx.collections.immutable.persistentListOf @Composable @@ -46,10 +46,10 @@ internal fun PushNotificationsContent( onDenyPermission: () -> Unit, showNotificationsInfo: Boolean, ) { - val requestPushPermission = requestPushPermission( + val requestPushPermission = requestPermission( onAllow = onAllowPermission, onDeny = onDenyPermission, - pushPermission = getPushPermissionOrNull(), + permission = PUSH_PERMISSION, ) Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt index 207ff60b54..4750463f50 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt @@ -1,15 +1,15 @@ package com.tangem.features.pushnotifications.impl.presentation.ui import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.showcase.Showcase import com.tangem.core.ui.components.showcase.model.ShowcaseButtonModel import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.utils.requestPushPermission +import com.tangem.core.ui.utils.requestPermission import com.tangem.feature.pushnotifications.impl.R -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import kotlinx.collections.immutable.persistentListOf @Composable @@ -20,10 +20,10 @@ internal fun PushNotificationsScreen( onDenyPermission: () -> Unit, showNotificationsInfo: Boolean, ) { - val requestPushPermission = requestPushPermission( + val requestPushPermission = requestPermission( onAllow = onAllowPermission, onDeny = onDenyPermission, - pushPermission = getPushPermissionOrNull(), + permission = PUSH_PERMISSION, ) Showcase( diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt index 64dc1120ee..cf08dc9da6 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -28,12 +29,12 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TestTags -import com.tangem.core.ui.utils.requestPushPermission +import com.tangem.core.ui.utils.requestPermission import com.tangem.feature.walletsettings.component.preview.PreviewWalletSettingsComponent import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM import com.tangem.feature.walletsettings.entity.WalletSettingsUM import com.tangem.feature.walletsettings.impl.R -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION @Composable internal fun WalletSettingsScreen( @@ -119,14 +120,16 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) { } } - val requestPushPermission = requestPushPermission( + val requestPushPermission = requestPermission( onAllow = { state.onPushNotificationPermissionGranted(true) }, onDeny = { state.onPushNotificationPermissionGranted(false) }, - pushPermission = getPushPermissionOrNull(), + permission = PUSH_PERMISSION, ) if (state.requestPushNotificationsPermission) { - requestPushPermission() + LaunchedEffect(Unit) { + requestPushPermission() + } } } From 43108b25b5d95f644434f424dc48e5f81aff829b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 15:30:39 +0400 Subject: [PATCH 05/11] Updated on 2026-08-14 --- .../currency/UserTokensResponseAddressesEnricher.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt index 886da0da0b..579c6989fa 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt @@ -10,7 +10,9 @@ import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds class UserTokensResponseAddressesEnricher @Inject constructor( private val notificationsFeatureToggles: NotificationsFeatureToggles, @@ -28,7 +30,10 @@ class UserTokensResponseAddressesEnricher @Inject constructor( return withContext(dispatchers.default) { val networksStatuses = if (isNotificationsEnabled) { - multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() + withTimeoutOrNull( + FETCH_TIMEOUT_SECONDS.seconds, + { multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() }, + ) ?: emptySet() } else { emptySet() } @@ -61,4 +66,8 @@ class UserTokensResponseAddressesEnricher @Inject constructor( response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled) } } + + companion object { + private const val FETCH_TIMEOUT_SECONDS = 3 + } } \ No newline at end of file From 1e4e932954b13ddbe15ddd639b79e81749fd6f16 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 15:54:39 +0300 Subject: [PATCH 06/11] Updated on 2026-08-14 --- .../DefaultOnboardingMultiWalletComponent.kt | 4 ++-- .../backup/model/MultiWalletBackupModel.kt | 24 ++++++++----------- ....kt => MultiWalletScanPrimaryComponent.kt} | 10 ++++---- ...odel.kt => MultiWalletScanPrimaryModel.kt} | 16 ++++++++++++- ...anPrimary.kt => MultiWalletScanPrimary.kt} | 4 ++-- .../v2/multiwallet/impl/di/ComponentModule.kt | 6 ++--- .../impl/model/OnboardingMultiWalletState.kt | 3 +++ gradle/tangem_dependencies.toml | 2 +- 8 files changed, 41 insertions(+), 28 deletions(-) rename features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/{Wallet1ScanPrimaryComponent.kt => MultiWalletScanPrimaryComponent.kt} (85%) rename features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/{Wallet1ScanPrimaryModel.kt => MultiWalletScanPrimaryModel.kt} (72%) rename features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/{Wallet1ScanPrimary.kt => MultiWalletScanPrimary.kt} (94%) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index ff138a6d70..90696c234b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -33,7 +33,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWall import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent -import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.Wallet1ScanPrimaryComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.MultiWalletScanPrimaryComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.MultiWalletSeedPhraseComponent import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState @@ -159,7 +159,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor backButtonClickFlow = backButtonClickFlow, onBack = { stackNavigation.pop() }, ) - ScanPrimary -> Wallet1ScanPrimaryComponent( + ScanPrimary -> MultiWalletScanPrimaryComponent( context = childContext, params = childParams, onDone = { handleNavigationEvent(AddBackupDevice) }, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index cee887eceb..4628feb506 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -84,6 +84,15 @@ class MultiWalletBackupModel @Inject constructor( } analyticsEventHandler.send(OnboardingEvent.Backup.Started) + + // Clear any saved backup before starting the backup process + // also clears the primary card if it was set + backupService.discardSavedBackup() + + // Primary card from ScanTask or from the ScanPrimaryModel or from MultiWalletCreateWalletModel + // always not null for this step + val primaryCard = requireNotNull(scanResponse.primaryCard) + backupService.setPrimaryCard(primaryCard) } private fun getInitState(): MultiWalletBackupUM { @@ -93,24 +102,11 @@ class MultiWalletBackupModel @Inject constructor( finalizeButtonEnabled = false, addBackupButtonEnabled = true, addBackupButtonLoading = false, - onAddBackupClick = ::startBackupWallet, + onAddBackupClick = ::addBackupCardWithService, onFinalizeButtonClick = ::onFinalizeClick, ) } - private fun startBackupWallet() { - if (state.value.numberOfBackupCards == 0 && scanResponse.primaryCard != null) { - backupService.discardSavedBackup() - } - - val primaryCard = scanResponse.primaryCard - if (primaryCard != null) { - backupService.setPrimaryCard(primaryCard) - } - - addBackupCardWithService() - } - private fun setNumberOfBackupCards(number: Int) { // set state for adding backup cards and disable button if there is more than 2 backup cards _uiState.update { st -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/MultiWalletScanPrimaryComponent.kt similarity index 85% rename from features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/MultiWalletScanPrimaryComponent.kt index f17700e3ed..8e84c772ff 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/MultiWalletScanPrimaryComponent.kt @@ -8,18 +8,18 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams -import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel -import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.Wallet1ScanPrimary +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.MultiWalletScanPrimaryModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.MultiWalletScanPrimary import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -internal class Wallet1ScanPrimaryComponent( +internal class MultiWalletScanPrimaryComponent( context: AppComponentContext, params: MultiWalletChildParams, onDone: () -> Unit, ) : AppComponentContext by context, ComposableContentComponent { - private val model: Wallet1ScanPrimaryModel = getOrCreateModel(params) + private val model: MultiWalletScanPrimaryModel = getOrCreateModel(params) init { params.innerNavigation.update { @@ -38,7 +38,7 @@ internal class Wallet1ScanPrimaryComponent( @Composable override fun Content(modifier: Modifier) { - Wallet1ScanPrimary( + MultiWalletScanPrimary( isRing = model.isRing, onScanPrimaryClick = model::onScanPrimaryClick, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/MultiWalletScanPrimaryModel.kt similarity index 72% rename from features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/MultiWalletScanPrimaryModel.kt index 621279a14b..3a73a41370 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/MultiWalletScanPrimaryModel.kt @@ -5,21 +5,24 @@ import com.tangem.common.CompletionResult import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.util.cardTypesResolver import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.sdk.api.BackupServiceHolder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @Stable @ModelScoped -internal class Wallet1ScanPrimaryModel @Inject constructor( +internal class MultiWalletScanPrimaryModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val backupServiceHolder: BackupServiceHolder, + private val cardRepository: CardRepository, ) : Model() { private val params = paramsContainer.require() @@ -31,9 +34,20 @@ internal class Wallet1ScanPrimaryModel @Inject constructor( fun onScanPrimaryClick() { val backupService = backupServiceHolder.backupService.get() ?: return val iconScanRes = R.drawable.img_hand_scan_ring.takeIf { isRing } + backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = scanResponse.card.cardId) { result -> when (result) { is CompletionResult.Success -> { + modelScope.launch { + cardRepository.startCardActivation(cardId = scanResponse.card.cardId) + } + params.multiWalletState.update { + it.copy( + currentScanResponse = scanResponse.copy( + primaryCard = result.data, + ), + ) + } modelScope.launch { onDone.emit(Unit) } } is CompletionResult.Failure -> Unit diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/MultiWalletScanPrimary.kt similarity index 94% rename from features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/MultiWalletScanPrimary.kt index e15c5a640a..f5dd3d3d56 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/MultiWalletScanPrimary.kt @@ -17,7 +17,7 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onboarding.v2.impl.R @Composable -internal fun Wallet1ScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, modifier: Modifier = Modifier) { +internal fun MultiWalletScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, modifier: Modifier = Modifier) { Column( modifier = modifier .fillMaxSize() @@ -66,7 +66,7 @@ internal fun Wallet1ScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, @Composable private fun Preview() { TangemThemePreview { - Wallet1ScanPrimary( + MultiWalletScanPrimary( isRing = true, onScanPrimaryClick = {}, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt index 9054f73faa..45a7844f49 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -9,7 +9,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.Mul import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model.Wallet1ChooseOptionModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model.MultiWalletFinalizeModel -import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.MultiWalletScanPrimaryModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import dagger.Binds @@ -70,6 +70,6 @@ internal interface ModelModule { @Binds @IntoMap - @ClassKey(Wallet1ScanPrimaryModel::class) - fun provideModel8(model: Wallet1ScanPrimaryModel): Model + @ClassKey(MultiWalletScanPrimaryModel::class) + fun provideModel8(model: MultiWalletScanPrimaryModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt index 9f1f17f18e..62ce686c37 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -24,6 +24,9 @@ data class OnboardingMultiWalletState( * -> [Done] * * Wallet2/Ring + * + * ScanPrimary -> | (BackupService is cleared, no PrimaryCard) + * | * CreateWallet -> SeedPhrase -> AddBackupDevice -> Finalize -> [Done] * | * -> AddBackupDevice -> Finalize -> [Done] diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index c7e9b1485a..7250e5eb0c 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -7,7 +7,7 @@ tangemBlockchainSdk = "releases-5.26.0-1123" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.26.0-504" +tangemCardSdk = "releases-5.26.0-508" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From aa54b0f84244c532f31ca17b3c374edf940dda18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 18:10:31 +0300 Subject: [PATCH 07/11] Updated on 2026-08-14 --- .../DefaultSelectedUserWalletRepository.kt | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt index 7a17c85cd9..b4a621fb21 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt @@ -4,26 +4,35 @@ import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext internal class DefaultSelectedUserWalletRepository( private val secureStorage: SecureStorage, private val dispatchers: CoroutineDispatcherProvider, ) : SelectedUserWalletRepository { + + private val mutex = Mutex() + override suspend fun get(): UserWalletId? = withContext(dispatchers.io) { - secureStorage.get(StorageKey.SelectedWalletId.name) - ?.decodeToString(throwOnInvalidSequence = true) - ?.let { UserWalletId(it) } + mutex.withLock { + secureStorage.get(StorageKey.SelectedWalletId.name) + ?.decodeToString(throwOnInvalidSequence = true) + ?.let { UserWalletId(it) } + } } override suspend fun set(walletId: UserWalletId?) = withContext(dispatchers.io) { - if (walletId == null) { - secureStorage.delete(StorageKey.SelectedWalletId.name) - } else { - secureStorage.store( - data = walletId.stringValue.encodeToByteArray(throwOnInvalidSequence = true), - account = StorageKey.SelectedWalletId.name, - ) + mutex.withLock { + if (walletId == null) { + secureStorage.delete(StorageKey.SelectedWalletId.name) + } else { + secureStorage.store( + data = walletId.stringValue.encodeToByteArray(throwOnInvalidSequence = true), + account = StorageKey.SelectedWalletId.name, + ) + } } } From 2cbfdb5c66835e190a1bc3f63c7d56d0b4f900f4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 23:19:12 +0400 Subject: [PATCH 08/11] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 10 ++++------ ...aweiDeviceWithoutGoogleServicesUseCase.kt} | 2 +- .../usecase/GetIsBiometricsEnabledUseCase.kt | 11 ++++++++++ .../disclaimer/impl/model/DisclaimerModel.kt | 6 +++++- .../model/WalletSettingsModel.kt | 10 ++++++---- .../wallet/child/wallet/model/WalletModel.kt | 20 +++++++++---------- 6 files changed, 36 insertions(+), 23 deletions(-) rename domain/notifications/src/main/java/com/tangem/domain/notifications/{ShouldShowHuaweiWarningUseCase.kt => GetIsHuaweiDeviceWithoutGoogleServicesUseCase.kt} (86%) create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index f073431daa..d0b2bf34d6 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -52,7 +52,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.google.GoogleServicesHelper import com.tangem.operations.backup.BackupService @@ -464,12 +463,11 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } else { lifecycleScope.launch { val shouldShowTos = !cardRepository.isTangemTOSAccepted() - val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false } - val route = when { - shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false) - shouldShowInitialPush -> AppRoute.PushNotification - else -> AppRoute.Home + val route = if (shouldShowTos) { + AppRoute.Disclaimer(isTosAccepted = false) + } else { + AppRoute.Home } store.dispatchNavigationAction { replaceAll(route) } diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetIsHuaweiDeviceWithoutGoogleServicesUseCase.kt similarity index 86% rename from domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt rename to domain/notifications/src/main/java/com/tangem/domain/notifications/GetIsHuaweiDeviceWithoutGoogleServicesUseCase.kt index 2e7c8ffbde..50dee4a7a0 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/ShouldShowHuaweiWarningUseCase.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetIsHuaweiDeviceWithoutGoogleServicesUseCase.kt @@ -4,7 +4,7 @@ import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.notifications.PushNotificationsTokenProvider import javax.inject.Inject -class ShouldShowHuaweiWarningUseCase @Inject constructor( +class GetIsHuaweiDeviceWithoutGoogleServicesUseCase @Inject constructor( private val appInfoProvider: AppInfoProvider, private val pushNotificationsTokenProvider: PushNotificationsTokenProvider, ) { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt new file mode 100644 index 0000000000..4b04c0e690 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.wallets.usecase + +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import javax.inject.Inject + +class GetIsBiometricsEnabledUseCase @Inject constructor( + private val userWalletsListManager: UserWalletsListManager, +) { + + operator fun invoke(): Boolean = userWalletsListManager as? UserWalletsListManager.Lockable != null +} \ No newline at end of file diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index 39b9bde62b..2c45bfb510 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -7,6 +7,7 @@ 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.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase @@ -28,6 +29,8 @@ internal class DisclaimerModel @Inject constructor( private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val appFinisher: AppFinisher, private val notificationsRepository: NotificationsRepository, + private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase, + paramsContainer: ParamsContainer, ) : Model() { @@ -48,7 +51,8 @@ internal class DisclaimerModel @Inject constructor( } else { cardRepository.acceptTangemTOS() val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() - if (shouldAskPushPermission) { + val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase() + if (shouldAskPushPermission && !isHuaweiDevice) { router.push(AppRoute.PushNotification) } else { neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index f2ca439c51..8e507b166e 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -24,7 +24,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase -import com.tangem.domain.notifications.ShouldShowHuaweiWarningUseCase +import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.repositories.PermissionRepository @@ -75,7 +75,7 @@ internal class WalletSettingsModel @Inject constructor( private val settingsManager: SettingsManager, private val permissionsRepository: PermissionRepository, private val notificationsRepository: NotificationsRepository, - private val shouldShowHuaweiWarningUseCase: ShouldShowHuaweiWarningUseCase, + private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -99,6 +99,8 @@ internal class WalletSettingsModel @Inject constructor( ) { maybeWallet, nftEnabled, notificationsEnabled -> val wallet = maybeWallet.getOrNull() ?: return@combine val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase() + val isNeedShowNotifications = notificationsToggles.isNotificationsEnabled && + !getIsHuaweiDeviceWithoutGoogleServicesUseCase() state.update { value -> value.copy( items = buildItems( @@ -108,7 +110,7 @@ internal class WalletSettingsModel @Inject constructor( isNFTFeatureEnabled = nftFeatureToggles.isNFTEnabled, isNFTEnabled = nftEnabled, isNotificationsEnabled = notificationsEnabled, - isNotificationsFeatureEnabled = notificationsToggles.isNotificationsEnabled, + isNotificationsFeatureEnabled = isNeedShowNotifications, isNotificationsPermissionGranted = isNotificationsPermissionGranted(), ), ) @@ -232,7 +234,7 @@ internal class WalletSettingsModel @Inject constructor( private fun onCheckedNotificationsChange(isChecked: Boolean) { modelScope.launch { if (isChecked) { - if (shouldShowHuaweiWarningUseCase()) { + if (getIsHuaweiDeviceWithoutGoogleServicesUseCase()) { showHuaweiDialog() return@launch } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index ed54e43c1b..f8702271da 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -17,6 +17,7 @@ import com.tangem.core.deeplink.global.ReferralDeepLink import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase +import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.* @@ -25,10 +26,7 @@ import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.isMultiCurrency -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase -import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.domain.wallets.usecase.GetWalletsForAutomaticallyPushEnablingUseCase -import com.tangem.domain.wallets.usecase.SetNotificationsEnabledUseCase +import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -50,7 +48,6 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid import com.tangem.features.biometry.AskBiometryComponent import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION -import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import kotlinx.coroutines.* @@ -94,6 +91,8 @@ internal class WalletModel @Inject constructor( private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase, private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, private val notificationsFeatureToggles: NotificationsFeatureToggles, + private val getIsBiometryIsEnabledUseCase: GetIsBiometricsEnabledUseCase, + private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase, val screenLifecycleProvider: ScreenLifecycleProvider, val innerWalletRouter: InnerWalletRouter, ) : Model() { @@ -208,12 +207,11 @@ internal class WalletModel @Inject constructor( modelScope.launch { val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION) val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() - val shouldShowBottomSheet = if (notificationsFeatureToggles.isNotificationsEnabled) { - (shouldAskPermission || afterUpdate) && !shouldShowSaveWalletScreenUseCase() - } else { - val isPushPermissionAvailable = getPushPermissionOrNull() != null - shouldAskPermission && isPushPermissionAvailable - } + val isBiometryIsEnabled = getIsBiometryIsEnabledUseCase() + val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase() + val shouldShowBottomSheet = shouldAskPermission || afterUpdate + if (!isBiometryIsEnabled) return@launch + if (isHuaweiDevice) return@launch if (!shouldShowBottomSheet) return@launch delay(timeMillis = 1_800) From be687c23a00fb1eec72b9494433d6fda3736e3c1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 23:45:04 +0400 Subject: [PATCH 09/11] Updated on 2026-08-14 --- .../domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt | 6 +++--- .../tangem/feature/wallet/child/wallet/model/WalletModel.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt index 4b04c0e690..b57e8d3ec8 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt @@ -1,11 +1,11 @@ package com.tangem.domain.wallets.usecase -import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.sdk.api.TangemSdkManager import javax.inject.Inject class GetIsBiometricsEnabledUseCase @Inject constructor( - private val userWalletsListManager: UserWalletsListManager, + private val tangemSdkManager: TangemSdkManager, ) { - operator fun invoke(): Boolean = userWalletsListManager as? UserWalletsListManager.Lockable != null + operator fun invoke(): Boolean = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index f8702271da..acd7a4fce5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -207,10 +207,10 @@ internal class WalletModel @Inject constructor( modelScope.launch { val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION) val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() - val isBiometryIsEnabled = getIsBiometryIsEnabledUseCase() + val isBiometricsEnabled = getIsBiometryIsEnabledUseCase() val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase() val shouldShowBottomSheet = shouldAskPermission || afterUpdate - if (!isBiometryIsEnabled) return@launch + if (!isBiometricsEnabled) return@launch if (isHuaweiDevice) return@launch if (!shouldShowBottomSheet) return@launch From f7a34acb84402e8c52024b5b98cfce207ab4db7d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Aug 2025 11:12:27 +0300 Subject: [PATCH 10/11] Updated on 2026-08-14 --- mock_resources/config_dev.json | 169 --------------------------------- 1 file changed, 169 deletions(-) delete mode 100644 mock_resources/config_dev.json diff --git a/mock_resources/config_dev.json b/mock_resources/config_dev.json deleted file mode 100644 index 7bdbf5433b..0000000000 --- a/mock_resources/config_dev.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "amplitudeApiKey": "place_your_key_here_if_needed", - "appsFlyer": { - "appsFlyerDevKey": "place_your_key_here_if_needed", - "appsFlyerAppID": "place_your_key_here_if_needed" - }, - "blockchairApiKeys": ["place_your_key_here_if_needed"], - "blockchairAuthorizationToken": "", - "blockcypherTokens": [ - "place_your_key_here_if_needed", - "place_your_key_here_if_needed", - "place_your_key_here_if_needed" - ], - "bscQuiknodeApiKey": "", - "bscQuiknodeSubdomain": "place_your_data_here", - "getBlockAccessTokens": { - "avalanche": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "ethereum": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "ethereumClassic": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "fantom": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "rsk": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "bsc": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "polygon": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "xdai": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "cronos": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "solana": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "ton": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "tron": { - "rest": "place_your_key_here_if_needed" - }, - "cosmos-hub": { - "rest": "place_your_key_here_if_needed" - }, - "near": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "xrp": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "cardano": { - "rosetta": "place_your_key_here_if_needed" - }, - "dogecoin": { - "blockBookRest": "place_your_key_here_if_needed", - "jsonRpc": "place_your_key_here_if_needed" - }, - "litecoin": { - "blockBookRest": "place_your_key_here_if_needed", - "jsonRpc": "place_your_key_here_if_needed" - }, - "dash": { - "blockBookRest": "place_your_key_here_if_needed", - "jsonRpc": "place_your_key_here_if_needed" - }, - "bitcoin": { - "blockBookRest": "place_your_key_here_if_needed", - "jsonRpc": "place_your_key_here_if_needed" - }, - "aptos": { - "rest": "place_your_key_here_if_needed" - }, - "algorand": { - "rest": "place_your_key_here_if_needed" - }, - "polygon-zkevm": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "zksync": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "base": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "blast": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "filecoin": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "arbitrum-one": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "bitcoinCash": { - "blockBookRest": "place_your_key_here_if_needed", - "jsonRpc": "place_your_key_here_if_needed" - }, - "kusama": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "moonbeam": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "optimism": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "polkadot": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "shibarium": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "sui": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "telos": { - "jsonRpc": "place_your_key_here_if_needed" - }, - "tezos": { - "rest": "place_your_key_here_if_needed" - } - }, - "kaspaSecondaryApiUrl": "place_your_kaspa_api_here", - "infuraProjectId": "place_your_key_here_if_needed", - "mercuryoSecret": "place_your_key_here_if_needed", - "mercuryoWidgetId": "place_your_key_here_if_needed", - "moonPayApiKey": "place_your_key_here_if_needed", - "moonPayApiSecretKey": "place_your_key_here_if_needed", - "nowNodesApiKey": "place_your_key_here_if_needed", - "tonCenterApiKey": { - "mainnet": "place_your_key_here_if_needed", - "testnet": "place_your_key_here_if_needed" - }, - "quiknodeApiKey": "", - "quiknodeSubdomain": "place_your_data_here_if_needed", - "tronGridApiKey": "place_your_key_here_if_needed", - "walletConnectProjectId": "place_your_key_here_if_needed", - "chiaFireAcademyApiKey": "place_your_key_here_if_needed", - "chiaTangemApiKey": "place_your_key_here_if_needed", - "express": { - "apiKey": "place_your_key_here_if_needed", - "signVerifierPublicKey": "place_your_key_here_if_needed" - }, - "devExpress": { - "apiKey": "place_your_key_here_if_needed", - "signVerifierPublicKey": "place_your_key_here_if_needed" - }, - "hederaArkhiaKey": "place_your_key_here_if_needed", - "polygonScanApiKey": "place_your_key_here_if_needed", - "koinosProApiKey": "place_your_key_here_if_needed", - "stakeKitApiKey": "place_your_key_here_if_needed", - "bittensorDwellirKey": "place_your_key_here_if_needed", - "bittensorOnfinalityKey": "place_your_key_here_if_needed", - "alephiumTangemApiKey": "place_your_key_here_if_needed", - "moralisApiKey": "place_your_key_here_if_needed", - "nftScanApiKey": "place_your_key_here_if_needed", - "blockaidApiKey": "place_your_key_here_if_needed" -} From 18a74ce5705e930466c1d8419a2483b3da56fde1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Aug 2025 22:44:22 +0700 Subject: [PATCH 11/11] Updated on 2026-08-14 --- .../wallet/presentation/wallet/domain/Wallet2CobrandImage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt index feb8dae8d3..22e0d2f345 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt @@ -305,7 +305,7 @@ internal enum class Wallet2CobrandImage( USA( cards2ResId = R.drawable.ill_usa_card2_120_106, cards3ResId = R.drawable.ill_usa_card3_120_106, - batchIds = setOf("AF91"), + batchIds = setOf("AF91", "AF990017"), ), VeChain(