diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bbe71b61be..a003607888 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,20 +27,12 @@ configurations.all { resolutionStrategy { dependencySubstitution { - substitute(module("com.facebook.react:react-native")) - .using(module("com.facebook.react:react-android:0.72.4")) - - substitute(module("com.facebook.react:hermes-engine")) - .using(module("com.facebook.react:hermes-android:0.72.4")) - substitute(module("org.bouncycastle:bcprov-jdk15on")) .using(module("org.bouncycastle:bcprov-jdk18on:1.73")) } force( "org.bouncycastle:bcpkix-jdk15on:1.70", - "com.facebook.react:react-android:0.72.4", - "com.facebook.react:hermes-android:0.72.4", ) } } @@ -224,10 +216,6 @@ dependencies { implementation(deps.walletConnectCore) implementation(deps.walletConnectWeb3) implementation(deps.prettyLogger) - implementation("com.facebook.react:react-android:0.72.4") - implementation(deps.sprClient) { - exclude(group = "com.github.stephenc.jcip") - } /** Testing libraries */ testImplementation(deps.test.coroutine) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 8a80aff9f7..8758b045e3 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.utils.popTo import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable @@ -49,7 +48,7 @@ internal class LockUserWalletsTimer( start() if (shouldOpenWelcomeScreenOnResume) { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) } } @@ -127,7 +126,7 @@ internal class LockUserWalletsTimer( if (wasApplicationStopped) { settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) } else { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } } } } diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 9a6577a66f..eaca995355 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -52,7 +52,6 @@ import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler -import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.LegacyFeedbackManager import com.tangem.tap.common.images.createCoilImageLoader @@ -316,7 +315,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { initAnalytics(this, config) - initFeedbackManager(this, foregroundActivityObserver, store) + initFeedbackManager(this, store) } private fun initAnalytics(application: Application, config: Config) { @@ -337,11 +336,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { // ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA] } - private fun initFeedbackManager( - context: Context, - foregroundActivityObserver: ForegroundActivityObserver, - store: Store, - ) { + private fun initFeedbackManager(context: Context, store: Store) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo { return AdditionalFeedbackInfo().apply { appVersion = try { @@ -382,7 +377,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { val feedbackManager = LegacyFeedbackManager( infoHolder = additionalFeedbackInfo, logCollector = tangemLogCollector, - chatManager = ChatManager(foregroundActivityObserver), feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, getFeedbackEmailUseCase = getFeedbackEmailUseCase, ) diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt deleted file mode 100644 index 5bbe3fc679..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.common.chat - -import android.content.Context -import com.tangem.datasource.config.models.ChatConfig -import com.tangem.datasource.config.models.SprinklrConfig -import com.tangem.tap.ForegroundActivityObserver -import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener -import java.io.File - -class ChatManager(private val foregroundActivityObserver: ForegroundActivityObserver) { - private val openers = mutableMapOf() - - fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) { - val opener = openers.getOrPut(config) { - when (config) { - is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver) - } - } - - opener.open(createFeedbackFile, createLogsFile) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt deleted file mode 100644 index aff21cad05..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.common.chat.opener - -import android.content.Context -import java.io.File - -internal interface ChatOpener { - fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt deleted file mode 100644 index 0fcb2a4cf2..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.tap.common.chat.opener.implementation - -import android.annotation.SuppressLint -import android.app.Application -import android.content.Context -import android.provider.Settings -import com.spr.messengerclient.config.SPRMessenger -import com.spr.messengerclient.config.bean.SPRMessengerConfig -import com.tangem.common.extensions.guard -import com.tangem.datasource.config.models.SprinklrConfig -import com.tangem.tap.ForegroundActivityObserver -import com.tangem.tap.common.chat.opener.ChatOpener -import timber.log.Timber -import java.io.File -import java.util.Locale - -internal class SprinklrChatOpener( - private val config: SprinklrConfig, - private val foregroundActivityObserver: ForegroundActivityObserver, -) : ChatOpener { - - override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) { - val messenger = SPRMessenger.shared() - - if (messenger.config == null) { - initSprConfig(messenger) - } - - messenger.startApplication() - } - - private fun initSprConfig(messenger: SPRMessenger) { - val application = foregroundActivityObserver.foregroundActivity?.application.guard { - Timber.e("The SPR chat cannot be opened because there are no activities in foreground") - return - } - - messenger.takeOff(application, createSprConfig(application, config)) - } - - @SuppressLint("HardwareIds") - private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig { - return SPRMessengerConfig().apply { - appId = config.appId - appKey = CHAT_APP_KEY - deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID) - environment = config.environment - skin = CHAT_SKIN - locale = Locale.getDefault().language - } - } - - private companion object { - const val CHAT_APP_KEY = "com.sprinklr.messenger.release" - const val CHAT_SKIN = "MODERN" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt index 9f2f6bb335..e7713995ac 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt @@ -2,13 +2,11 @@ package com.tangem.tap.common.feedback import android.content.Context import com.tangem.core.navigation.email.EmailSender -import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector @@ -29,12 +27,10 @@ import java.io.StringWriter class LegacyFeedbackManager( val infoHolder: AdditionalFeedbackInfo, private val logCollector: TangemLogCollector, - private val chatManager: ChatManager, private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase, ) { - private var sessionFeedbackFile: File? = null private var sessionLogsFile: File? = null fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) { @@ -97,43 +93,6 @@ class LegacyFeedbackManager( } } - fun openChat(config: ChatConfig, feedbackData: FeedbackData) { - chatManager.open( - config = config, - createLogsFile = ::getLogFile, - createFeedbackFile = { context -> getFeedbackFile(context, feedbackData) }, - ) - } - - private fun getFeedbackFile(context: Context, feedbackData: FeedbackData): File? { - return try { - if (sessionFeedbackFile != null) { - return sessionFeedbackFile - } - val file = File(context.filesDir, FEEDBACK_FILE) - file.delete() - file.createNewFile() - - val feedback = feedbackData.run { - prepare(infoHolder) - joinTogether(context, infoHolder) - } - val fileWriter = FileWriter(file) - fileWriter.write(feedback) - fileWriter.close() - - if (file.exists()) { - sessionFeedbackFile = file - sessionFeedbackFile - } else { - null - } - } catch (ex: Exception) { - Timber.e(ex, "Can't create the logs file") - null - } - } - private fun getLogFile(context: Context): File? { return try { if (sessionLogsFile != null) { @@ -172,7 +131,6 @@ class LegacyFeedbackManager( private companion object { const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com" const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com" - const val FEEDBACK_FILE = "feedback.txt" const val LOGS_FILE = "logs.txt" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index c35ddefdd8..58d13a6d79 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -3,7 +3,6 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.datasource.config.ConfigManager -import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog @@ -79,7 +78,6 @@ sealed class GlobalAction : Action { data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction() data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction() - data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction() object ExchangeManager : GlobalAction() { object Init : GlobalAction() { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 957b756803..9c6882deda 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -74,26 +74,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) { scanResponse = action.scanResponse, ) } - is GlobalAction.OpenChat -> { - val globalState = store.state.globalState - val feedbackManager = globalState.feedbackManager.guard { - store.dispatchDebugErrorNotification("FeedbackManager not initialized") - return - } - val config = globalState.configManager?.config.guard { - store.dispatchDebugErrorNotification("Config not initialized") - return - } - - // if config not set -> try to get it based on a scanResponse.productType - val unsafeChatConfig = action.chatConfig ?: config.sprinklr - - val chatConfig = unsafeChatConfig.guard { - store.dispatchDebugErrorNotification("The chat config is not initialized") - return - } - feedbackManager.openChat(chatConfig, action.feedbackData) - } is GlobalAction.ExchangeManager.Init -> { val appStateSafe = appState() ?: return val config = appStateSafe.globalState.configManager?.config ?: return diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index 58d13616e4..1661cedb1d 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -1,5 +1,6 @@ package com.tangem.tap.data +import com.tangem.common.CompletionResult import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet @@ -30,7 +31,10 @@ internal class RuntimeUserWalletsStore( return userWalletsListManager.userWallets.firstOrNull() } - override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) { - userWalletsListManager.update(userWalletId, update) + override suspend fun update( + userWalletId: UserWalletId, + update: suspend (UserWallet) -> UserWallet, + ): CompletionResult { + return userWalletsListManager.update(userWalletId, update) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index d7bb96e4c3..64e1b31472 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -19,7 +19,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching +import kotlinx.coroutines.withContext import timber.log.Timber internal typealias Derivations = Map> @@ -48,13 +48,12 @@ internal class DefaultDerivationsRepository( tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations) .doOnSuccess { response -> - updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold( - onSuccess = { - validateDerivations(userWallet.scanResponse, derivations) + updatePublicKeys(userWalletId = userWalletId, keys = response.entries) + .doOnSuccess { + validateDerivations(scanResponse = it.scanResponse, derivations = derivations) return - }, - onFailure = { throw it }, - ) + } + .doOnFailure { throw it } } .doOnFailure { throw it } @@ -99,8 +98,8 @@ internal class DefaultDerivationsRepository( } } - private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result { - return runCatching(dispatchers.io) { + private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): CompletionResult { + return withContext(dispatchers.io) { userWalletsStore.update( userWalletId = userWalletId, update = { userWallet -> userWallet.updateDerivedKeys(keys) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index a72851ef35..40f3d03a11 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -245,7 +245,7 @@ class DetailsMiddleware { deleteSavedAccessCodes() store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false) - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Home) } return CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 97d4a464d9..d8b9f44ca9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -109,7 +109,10 @@ internal class CardSettingsViewModel @Inject constructor( if (isResetCardAllowed) { CardInfo.ResetToFactorySettings( - description = getResetToFactoryDescription(card.backupStatus, cardTypesResolver), + description = getResetToFactoryDescription( + isActiveBackupStatus = card.backupStatus?.isActive == true, + typesResolver = cardTypesResolver, + ), ).let(::add) } } @@ -135,10 +138,15 @@ internal class CardSettingsViewModel @Inject constructor( push( route = AppRoute.ResetToFactory( userWalletId = userWalletId, - cardSpecificInfo = AppRoute.ResetToFactory.CardSpecificInfo( - cardId = card.cardId, - backupStatus = card.backupStatus, - ), + cardId = card.cardId, + isActiveBackupStatus = card.backupStatus?.isActive == true, + backupCardsCount = when (val status = card.backupStatus) { + is CardDTO.BackupStatus.Active -> status.cardCount + is CardDTO.BackupStatus.CardLinked, + CardDTO.BackupStatus.NoBackup, + null, + -> 0 + }, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt index 5be7b9eb88..b269ee6352 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt @@ -1,15 +1,14 @@ package com.tangem.tap.features.details.ui.common.utils import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R internal fun getResetToFactoryDescription( - backupStatus: CardDTO.BackupStatus?, + isActiveBackupStatus: Boolean, typesResolver: CardTypesResolver, ): TextReference { - return if (backupStatus?.isActive != true || typesResolver.isTangemTwins()) { + return if (!isActiveBackupStatus || typesResolver.isTangemTwins()) { TextReference.Res(R.string.reset_card_without_backup_to_factory_message) } else { TextReference.Res(R.string.reset_card_with_backup_to_factory_message) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 2131cd42e2..41d0535147 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -13,7 +13,6 @@ import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.models.UserWalletId @@ -65,8 +64,15 @@ internal class ResetCardViewModel @Inject constructor( // endregion // region Data of card that was scanned on CardSettings - private val primaryCardId: String - private val primaryBackupStatus: CardDTO.BackupStatus? + private val primaryCardId: String = savedStateHandle.get(AppRoute.ResetToFactory.CARD_ID) + ?: error("CardId must be provided for ResetCardViewModel") + + private val isActiveBackupPrimaryCard = + savedStateHandle.get(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS) + ?: error("IsActiveBackupCard must be provided for ResetCardViewModel") + + private val primaryBackupCardsCount = savedStateHandle.get(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT) + ?: error("CardCount must be provided for ResetCardViewModel") // endregion // TODO: move logic to separate domain entity @@ -76,15 +82,6 @@ internal class ResetCardViewModel @Inject constructor( value = getInitialState(), ) - init { - val cardSpecificInfo = savedStateHandle.get(AppRoute.ResetToFactory.CARD_SPECIFIC_DATA) - ?.unbundle(AppRoute.ResetToFactory.CardSpecificInfo.serializer()) - ?: error("CardSpecificData must be provided for ResetCardViewModel") - - primaryCardId = cardSpecificInfo.cardId - primaryBackupStatus = cardSpecificInfo.backupStatus - } - private fun getInitialState(): ResetCardScreenState { val shouldShowResetPasswordButton = shouldShowResetPasswordButton() val warningsToShow = buildList { @@ -98,7 +95,7 @@ internal class ResetCardViewModel @Inject constructor( return ResetCardScreenState( resetButtonEnabled = false, descriptionText = getResetToFactoryDescription( - backupStatus = primaryBackupStatus, + isActiveBackupStatus = isActiveBackupPrimaryCard, typesResolver = currentCardTypesResolver, ), warningsToShow = warningsToShow, @@ -115,7 +112,7 @@ internal class ResetCardViewModel @Inject constructor( private fun shouldShowResetPasswordButton(): Boolean { val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2() - return isTangemWallet && primaryBackupStatus is CardDTO.BackupStatus.Active + return isTangemWallet && isActiveBackupPrimaryCard } private fun toggleFirstCondition(isAccepted: Boolean) { @@ -283,12 +280,6 @@ internal class ResetCardViewModel @Inject constructor( private fun getBackupCardsCount(): Int { if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0 - return when (val status = primaryBackupStatus) { - is CardDTO.BackupStatus.Active -> status.cardCount - is CardDTO.BackupStatus.CardLinked, - is CardDTO.BackupStatus.NoBackup, - null, - -> 0 - } + return primaryBackupCardsCount } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt index 3e9df5885e..5b6c8b8c85 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt @@ -13,7 +13,7 @@ internal object AttestationFailedDialog { return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { setTitle(R.string.common_error) setMessage(R.string.issuer_signature_loading_failed) - setPositiveButton(R.string.ok) { dialog, _ -> + setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() } setOnDismissListener { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt index b10741f96c..41495924b3 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt @@ -135,6 +135,7 @@ internal class TokensListMigration( derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) .onRight { addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) + store.dispatchNavigationAction { popTo() } } .onLeft { Timber.e(it, "Failed to derive public keys") } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index a716c916bb..6e471d192d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -11,8 +11,6 @@ import androidx.lifecycle.viewModelScope import androidx.paging.* import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getGreyedOutIconRes @@ -26,7 +24,6 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getNetworkName import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType @@ -326,7 +323,6 @@ internal class TokensListViewModel @Inject constructor( ) uiState = state.copy(isSavingInProgress = false) - store.dispatchNavigationAction { popTo() } } } diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index fdf0ed8db2..12be6b889d 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -12,7 +12,9 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.* +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -144,7 +146,7 @@ internal class DefaultDerivationsRepositoryTest { coEvery { tangemSdkManager.derivePublicKeys(null, any()) } returns CompletionResult.Success( DerivationTaskResponse(DerivedKeysMocks.ethereumDerivedKeys), ) - coEvery { userWalletsStore.update(defaultUserWalletId, any()) } just Runs + coEvery { userWalletsStore.update(defaultUserWalletId, any()) } returns CompletionResult.Success(userWallet) runCatching { repository.derivePublicKeys( diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 8f9796de2b..8bb8395a7d 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -5,7 +5,6 @@ import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.common.routing.bundle.bundle import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.decompose.navigation.Route -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency @@ -145,25 +144,35 @@ sealed class AppRoute(val path: String) : Route { data object AppSettings : AppRoute(path = "/app_settings") /** - * Reset to factory route + * Reset to factory * - * @property userWalletId user wallet id - * @property cardSpecificInfo info about card that was scanned on CardSettings + * @property userWalletId user wallet id + * @property cardId reset card id + * @property isActiveBackupStatus reset backup card status + * @property backupCardsCount backup cards count */ @Serializable data class ResetToFactory( val userWalletId: UserWalletId, - val cardSpecificInfo: CardSpecificInfo, - ) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}/$cardSpecificInfo"), RouteBundleParams { + val cardId: String, + val isActiveBackupStatus: Boolean, + val backupCardsCount: Int, + ) : AppRoute( + path = "/reset_to_factory" + + "/${userWalletId.stringValue}" + + "/$cardId" + + "/$isActiveBackupStatus" + + "/$backupCardsCount", + ), + RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) - @Serializable - data class CardSpecificInfo(val cardId: String, val backupStatus: CardDTO.BackupStatus?) - companion object { const val USER_WALLET_ID = "userWalletId" - const val CARD_SPECIFIC_DATA = "cardSpecificInfo" + const val CARD_ID = "cardId" + const val IS_ACTIVE_BACKUP_STATUS = "isActiveBackupStatus" + const val BACKUP_CARDS_COUNT = "backupCardsCount" } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt index 6aa2933ba2..54fe4ff322 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt @@ -1,5 +1,6 @@ package com.tangem.datasource.local.userwallet +import com.tangem.common.CompletionResult import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -14,6 +15,8 @@ interface UserWalletsStore { suspend fun getAllSyncOrNull(): List? - @Throws - suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) + suspend fun update( + userWalletId: UserWalletId, + update: suspend (UserWallet) -> UserWallet, + ): CompletionResult } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt index 91ceeb0049..fcce746f21 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt @@ -8,8 +8,6 @@ import com.tangem.common.card.EncryptionMode import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.operations.attestation.Attestation -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable import java.util.Date import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion @@ -300,19 +298,12 @@ data class CardDTO( } } - @Serializable sealed class BackupStatus { - @Serializable - @SerialName("card_linked") data class CardLinked(val cardCount: Int) : BackupStatus() - @Serializable - @SerialName("active") data class Active(val cardCount: Int) : BackupStatus() - @Serializable - @SerialName("no_backup") data object NoBackup : BackupStatus() val isActive: Boolean diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt index 9a9c5575d1..ec33cbbaaf 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt @@ -24,7 +24,7 @@ internal class DefaultDetailsComponent @AssistedInject constructor( private val model: DetailsModel = getOrCreateModel(params) private val userWalletListComponent = userWalletListComponentFactory.create( - context = child(key = "user_wallet_list"), + context = child(key = "user_wallet_list_component"), ) @Composable diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index 47ae7ed4f7..80a7f98ce1 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -1,17 +1,22 @@ package com.tangem.features.details.utils -import arrow.core.raise.* -import arrow.core.recover +import androidx.compose.ui.res.stringResource +import arrow.core.raise.Raise +import arrow.core.raise.ensureNotNull +import arrow.core.raise.fold +import arrow.core.raise.recover import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.popTo import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.components.SimpleOkDialog import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.ContentMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.models.scan.ScanResponse @@ -21,7 +26,7 @@ import com.tangem.domain.wallets.models.SaveWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.domain.wallets.usecase.SelectWalletUseCase +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase import com.tangem.features.details.impl.R import javax.inject.Inject @@ -31,7 +36,7 @@ internal class UserWalletSaver @Inject constructor( private val scanCardProcessor: ScanCardProcessor, private val saveWalletUseCase: SaveWalletUseCase, private val generateWalletNameUseCase: GenerateWalletNameUseCase, - private val selectWalletUseCase: SelectWalletUseCase, + private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase, private val reduxStateHolder: ReduxStateHolder, private val messageSender: UiMessageSender, private val router: Router, @@ -43,8 +48,6 @@ internal class UserWalletSaver @Inject constructor( val userWallet = createUserWallet(response) saveWallet(userWallet) - - router.popTo() }, recover = { error -> val message = error.message @@ -56,28 +59,44 @@ internal class UserWalletSaver @Inject constructor( ) private suspend fun Raise.saveWallet(userWallet: UserWallet) { - saveWalletUseCase(userWallet).recover { error -> - when (error) { - is SaveWalletError.WalletAlreadySaved -> selectUserWallet(userWallet) - is SaveWalletError.DataError -> { - val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) { - Error.Unknown + fold( + block = { saveWalletUseCase(userWallet).bind() }, + recover = { error -> + when (error) { + is SaveWalletError.WalletAlreadySaved -> { + if (shouldSaveUserWalletsSyncUseCase()) { + selectUserWallet() + } else { + router.popTo() + } } + is SaveWalletError.DataError -> { + val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) { + Error.Unknown + } - raise(Error.Message(messageRef)) + raise(Error.Message(messageRef)) + } } - } - }.bind() + }, + transform = { + // call only if wallet is successfully saved + reduxStateHolder.onUserWalletSelected(userWallet) - reduxStateHolder.onUserWalletSelected(userWallet) + router.popTo() + }, + ) } - private suspend fun Raise.selectUserWallet(userWallet: UserWallet) { - withError({ Error.Unknown }) { - selectWalletUseCase(userWallet.walletId).bind() - } - - router.popTo() + private fun selectUserWallet() { + messageSender.send( + message = ContentMessage { onDismiss -> + SimpleOkDialog( + message = stringResource(id = R.string.user_wallet_list_error_wallet_already_saved), + onDismissDialog = onDismiss, + ) + }, + ) } private suspend fun Raise.createUserWallet(response: ScanResponse): UserWallet { 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 b3740659cc..208feec00e 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 @@ -59,7 +59,11 @@ internal fun DisclaimerScreen(state: DisclaimerUM) { .background(backgroundColor) .statusBarsPadding(), ) { - Column(modifier = Modifier.padding(bottom = bottomPadding)) { + Column( + modifier = Modifier + .padding(bottom = bottomPadding) + .fillMaxSize(), + ) { TangemTopAppBar( title = resourceReference(R.string.disclaimer_title), startButton = TopAppBarButtonUM( @@ -110,6 +114,7 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) { it.setBackgroundColor(backgroundColor.toArgb()) }, client = remember { DisclaimerWebViewClient() }, + modifier = Modifier.fillMaxSize(), ) AnimatedVisibility( @@ -117,6 +122,9 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) { label = "Loading state change animation", enter = fadeIn(), exit = fadeOut(), + modifier = Modifier + .fillMaxSize() + .background(backgroundColor), ) { Box( modifier = Modifier diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 9ea4aa95ee..378b55e62d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -32,6 +31,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.OkxPromoNotification +import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.txHistoryItems import com.tangem.core.ui.event.EventEffect @@ -64,7 +64,16 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { val snackbarHostState = remember { SnackbarHostState() } Scaffold( topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + snackbarHost = { + TangemSnackbarHost( + modifier = Modifier.padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = bottomBarHeight + TangemTheme.dimens.spacing16, + ), + hostState = snackbarHostState, + ) + }, contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars), containerColor = TangemTheme.colors.background.secondary, ) { scaffoldPaddings -> 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 9e02bbe2c8..a6bb2a02db 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 @@ -368,7 +368,9 @@ private fun BaseScaffoldWithMarkets( WalletSnackbarHost( snackbarHostState = it, event = state.event, - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing4) + .navigationBarsPadding(), ) }, containerColor = TangemTheme.colors.background.secondary, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a15817a145..3a9681b918 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -341,6 +341,8 @@ internal class WalletViewModel @Inject constructor( } private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { + walletScreenContentLoader.cancel(action.deletedWalletId) + walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 377070533f..56d140ad13 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -254,7 +254,6 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" } walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" } prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" } -sprClient = { module = "com.spr:messengerclient", version.ref = "spr-client" } chucker = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" } chuckerStub = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" } mlKit-barcodeScanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlKit-barcodeScanning" } diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index d65c211b5d..1d659a2d09 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -65,9 +65,7 @@ private fun AppExtension.configureBuildTypes() { private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buildType: BuildType) { when (buildType) { - BuildType.Release, - BuildType.External, - -> { + BuildType.Release -> { isDebuggable = false isMinifyEnabled = false proguardFiles(extension.getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") @@ -76,6 +74,11 @@ private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buil isDebuggable = true isMinifyEnabled = false } + BuildType.External -> { + initWith(extension.buildTypes.getByName(BuildType.Release.id)) + matchingFallbacks.add(BuildType.Release.id) + signingConfig = extension.signingConfigs.getByName(BuildType.Debug.id) + } BuildType.Internal, BuildType.Mocked, -> {