From fdd2b563bda130fb1e60f1b0aeacfde1c780127d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Dec 2022 20:34:08 +0400 Subject: [PATCH 01/42] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 11 + .../com/tangem/tap/LockUserWalletsTimer.kt | 26 +- .../com/tangem/tap/common/IntentHandler.kt | 44 ++- .../converters/BasicEventConverter.kt | 10 +- .../tangem/tap/common/extensions/String.kt | 7 +- .../AccessCodeRequestPolicyMiddleware.kt | 27 ++ .../com/tangem/tap/common/redux/AppState.kt | 1 + .../tap/common/redux/global/GlobalAction.kt | 2 +- .../tap/common/redux/global/GlobalReducer.kt | 2 +- .../redux/navigation/NavigationMiddleware.kt | 20 +- .../redux/navigation/NavigationReducer.kt | 2 + .../tap/common/shop/TangemShopService.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 28 +- .../com/tangem/tap/domain/TapWalletManager.kt | 6 +- .../tap/domain/model/TotalFiatBalance.kt | 4 - .../com/tangem/tap/domain/model/UserWallet.kt | 14 +- .../tap/domain/model/WalletDataModel.kt | 14 - .../tap/domain/model/WalletStoreModel.kt | 53 ++- .../model/builders/UserWalletBuilder.kt | 72 +++- .../model/builders/WalletStoreBuilder.kt | 13 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 19 +- .../DefaultTotalFiatBalanceCalculator.kt | 13 - .../domain/twins/CreateFirstTwinWalletTask.kt | 3 + .../twins/CreateSecondTwinWalletTask.kt | 2 + .../tap/domain/twins/FinalizeTwinTask.kt | 2 + .../tap/domain/twins/TwinCardsManager.kt | 5 +- .../userWalletList/UserWalletsListManager.kt | 13 +- .../BiometricUserWalletsListManager.kt | 211 +++++------- .../DummyUserWalletsListManager.kt | 10 +- .../model/UserWalletInformation.kt | 1 + .../repository/UserWalletsKeysRepository.kt | 25 ++ .../UserWalletsPublicInformationRepository.kt | 2 + ...erWalletsSensitiveInformationRepository.kt | 3 +- .../BiometricUserWalletsKeysRepository.kt | 173 ++++++++-- ...tUserWalletsPublicInformationRepository.kt | 52 +-- ...erWalletsSensitiveInformationRepository.kt | 185 +++++++---- .../tap/domain/userWalletList/utils/Mapper.kt | 2 + .../WalletCurrenciesManager.kt | 3 +- .../DefaultWalletCurrenciesManager.kt | 212 ++++++------ .../walletStores/WalletStoresManager.kt | 6 +- .../DefaultWalletStoresManager.kt | 63 ++-- .../DummyWalletStoresManager.kt | 6 +- .../utils/CompletitionsResultOperations.kt | 27 -- .../repository/WalletAmountsRepository.kt | 28 +- .../repository/WalletManagersRepository.kt | 7 +- .../repository/WalletStoresRepository.kt | 1 + .../DefaultWalletAmountsRepository.kt | 309 +++++++++--------- .../DefaultWalletManagersRepository.kt | 53 +-- .../DefaultWalletStoresRepository.kt | 44 +-- .../utils/WalletDataOperations.kt | 3 +- .../utils/WalletStoreOperations.kt | 69 ++-- .../storage/WalletManagerStorage.kt | 6 +- .../storage/WalletStoresStorage.kt | 21 +- .../details/redux/DetailsMiddleware.kt | 40 ++- .../ui/cardsettings/CardSettingsScreen.kt | 8 +- .../details/ui/details/DetailsScreen.kt | 6 + .../disclaimer/redux/DisclaimerAction.kt | 14 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 10 +- .../disclaimer/redux/DisclaimerReducer.kt | 3 +- .../disclaimer/redux/DisclaimerState.kt | 1 + .../disclaimer/ui/DisclaimerFragment.kt | 6 + .../tap/features/home/redux/HomeMiddleware.kt | 17 +- .../features/onboarding/OnboardingHelper.kt | 15 - .../twins/redux/TwinCardsMiddleware.kt | 24 +- .../products/twins/ui/TwinsCardsFragment.kt | 6 + .../saveWallet/redux/SaveWalletMiddleware.kt | 7 - .../send/redux/middlewares/SendMiddleware.kt | 35 +- .../features/tokens/redux/TokensMiddleware.kt | 4 +- .../features/tokens/ui/AddTokensFragment.kt | 3 +- .../tokens/ui/compose/CurrenciesScreen.kt | 6 + .../tap/features/wallet/redux/WalletAction.kt | 21 +- .../tap/features/wallet/redux/WalletState.kt | 106 +++--- .../wallet/redux/middlewares/Mapper.kt | 133 ++++++++ .../middlewares/MultiWalletMiddleware.kt | 2 +- .../middlewares/TradeCryptoMiddleware.kt | 6 +- .../middlewares/WalletDialogsMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 90 ++--- .../redux/middlewares/WarningsMiddleware.kt | 6 +- .../redux/reducers/MultiWalletReducer.kt | 59 ++-- .../redux/reducers/OnWalletLoadedReducer.kt | 10 +- .../redux/reducers/TotalBalanceOperations.kt | 1 + .../wallet/redux/reducers/WalletReducer.kt | 187 +++-------- .../tap/features/wallet/ui/BalanceWidget.kt | 3 +- .../wallet/ui/WalletDetailsFragment.kt | 156 ++++++--- .../tap/features/wallet/ui/WalletFragment.kt | 13 +- .../wallet/ui/adapters/WalletAdapter.kt | 5 +- .../ui/test/TestWalletDetailsActions.kt | 2 +- .../wallet/ui/wallet/MultiWalletView.kt | 74 ++++- .../ui/wallet/SaltPaySingleWalletView.kt | 2 +- .../wallet/ui/wallet/SingleWalletView.kt | 6 +- .../features/wallet/ui/wallet/WalletView.kt | 2 + .../walletSelector/redux/UserWalletModel.kt | 3 +- .../redux/WalletSelectorAction.kt | 10 +- .../redux/WalletSelectorMiddleware.kt | 180 +++++----- .../redux/WalletSelectorReducer.kt | 18 +- .../redux/WalletSelectorState.kt | 3 +- .../tap/features/walletSelector/ui/Mapper.kt | 6 +- .../ui/WalletSelectorBottomSheetFragment.kt | 23 +- .../ui/WalletSelectorScreenState.kt | 5 +- .../ui/WalletSelectorViewModel.kt | 50 +-- .../walletSelector/ui/components/MockData.kt | 11 +- .../WallectSelectorScreenContent.kt | 165 +++++----- .../ui/components/WalletItem.kt | 15 +- .../walletSelector/ui/model/UserWalletItem.kt | 15 +- .../welcome/redux/WelcomeMiddleware.kt | 11 +- .../tap/persistence/PreferencesStorage.kt | 14 + .../tangem/tap/proxy/DerivationManagerImpl.kt | 2 +- app/src/main/res/layout/fragment_wallet.xml | 4 +- .../res/layout/fragment_wallet_details.xml | 38 +-- .../layout/item_currency_wallet_content.xml | 128 ++++---- .../res/layout/layout_card_total_balance.xml | 47 ++- buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 3 +- .../src/main/res/values-de/strings_final.xml | 1 + .../src/main/res/values-fr/strings_final.xml | 1 + .../src/main/res/values-it/strings_final.xml | 1 + .../src/main/res/values-ru/strings_final.xml | 1 + .../res/src/main/res/values/strings_final.xml | 1 + .../fragments/ComposeBottomSheetFragment.kt | 10 +- .../tangem/core/ui/fragments/FragmentExt.kt | 14 + 121 files changed, 2218 insertions(+), 1594 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a15171a939..1876b0038c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -198,6 +198,7 @@ dependencies { implementation(Library.armadillo) implementation(Library.googlePlayServicesWallet) implementation(Library.composeShimmer) + implementation(Library.mviCoreWatcher) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e8a5618a92..e7d11764b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,17 @@ + + + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 0bfb76a8ac..426f49ca25 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -16,7 +16,7 @@ import kotlin.time.Duration internal class LockUserWalletsTimer( owner: LifecycleOwner, - private val duration: Duration = with(Duration) { 5.minutes }, + private val duration: Duration = with(Duration) { 10.minutes }, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -25,8 +25,6 @@ internal class LockUserWalletsTimer( field?.cancel() field = value } - private var isStopped = false - private var openWelcomeScreenWhenResumed = false init { lifecycle.addObserver(this) @@ -35,22 +33,22 @@ internal class LockUserWalletsTimer( override fun onResume(owner: LifecycleOwner) { Timber.d( """ - Owner resumed - |- Was stopped: $isStopped - |- Need to open welcome screen: $openWelcomeScreenWhenResumed - """.trimIndent(), + Owner resumed + |- Was stopped: ${preferencesStorage.wasApplicationStopped} + |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume} + """.trimIndent(), ) - isStopped = false + preferencesStorage.wasApplicationStopped = false start() - if (openWelcomeScreenWhenResumed) { + if (preferencesStorage.shouldOpenWelcomeScreenOnResume) { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) - openWelcomeScreenWhenResumed = false + preferencesStorage.shouldOpenWelcomeScreenOnResume = false } } override fun onStop(owner: LifecycleOwner) { Timber.d("Owner stopped") - isStopped = true + preferencesStorage.wasApplicationStopped = true } override fun onDestroy(owner: LifecycleOwner) { @@ -103,13 +101,13 @@ internal class LockUserWalletsTimer( Timber.d( """ Finished - |- App is stopped: $isStopped + |- App is stopped: ${preferencesStorage.wasApplicationStopped} |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) userWalletsListManager.lock() - if (isStopped) { - openWelcomeScreenWhenResumed = true + if (preferencesStorage.wasApplicationStopped) { + preferencesStorage.shouldOpenWelcomeScreenOnResume = true } else { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index cfee84fd69..4ac3547aa0 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag +import com.tangem.tap.common.extensions.removePrefixOrNull import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.walletconnect.WalletConnectManager @@ -14,14 +15,20 @@ import timber.log.Timber class IntentHandler { - private val TRANSACTION_ID_PARAM = "transactionId" - private val CURRENCY_CODE_PARAM = "baseCurrencyCode" - private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" - private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - fun handleWalletConnectLink(intent: Intent?) { - if (intent?.scheme == WalletConnectManager.WC_SCHEME) { - store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString())) + val wcUri = when (intent?.scheme) { + WalletConnectManager.WC_SCHEME -> { + intent.data?.toString() + } + TANGEM_SCHEME -> { + intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX) + } + else -> { + null + } + } + if (wcUri != null) { + store.dispatch(WalletConnectAction.HandleDeepLink(wcUri)) } } @@ -54,16 +61,25 @@ class IntentHandler { Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") - store.dispatch(WalletAction.TradeCryptoAction.SendCrypto( - currencyId = currency, - amount = amount, - destinationAddress = destinationAddress, - transactionId = transactionID - )) + store.dispatch( + WalletAction.TradeCryptoAction.SendCrypto( + currencyId = currency, + amount = amount, + destinationAddress = destinationAddress, + transactionId = transactionID, + ), + ) } catch (exception: Exception) { Timber.d("Not MoonPay URL") } } - + companion object { + private const val TRANSACTION_ID_PARAM = "transactionId" + private const val CURRENCY_CODE_PARAM = "baseCurrencyCode" + private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" + private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" + private const val TANGEM_SCHEME = "tangem" + private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 301ae0cb07..45d55aac2a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -25,7 +25,7 @@ class BasicSignInEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null return Basic.SignedIn( - state = AnalyticsParam.CardBalanceState.from(value.walletsData), + state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { @@ -44,7 +44,7 @@ class BasicTopUpEventConverter( val data = BasicTopUpFilter.Data( walletId = scanResponse.card.userWalletId.stringValue, - cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData), + cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) return Basic.ToppedUp(cardCurrency).apply { filterData = data } @@ -61,16 +61,16 @@ private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List acc + i } - if (balancesCount != state.wallets.size) return false + if (balancesCount != state.walletsStores.size) return false return true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 7d54a7bf92..021cc3a7f2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -62,4 +62,9 @@ fun String.toQrCode(): Bitmap { return bmp } -fun String.urlEncode(): String = Uri.encode(this) \ No newline at end of file +fun String.urlEncode(): String = Uri.encode(this) + +fun String.removePrefixOrNull(prefix: String): String? = when { + startsWith(prefix) -> substring(prefix.length) + else -> null +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt new file mode 100644 index 0000000000..e9aaabfd70 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.redux + +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.preferencesStorage +import com.tangem.tap.tangemSdkManager +import org.rekotlin.Middleware + +class AccessCodeRequestPolicyMiddleware { + val middleware: Middleware = { _, _ -> + { next -> + { action -> + if (action is GlobalAction.SaveScanResponse) { + updateAccessCodeRequestPolicy(action.scanResponse) + } + next(action) + } + } + } + + private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + scanResponse.card.isAccessCodeSet, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index fad0770f65..9f7c34b90f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -93,6 +93,7 @@ data class AppState( SaveWalletMiddleware().middleware, WalletSelectorMiddleware().middleware, LockUserWalletsTimerMiddleware().middleware, + AccessCodeRequestPolicyMiddleware().middleware, ) } } 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 d4c5620fb3..1f51374cae 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 @@ -64,7 +64,7 @@ sealed class GlobalAction : Action { object Increment : GlobalAction() } - data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction() + data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction() data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index f4aa80ad03..543e734f8f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -33,7 +33,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.ScanFailsCounter.Reset -> { globalState.copy(scanCardFailsCounter = 0) } - is GlobalAction.SaveScanNoteResponse -> { + is GlobalAction.SaveScanResponse -> { appStateHolder.scanResponse = action.scanResponse domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index c8457793e5..871ef1ea5c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -28,15 +28,17 @@ val navigationMiddleware: Middleware = { _, state -> ) } is NavigationAction.PopBackTo -> { - when (val screen = action.screen) { - AppScreen.Home, - AppScreen.Welcome, - -> { - navState?.activity?.get()?.popBackTo(screen, inclusive = true) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - } - else -> { - navState?.activity?.get()?.popBackTo(screen, action.inclusive) + if (navState?.backStack?.lastOrNull() != action.screen) { + when (val screen = action.screen) { + AppScreen.Home, + AppScreen.Welcome, + -> { + navState?.activity?.get()?.popBackTo(screen, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } + else -> { + navState?.activity?.get()?.popBackTo(screen, action.inclusive) + } } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index 7d63cb95ee..b6f7522586 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -20,6 +20,8 @@ private fun internalReduce(action: Action, state: AppState): NavigationState { navState.copy(backStack = navState.backStack + navigationAction.screen) } is NavigationAction.PopBackTo -> { + if (navState.backStack.lastOrNull() == navigationAction.screen) return navState + val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() val index = navState.backStack.lastIndexOf(screen) + 1 state.navigationState.copy(backStack = navState.backStack.subList(0, index)) diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 271886dea1..808cb5bbbc 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -208,8 +208,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { } companion object { - const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2" - const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3" + const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S" + const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S" val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU) } } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 81ddf880c3..01350c6c63 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -46,6 +46,14 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + + private val userCodeRepository by lazy { + UserCodeRepository( + biometricManager = tangemSdk.biometricManager, + secureStorage = tangemSdk.secureStorage, + ) + } + val canUseBiometry: Boolean get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics @@ -119,8 +127,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { - return createUserCodeRepository().save( - cardIds = cardsIds, + return userCodeRepository.save( + cardsIds = cardsIds, userCode = UserCode( type = UserCodeType.AccessCode, stringValue = accessCode, @@ -128,8 +136,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } + suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { + return userCodeRepository.delete(cardsIds.toSet()) + } + suspend fun clearSavedUserCodes(): CompletionResult { - return createUserCodeRepository().clear() + return userCodeRepository.clear() } suspend fun setPasscode(cardId: String?): CompletionResult { @@ -166,9 +178,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanCard( cardId: String? = null, + allowRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { return runTaskAsyncReturnOnMain( - runnable = ScanTask(), + runnable = ScanTask(allowRequestAccessCodeFromRepository), cardId = cardId, initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) @@ -221,13 +234,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } - private fun createUserCodeRepository() = with(tangemSdk) { - UserCodeRepository( - biometricManager = biometricManager, - secureStorage = secureStorage, - ) - } - companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 7eeb396463..ea26d15848 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -33,6 +33,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers @@ -94,6 +95,7 @@ class TapWalletManager { val card = scanResponse.card val attestationFailed = card.attestation.status == Attestation.Status.Failed + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) @@ -101,7 +103,7 @@ class TapWalletManager { store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse)) + store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatch(WalletAction.Warnings.CheckIfNeeded) @@ -149,7 +151,7 @@ class TapWalletManager { withMainContext { store.dispatch(WalletAction.ResetState(data.card)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(data)) + store.dispatch(GlobalAction.SaveScanResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) store.dispatch(WalletConnectAction.RestoreSessions(data)) diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt index ac3ac6d56a..dd9a0b27a1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt @@ -11,10 +11,6 @@ sealed class TotalFiatBalance { object Loading : TotalFiatBalance() - data class Refreshing( - override val amount: BigDecimal, - ) : TotalFiatBalance() - data class Error( override val amount: BigDecimal, ) : TotalFiatBalance() diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 8c3403ede9..4dec0df7d1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -6,22 +6,23 @@ import com.tangem.domain.common.util.UserWalletId /** * Represents user's wallet which stored in app persistence - * @param name User's wallet name - * @param walletId User's wallet [UserWalletId] + * @param name User wallet name + * @param walletId User wallet [UserWalletId] * @param artworkUrl User wallet card artwork URL * @param cardsInWallet List of cards IDs assigned with this user's wallet + * @param isMultiCurrency Indicates whether this user wallet can work with more than one currency * @param scanResponse [ScanResponse] of primary user's wallet card. * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card * @property hasAccessCode Indicates if the user's wallet primary card has access code * @property isLocked Indicates if this primary card has no currency wallets - * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( val name: String, val walletId: UserWalletId, val artworkUrl: String, val cardsInWallet: Set, + val isMultiCurrency: Boolean, val scanResponse: ScanResponse, ) { val cardId: String @@ -32,17 +33,18 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() - - internal var isSaved: Boolean = true } /** * !!! Workaround !!! * * Calculate same [UserWalletId] for twins instead + * + * TODO: Remove after [REDACTED_JIRA] * */ fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins()) return false + if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false + if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { return true } diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index 439b900c39..4462f4d8ec 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -35,12 +35,6 @@ data class WalletDataModel( open val pendingTransactions: List = emptyList() open val errorMessage: String? = null open val isErrorStatus: Boolean = false - - fun asRefreshing() = Refreshing( - amount = amount, - pendingTransactions = pendingTransactions, - errorMessage = errorMessage, - ) } object Loading : Status() @@ -74,12 +68,4 @@ data class WalletDataModel( object MissedDerivation : Status() { override val isErrorStatus: Boolean = true } - - data class Refreshing( - override val amount: BigDecimal, - override val pendingTransactions: List, - override val errorMessage: String?, - ) : Status() { - override val isErrorStatus: Boolean = errorMessage != null - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt index cba89801a3..a0511784e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -1,6 +1,8 @@ package com.tangem.tap.domain.model +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel.WalletRent import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -8,21 +10,28 @@ import java.math.BigDecimal /** * Contains info about the blockchain and its currencies - * @param userWalletId ID of the [UserWallet] which uses that store - * @param blockchainNetwork Store's [BlockchainNetwork] - * @param walletManager Store's [WalletManager], may be null if it fails to create this manager. TODO: Remove after - * WalletMiddleware refactoring + * @param userWalletId ID of the associated [UserWallet] + * @param blockchain [Blockchain] of this WalletStore + * @param derivationPath [DerivationPath] of this store, null if the card does not support the + * [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/) * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies - * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then + * @param walletRent [WalletRent], null if store has no rent or currency balance is greater then * [WalletRent.exemptionAmount] + * @param blockchainNetwork [BlockchainNetwork]. + * TODO: Remove after WalletMiddleware refactoring + * @param walletManager [WalletManager], may be null if it fails to create this manager. + * TODO: Remove after WalletMiddleware refactoring * */ data class WalletStoreModel( val userWalletId: UserWalletId, + val blockchain: Blockchain, + val derivationPath: DerivationPath?, + val walletsData: List, + val walletRent: WalletRent?, + @Deprecated("Don't use it, will be removed") val blockchainNetwork: BlockchainNetwork, @Deprecated("Don't use it, will be removed") val walletManager: WalletManager?, - val walletsData: List, - val walletRent: WalletRent?, ) { /** @@ -35,4 +44,34 @@ data class WalletStoreModel( val rent: BigDecimal, val exemptionAmount: BigDecimal, ) + + // TODO: Remove the generated methods after blockchainNetwork and walletManager are removed from the model + // region Generated + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WalletStoreModel) return false + + if (userWalletId != other.userWalletId) return false + if (blockchain != other.blockchain) return false + if (derivationPath != other.derivationPath) return false + if (walletsData != other.walletsData) return false + if (walletRent != other.walletRent) return false + + return true + } + + override fun hashCode(): Int { + var result = userWalletId.hashCode() + result = 31 * result + blockchain.hashCode() + result = 31 * result + (derivationPath?.hashCode() ?: 0) + result = 31 * result + walletsData.hashCode() + result = 31 * result + (walletRent?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "WalletStoreModel(userWalletId=$userWalletId, blockchain=$blockchain, derivationPath=$derivationPath, " + + "walletsData=$walletsData, walletRent=$walletRent)" + } + // endregion Generated } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 377b8cf8c6..6e4d834d38 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,15 +1,48 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.common.TwinsHelper import com.tangem.domain.common.util.userWalletId -import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.features.wallet.redux.Artwork class UserWalletBuilder( private val scanResponse: ScanResponse, + private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(), ) { private var backupCardsIds: Set = emptySet() + private val CardDTO.isBackupNotAllowed: Boolean + get() = !this.settings.isBackupAllowed + + private val ScanResponse.userWalletName: String + get() = when (productType) { + ProductType.Note -> "Note" + ProductType.Twins -> "Twin" + ProductType.SaltPay -> "SaltPay" + ProductType.Wallet -> when { + card.isBackupNotAllowed -> "Tangem card" + card.isStart2Coin -> "Start2Coin" + else -> "Wallet" + } + } + + private val ScanResponse.isMultiCurrency: Boolean + get() = when (productType) { + ProductType.Note -> false + ProductType.Twins -> false + ProductType.SaltPay -> false + ProductType.Wallet -> !card.isStart2Coin + } + fun backupCardsIds(backupCardsIds: Set?) = this.apply { if (backupCardsIds != null) { this.backupCardsIds = backupCardsIds @@ -20,11 +53,44 @@ class UserWalletBuilder( return with(scanResponse) { UserWallet( walletId = card.userWalletId, - name = productType.name, - artworkUrl = card.getOrLoadCardArtworkUrl(), + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), cardsInWallet = backupCardsIds.plus(card.cardId), scanResponse = this, + isMultiCurrency = isMultiCurrency, ) } } + + private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String { + return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) { + is Result.Success -> { + val artworkId = result.data.artwork?.id + if (artworkId.isNullOrEmpty()) { + getFallbackArtworkUrl(cardId) + } else { + getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + } + + is Result.Failure -> getFallbackArtworkUrl(cardId) + } + } + + private fun getFallbackArtworkUrl(cardId: String): String { + return when { + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> when (TwinsHelper.getTwinCardNumber(cardId)) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } + } + } + + private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String { + return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK + + "?artworkId=${artworkId}&CID=${cardId}&publicKey=$cardPublicKeyHex" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index f41c664276..e76a291668 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel @@ -54,10 +55,12 @@ private class BlockchainNetworkWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = blockchainNetwork, - walletManager = walletManager, + blockchain = blockchainNetwork.blockchain, + derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) }, walletsData = (listOf(blockchainWalletData) + tokensWalletsData), walletRent = null, + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, ) } } @@ -74,10 +77,12 @@ private class WalletMangerWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), - walletManager = walletManager, + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath, walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)), walletRent = null, + walletManager = walletManager, + blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 6ab45894b9..d940a338ad 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -82,6 +82,7 @@ object ScanCardProcessor { nextHandler = { scanResponse1 -> showDisclaimerIfNeed( scanResponse = scanResponse1, + onProgressStateChange = onProgressStateChange, nextHandler = { scanResponse2 -> onScanSuccess( scanResponse = scanResponse2, @@ -127,6 +128,7 @@ object ScanCardProcessor { private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline nextHandler: suspend (ScanResponse) -> Unit, ) { val disclaimerType = DisclaimerType.get(scanResponse) @@ -137,11 +139,18 @@ object ScanCardProcessor { } else scope.launch(Dispatchers.Main) { delay(DELAY_SDK_DIALOG_CLOSE) store.dispatchOnMain( - DisclaimerAction.Show { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, + DisclaimerAction.Show( + onAcceptCallback = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismissCallback = { + scope.launch(Dispatchers.Main) { + onProgressStateChange(false) + } + }, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index 512e986cbf..0c0f909c80 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -33,9 +33,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { when (walletsData.findStatus()) { TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading - TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing( - amount = prevAmount ?: BigDecimal.ZERO, - ) TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount()) TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount()) } @@ -54,7 +51,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private fun Sequence.mapToStatus(): Sequence { return this.map { walletData -> when (walletData.status) { - is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing is WalletDataModel.VerifiedOnline, is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, @@ -84,18 +80,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { ): TotalFiatBalanceStatus { return when (prevStatus) { TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing -> when (newStatus) { - TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing, - TotalFiatBalanceStatus.Error, - TotalFiatBalanceStatus.Loaded, - -> newStatus - } TotalFiatBalanceStatus.Loaded, TotalFiatBalanceStatus.Error, -> when (newStatus) { TotalFiatBalanceStatus.Loading, - TotalFiatBalanceStatus.Refreshing, TotalFiatBalanceStatus.Error, -> newStatus TotalFiatBalanceStatus.Loaded -> prevStatus @@ -105,7 +93,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private enum class TotalFiatBalanceStatus { Loading, - Refreshing, Error, Loaded, } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index ae9a72152a..28016d704f 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -10,6 +10,9 @@ import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable { + + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 57b66d7a62..628c041112 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -20,6 +20,8 @@ class CreateSecondTwinWalletTask( private val creatingWalletMessage: Message, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 73b9e2f40a..d4e00cb3fe 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -13,6 +13,8 @@ class FinalizeTwinTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 8310047019..b910fcb589 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -20,11 +20,9 @@ class TwinCardsManager( assetReader: AssetReader, ) { private val firstCardId: String = card.cardId - private var secondCardId: String? = null private var currentCardPublicKey: String? = null - var secondCardPublicKey: String? = null - private set + private var secondCardPublicKey: String? = null private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) @@ -57,7 +55,6 @@ class TwinCardsManager( when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() - secondCardId = response.data.cardId } is CompletionResult.Failure -> {} } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 62ea1a37a4..dfd62c05f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -14,24 +14,23 @@ interface UserWalletsListManager { val hasSavedUserWallets: Boolean suspend fun unlockWithBiometry(): CompletionResult - suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult fun lock() - suspend fun selectWallet(walletId: UserWalletId): CompletionResult + suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user's wallet - * @param userWallet User's wallet to save + * Save user wallet + * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card - * @return [CompletionResult] operation result + * @return [CompletionResult] of operation * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult - suspend fun get(walletId: UserWalletId): CompletionResult + suspend fun get(userWalletId: UserWalletId): CompletionResult companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 248719dd20..f7781ca770 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -28,21 +28,19 @@ internal class BiometricUserWalletsListManager( override val userWallets: Flow> get() = state - .mapLatest { it.wallets } + .mapLatest { it.userWallets } .distinctUntilChanged() override val selectedUserWallet: Flow get() = state .mapLatest { state -> - state.wallets.find { - it.walletId == state.selectedWalletId - } + findSelectedUserWallet(state.userWallets) } .filterNotNull() .distinctUntilChanged() override val selectedUserWalletSync: UserWallet? - get() = findSelectedWallet() + get() = findSelectedUserWallet() override val isLocked: Flow get() = state @@ -53,44 +51,13 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = selectedUserWalletRepository.get() != null + get() = publicInformationRepository.isNotEmpty() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() .map { selectedUserWalletSync } } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - state.update { prevState -> - // If the previous state contains a saved user wallet with the same ID, it is also saved - userWallet.isSaved = prevState.wallets.any { - it.walletId == userWallet.walletId && it.isSaved - } - - val newEncryptionKeys = prevState.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - val newUserWallets = prevState.wallets - .plus(userWallet) - .distinctBy { it.walletId } - - prevState.copy( - encryptionKeys = newEncryptionKeys, - wallets = newUserWallets, - selectedWalletId = userWallet.walletId, - ) - } - - return loadModels() - .map { - state.update { prevState -> - prevState.copy( - isLocked = prevState.wallets.any { it.isLocked }, - ) - } - } - } - override fun lock() { state.update { prevState -> prevState.copy( @@ -100,85 +67,62 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult = catching { - if (state.value.selectedWalletId == walletId) { - return@catching findSelectedWallet()!! + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching { + if (state.value.selectedUserWalletId == userWalletId) { + return@catching findSelectedUserWallet()!! } - if (!state.value.isLocked) { - selectedUserWalletRepository.set(walletId) + selectedUserWalletRepository.set(userWalletId) - state.update { prevState -> - prevState.copy( - selectedWalletId = walletId, - ) - } + state.update { prevState -> + prevState.copy( + selectedUserWalletId = userWalletId, + ) } - findSelectedWallet()!! + findSelectedUserWallet()!! } - override suspend fun save( - userWallet: UserWallet, - canOverride: Boolean, - ): CompletionResult = withUnlock { - val isWalletSaved = state.value.wallets - .filter { it.isSaved } - .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) - } - - if (isWalletSaved && !canOverride) { - CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { + return if (canOverride) { + saveInternal(userWallet) } else { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } + val isWalletSaved = state.value.userWallets + .any { + // Workaround, check [UserWallet.isTwinnedWith] + it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + } - keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - ) - } - } - .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } - .flatMap { loadModels() } - .doOnSuccess { - userWallet.isSaved = true - } + if (isWalletSaved) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + saveInternal(userWallet) + } } } - override suspend fun delete(walletIds: List): CompletionResult { - val walletIdsToRemove = state.value.wallets - .map { it.walletId } - .filter { it in walletIds } - val remainingEncryptionKeys = state.value.encryptionKeys - .filter { it.walletId !in walletIdsToRemove } + override suspend fun delete(userWalletIds: List): CompletionResult { + if (userWalletIds.isEmpty()) { + return CompletionResult.Success(Unit) + } - changeSelectedWalletIfNeeded(walletIdsToRemove) + changeSelectedUserWalletIdIfNeeded(userWalletIds) - return sensitiveInformationRepository.delete(walletIdsToRemove) - .flatMap { publicInformationRepository.delete(walletIdsToRemove) } - .flatMap { keysRepository.store(remainingEncryptionKeys) } + return sensitiveInformationRepository.delete(userWalletIds) + .flatMap { publicInformationRepository.delete(userWalletIds) } + .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> prevState.copy( - encryptionKeys = remainingEncryptionKeys, - wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, + encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, + userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, ) } } } override suspend fun clear(): CompletionResult { - return sensitiveInformationRepository.delete( - walletIds = state.value.wallets.map { it.walletId }, - ) + return sensitiveInformationRepository.clear() .flatMap { publicInformationRepository.clear() } .flatMap { keysRepository.clear() } .map { @@ -187,17 +131,37 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { - state.value.wallets.first { it.walletId == walletId } + state.value.userWallets.first { it.walletId == userWalletId } } } - private suspend inline fun withUnlock( - block: () -> CompletionResult, - ): CompletionResult { - return (if (state.value.isLocked) unlockWithBiometryInternal() else CompletionResult.Success(Unit)) - .flatMap { block() } + private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { + val newEncryptionKeys = state.value.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + + return keysRepository.store(newEncryptionKeys) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = newEncryptionKeys, + selectedUserWalletId = userWallet.walletId, + ) + } + } + .flatMap { publicInformationRepository.save(userWallet) } + .flatMap { sensitiveInformationRepository.save(userWallet) } + .map { selectedUserWalletRepository.set(userWallet.walletId) } + .flatMap { loadModels() } + .doOnSuccess { + state.update { prevState -> + prevState.copy( + isLocked = prevState.userWallets.any { it.isLocked }, + ) + } + } } private suspend fun unlockWithBiometryInternal(): CompletionResult { @@ -223,11 +187,11 @@ internal class BiometricUserWalletsListManager( return getSavedUserWallets() .map { userWallets -> if (userWallets.isNotEmpty()) state.update { prevState -> - val wallets = (userWallets + prevState.wallets).distinctBy { it.walletId } + val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } prevState.copy( - wallets = wallets, - selectedWalletId = findOrSetSelectedWallet(prevState.selectedWalletId, wallets), + userWallets = wallets, + selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets), ) } } @@ -247,56 +211,55 @@ internal class BiometricUserWalletsListManager( } } - private fun findOrSetSelectedWallet( + private fun findOrSetSelectedUserWalletId( prevSelectedWalletId: UserWalletId?, userWallets: List, ): UserWalletId? { - return prevSelectedWalletId - ?: (selectedUserWalletRepository.get() - ?: (userWallets.firstOrNull()?.walletId - ?.also { selectedUserWalletRepository.set(it) })) + val findUnlockedAndSet = { + userWallets.firstOrNull { !it.isLocked } + ?.walletId + ?.also { selectedUserWalletRepository.set(it) } + } + + return prevSelectedWalletId ?: (selectedUserWalletRepository.get() ?: findUnlockedAndSet()) } - private fun changeSelectedWalletIfNeeded( - walletsIdsToRemove: List, - ) { - val remainingWallets = state.value.wallets.filter { + private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { + val remainingWallets = state.value.userWallets.filter { it.walletId !in walletsIdsToRemove } - val selectedWallet = findSelectedWallet() + val selectedWallet = findSelectedUserWallet() when { remainingWallets.isEmpty() -> { state.update { prevState -> prevState.copy( - selectedWalletId = null, + selectedUserWalletId = null, ) } selectedUserWalletRepository.set(null) } !remainingWallets.contains(selectedWallet) -> { - val newSelectedWallet = remainingWallets.first() + val newSelectedWallet = remainingWallets.firstOrNull { !it.isLocked } state.update { prevState -> prevState.copy( - selectedWalletId = newSelectedWallet.walletId, + selectedUserWalletId = newSelectedWallet?.walletId, ) } - selectedUserWalletRepository.set(newSelectedWallet.walletId) + selectedUserWalletRepository.set(newSelectedWallet?.walletId) } } } - private fun findSelectedWallet(): UserWallet? { - return with(state.value) { - wallets.find { - it.walletId == selectedWalletId - } + private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { + return userWallets.find { + it.walletId == state.value.selectedUserWalletId } } private data class State( val encryptionKeys: List = emptyList(), - val wallets: List = emptyList(), - val selectedWalletId: UserWalletId? = null, + val userWallets: List = emptyList(), + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = true, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt index a3c3a3e929..e616c48758 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -26,15 +26,11 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(null) } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - return CompletionResult.Success(Unit) - } - override fun lock() { /* no-op */ } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult { + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } @@ -44,7 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun delete(walletIds: List): CompletionResult { + override suspend fun delete(userWalletIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -52,7 +48,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index b2308343ff..d383e11e3f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -17,4 +17,5 @@ internal data class UserWalletPublicInformation( val artworkUrl: String, val cardsInWallet: Set, val scanResponse: ScanResponse, + val isMultiCurrency: Boolean, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index 974879ca31..f10e0beeb5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -1,10 +1,35 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { + /** + * Obtaining the encryption keys of all user wallets from the biometric vault. Biometric authentication required + * If that operation runs more than biometric cipher key expiration time then the user will not receive all + * encryption keys + * @return [CompletionResult] of operation with stored [UserWalletEncryptionKey] list + * */ suspend fun getAll(): CompletionResult> + + /** + * Store the encryption keys for user wallets. Biometric authentication not required + * @param encryptionKeys List of encryption keys for user wallets + * @return [CompletionResult] of operation + * */ suspend fun store(encryptionKeys: List): CompletionResult + + /** + * Delete encryption keys for user wallets. Biometric authentication not required + * @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted + * @return [CompletionResult] of operation + * */ + suspend fun delete(userWalletsIds: List): CompletionResult + + /** + * Clear all encryption keys for user wallets. Biometric authentication not required + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index 8765a6b804..f0387d3f0e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,4 +12,6 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult + + fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index dcc5677814..7e03055150 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -12,5 +12,6 @@ internal interface UserWalletsSensitiveInformationRepository { encryptionKeys: List, ): CompletionResult> - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index cd39605d96..5e57d10474 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -6,62 +6,183 @@ import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.biometric.BiometricManager import com.tangem.common.biometric.BiometricStorage +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( moshi: Moshi, - secureStorage: SecureStorage, biometricManager: BiometricManager, + private val secureStorage: SecureStorage, ) : UserWalletsKeysRepository { private val biometricStorage = BiometricStorage( biometricManager = biometricManager, secureStorage = secureStorage, ) - private val walletsKeysAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, UserWalletEncryptionKey::class.java), + private val encryptionKeyAdapter: JsonAdapter = moshi.adapter( + UserWalletEncryptionKey::class.java, + ) + private val userWalletsIdsListAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletId::class.java), ) override suspend fun getAll(): CompletionResult> { - return biometricStorage.get(key = StorageKey.WalletEncryptionKeys.name) - .map { encryptionKeys -> - encryptionKeys.decodeToKeys() - } - .mapFailure { error -> - UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) - } + return withContext(Dispatchers.IO) { + getAllInternal() + .mapFailure { error -> + UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) + } + } } override suspend fun store(encryptionKeys: List): CompletionResult { - return biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encryptionKeys.encode(), - ) - .mapFailure { error -> - UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + return withContext(Dispatchers.IO) { + encryptionKeys.map { storeEncryptionKey(it) } + .fold() + .mapFailure { error -> + UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + } + } + } + + override suspend fun delete(userWalletsIds: List): CompletionResult { + return withContext(Dispatchers.IO) { + userWalletsIds.map { userWalletId -> + deleteEncryptionKey(userWalletId) } + .fold() + .map { deleteUserWalletsIds(userWalletsIds) } + } } override suspend fun clear(): CompletionResult { - return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) + return withContext(Dispatchers.IO) { + getUserWalletsIds() + .map { userWalletId -> + deleteEncryptionKey(userWalletId) + } + .fold() + .map { + clearUserWalletsIds() + } + } } - private fun List.encode(): ByteArray { - return this.let(walletsKeysAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) + private suspend fun getAllInternal(): CompletionResult> { + return getUserWalletsIds() + .map { userWalletId -> + // This is possible because the Card SDK cipher key has an expiration time + // If this operation runs more than that expiration time, the user will not receive all encryption keys + getEncryptionKey(userWalletId) + .doOnFailure { error -> + // If the user cancels biometric authentication, cancel the request for all keys + if (error is TangemSdkError.BiometricsAuthenticationFailed) { + return CompletionResult.Failure(error) + } + } + } + .fold(listOf()) { acc, data -> + if (data != null) acc + data else acc + } } - private fun ByteArray?.decodeToKeys(): List { - return this?.decodeToString(throwOnInvalidSequence = true) - ?.let(walletsKeysAdapter::fromJson) - .orEmpty() + private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.get(StorageKey.WalletEncryptionKey(userWalletId).name) + .map { it.decodeToKey() } } - private enum class StorageKey { - WalletEncryptionKeys + private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult { + return biometricStorage.store( + key = StorageKey.WalletEncryptionKey(encryptionKey.walletId).name, + data = encryptionKey.encode(), + ) + .map { storeUserWalletId(encryptionKey.walletId) } + } + + private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.delete(StorageKey.WalletEncryptionKey(userWalletId).name) + } + + private suspend fun getUserWalletsIds(): List { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletIds.name) + .decodeToUserWalletsIds() + } + } + + private suspend fun storeUserWalletId(userWalletId: UserWalletId) { + val userWalletIds = (getUserWalletsIds() + userWalletId).distinct() + + withContext(Dispatchers.IO) { + secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun deleteUserWalletsIds(userWalletsIds: List) { + val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet()) + + withContext(Dispatchers.IO) { + secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun clearUserWalletsIds() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletIds.name) + } + } + + private suspend fun UserWalletEncryptionKey.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptionKeyAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? { + return withContext(Dispatchers.Default) { + this@decodeToKey + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptionKeyAdapter::fromJson) + } + } + + private suspend fun List.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(userWalletsIdsListAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToUserWalletsIds(): List { + return withContext(Dispatchers.Default) { + this@decodeToUserWalletsIds + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(userWalletsIdsListAdapter::fromJson) + .orEmpty() + } + } + + private sealed interface StorageKey { + val name: String + + class WalletEncryptionKey(userWalletId: UserWalletId) : StorageKey { + override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}" + } + + object UserWalletIds : StorageKey { + override val name: String = "user_wallets_ids_with_saved_keys" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46d0fc073b..46a016f8c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -25,19 +25,21 @@ internal class DefaultUserWalletsPublicInformationRepository( ) override suspend fun save(userWallet: UserWallet): CompletionResult { - return getAll() - .flatMap { savedInformation -> - val infoToSave = withContext(Dispatchers.Default) { - savedInformation.toMutableList() - .apply { - replaceByOrAdd(userWallet.publicInformation) { - userWallet.walletId == it.walletId + return withContext(Dispatchers.IO) { + getAll() + .flatMap { savedInformation -> + val infoToSave = withContext(Dispatchers.Default) { + savedInformation.toMutableList() + .apply { + replaceByOrAdd(userWallet.publicInformation) { + userWallet.walletId == it.walletId + } } - } - } + } - save(infoToSave) - } + save(infoToSave) + } + } } override suspend fun getAll(): CompletionResult> = catching { @@ -50,20 +52,28 @@ internal class DefaultUserWalletsPublicInformationRepository( } override suspend fun delete(walletIds: List): CompletionResult { - return getAll() - .flatMap { publicInformation -> - val infoToRemove = publicInformation - .filter { it.walletId in walletIds } - .toSet() + return withContext(Dispatchers.IO) { + getAll() + .flatMap { publicInformation -> + val infoToRemove = publicInformation + .filter { it.walletId in walletIds } + .toSet() - save( - publicInformation = publicInformation - infoToRemove, - ) - } + save( + publicInformation = publicInformation - infoToRemove, + ) + } + } } override suspend fun clear(): CompletionResult = catching { - secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + } + } + + override fun isNotEmpty(): Boolean { + return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true } @JvmName("saveWithPublicInformation") diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index c338d17c67..5918312b6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -3,12 +3,14 @@ package com.tangem.tap.domain.userWalletList.repository.implementation import android.security.keystore.KeyProperties import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi +import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -28,29 +30,23 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( UserWalletSensitiveInformation::class.java, ) + private val encryptedSensitiveInformationMapAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), + ) + private val cipher: Cipher by lazy { Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } override suspend fun save(userWallet: UserWallet): CompletionResult { return catching { - withContext(Dispatchers.Default) { - userWallet.sensitiveInformation - .let(sensitiveInformationAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) - .encryptAndStoreIv( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .let { encryptedInformation -> - withContext(Dispatchers.IO) { - secureStorage.store( - data = encryptedInformation, - account = StorageKey.SensitiveInformation(userWallet.walletId).name, - ) - } - } - } + val encryptedSensitiveInformation = userWallet.sensitiveInformation + .encode() + .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + + getAllEncrypted().toMutableMap() + .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } + .let { saveInternal(it) } } .mapFailure { error -> UserWalletListError.SaveSensitiveInformationError(error.cause ?: error) @@ -60,77 +56,130 @@ internal class DefaultUserWalletsSensitiveInformationRepository( override suspend fun getAll( encryptionKeys: List, ): CompletionResult> { + if (encryptionKeys.isEmpty()) { + return CompletionResult.Success(emptyMap()) + } + return catching { - if (encryptionKeys.isEmpty()) { - return@catching emptyMap() - } + val encryptedSensitiveInformation = getAllEncrypted() - val keyToEncryptedInformation = withContext(Dispatchers.IO) { - encryptionKeys.associateWith { encryptionKey -> - secureStorage.get(StorageKey.SensitiveInformation(encryptionKey.walletId).name) + encryptionKeys + .associateBy { it.walletId } + .mapValues { (userWalletId, encryptionKey) -> + encryptedSensitiveInformation[userWalletId.stringValue] + ?.getIvAndDecrypt(userWalletId.stringValue, encryptionKey.encryptionKey) + .decodeToSensitiveInformation() } - } - - withContext(Dispatchers.Default) { - val keyToInformation = - mutableMapOf() - - keyToEncryptedInformation.forEach { (key, encryptedInformation) -> - val information = encryptedInformation - ?.getIvAndDecrypt( - walletId = key.walletId, - encryptionKey = key.encryptionKey, - ) - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(sensitiveInformationAdapter::fromJson) - - if (information != null) { - keyToInformation[key.walletId] = information - } - } - - keyToInformation - } + .filterNotNull() } .mapFailure { error -> UserWalletListError.ReceiveSensitiveInformationError(error.cause ?: error) } } - override suspend fun delete(walletIds: List): CompletionResult = catching { - walletIds - .forEach { walletId -> - secureStorage.delete(StorageKey.SensitiveInformation(walletId).name) - } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return catching { deleteInternal(userWalletsIds) } } - private fun ByteArray.encryptAndStoreIv(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray { - val secretKey = SecretKeySpec(encryptionKey, algorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - val encryptedData = cipher.doFinal(this) - secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(walletId).name) - return encryptedData + override suspend fun clear(): CompletionResult { + return catching { clearInternal() } } - private fun ByteArray.getIvAndDecrypt(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray? { - val iv = secureStorage.get(StorageKey.SensitiveInformationIv(walletId).name) - ?: error("IV not found") - val ivParam = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) - return cipher - .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } - .doFinal(this) + private suspend fun getAllEncrypted(): Map { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletsSensitiveInformation.name) + .decodeToEncryptedSensitiveInformation() + } + } + + private suspend fun saveInternal(sensitiveInformation: Map) { + return withContext(Dispatchers.IO) { + secureStorage.store( + account = StorageKey.UserWalletsSensitiveInformation.name, + data = sensitiveInformation.encode(), + ) + } + } + + private suspend fun deleteInternal(userWalletsIds: List) { + return saveInternal( + sensitiveInformation = getAllEncrypted() - userWalletsIds.map { it.stringValue }.toSet(), + ) + } + + private suspend fun clearInternal() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletsSensitiveInformation.name) + } + } + + private suspend fun ByteArray?.decodeToEncryptedSensitiveInformation(): Map { + return withContext(Dispatchers.Default) { + this@decodeToEncryptedSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptedSensitiveInformationMapAdapter::fromJson) + .orEmpty() + } + } + + private suspend fun ByteArray?.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { + return withContext(Dispatchers.Default) { + this@decodeToSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(sensitiveInformationAdapter::fromJson) + } + } + + private suspend fun UserWalletSensitiveInformation.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(sensitiveInformationAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun Map.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptedSensitiveInformationMapAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray.encryptAndStoreIv(userWalletId: String, encryptionKey: ByteArray): ByteArray { + return withContext(Dispatchers.Default) { + val secretKey = SecretKeySpec(encryptionKey, algorithm) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val encryptedData = cipher.doFinal(this@encryptAndStoreIv) + secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(userWalletId).name) + encryptedData + } + } + + private suspend fun ByteArray.getIvAndDecrypt( + userWalletId: String, + encryptionKey: ByteArray, + ): ByteArray? { + return withContext(Dispatchers.Default) { + val iv = secureStorage.get(StorageKey.SensitiveInformationIv(userWalletId).name) + ?: error("IV not found") + val ivParam = IvParameterSpec(iv) + val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) + cipher + .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } + .doFinal(this@getIvAndDecrypt) + } } private sealed interface StorageKey { val name: String - class SensitiveInformation(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_${walletId.stringValue}" + object UserWalletsSensitiveInformation : StorageKey { + override val name: String = "user_wallets_sensitive_information" } - class SensitiveInformationIv(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_iv_${walletId.stringValue}" + class SensitiveInformationIv(userWalletId: String) : StorageKey { + override val name: String = "user_wallet_sensitive_information_iv_${userWalletId}" } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 810ba463f6..7d8170895c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -14,6 +14,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation walletId = walletId, artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, + isMultiCurrency = isMultiCurrency, scanResponse = scanResponse.copy( card = scanResponse.card.copy( wallets = emptyList(), @@ -28,6 +29,7 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, scanResponse = scanResponse, + isMultiCurrency = isMultiCurrency, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt index f1115ee3ef..fb77a29584 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -2,13 +2,12 @@ package com.tangem.tap.domain.walletCurrencies import com.tangem.common.CompletionResult import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency interface WalletCurrenciesManager { suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, + currency: Currency, ): CompletionResult suspend fun addCurrencies( diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 837ff8452c..5fd2e25234 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -1,25 +1,25 @@ package com.tangem.tap.domain.walletCurrencies.implementation +import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult -import com.tangem.common.catching import com.tangem.common.flatMap +import com.tangem.common.fold +import com.tangem.common.map import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.getTokens -import com.tangem.tap.features.wallet.models.toCurrencies +import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -31,18 +31,18 @@ internal class DefaultWalletCurrenciesManager( ) : WalletCurrenciesManager { override suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, - ): CompletionResult { - val walletStore = walletStoresRepository.get(userWallet.walletId).first() + currency: Currency, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletStore = walletStoresRepository.getSync(userWallet.walletId) .find { - it.blockchainNetwork.blockchain == blockchainNetwork.blockchain - && it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath + it.blockchain == currency.blockchain + && it.derivationPath?.rawPath == currency.derivationPath } - return if (walletStore != null) { - walletAmountsRepository.update( - userWallet = userWallet, + if (walletStore != null) { + walletAmountsRepository.updateAmountsForWalletStore( walletStore = walletStore, + userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) } else CompletionResult.Success(Unit) @@ -52,25 +52,42 @@ internal class DefaultWalletCurrenciesManager( userWallet: UserWallet, currenciesToAdd: List, ): CompletionResult = withContext(Dispatchers.Default) { - var newBlockchainNetworks = listOf() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - newBlockchainNetworks = (savedCurrencies + currenciesToAdd) - .toBlockchainNetworks(userWallet.scanResponse.card) - val newCurrencies = newBlockchainNetworks.toCurrencies() + val card = userWallet.scanResponse.card + val updatedBlockchainNetworks = currenciesToAdd + .addMissingBlockchains(card) + .toBlockchainNetworks() + val newCurrencies = (getSavedCurrencies(userWallet.walletId) + currenciesToAdd) + .addMissingBlockchains(card) - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = newCurrencies, + updateWalletStores(userWallet, updatedBlockchainNetworks) + .map { + saveUserCurrencies(card, newCurrencies) + } + .flatMap { + walletAmountsRepository.updateAmountsForUserWallet( + userWallet = userWallet, + fiatCurrency = appCurrencyProvider(), ) } - } + } + + override suspend fun removeCurrencies( + userWallet: UserWallet, + currenciesToRemove: List, + ): CompletionResult = withContext(Dispatchers.Default) { + val card = userWallet.scanResponse.card + val remainingCurrencies = getSavedCurrencies(userWallet.walletId) + .filter { it !in currenciesToRemove } + val remainingBlockchains = remainingCurrencies + .filterIsInstance() + .map { it.blockchain } + + walletStoresRepository.deleteDifference(userWallet.walletId, remainingBlockchains) .flatMap { - newBlockchainNetworks.updateWalletStores(userWallet) + updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks()) + } + .map { + saveUserCurrencies(card, remainingCurrencies) } } @@ -81,108 +98,69 @@ internal class DefaultWalletCurrenciesManager( return removeCurrencies(userWallet, listOf(currencyToRemove)) } - override suspend fun removeCurrencies( - userWallet: UserWallet, - currenciesToRemove: List, - ): CompletionResult = withContext(Dispatchers.Default) { - var remainingBlockchainsNetworks = emptyList() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - - val remainingCurrencies = arrayListOf() - savedCurrencies.forEach { savedCurrency -> - if (savedCurrency !in currenciesToRemove) { - remainingCurrencies.add(savedCurrency) + private suspend fun getSavedCurrencies(userWalletId: UserWalletId): List { + return withContext(Dispatchers.Default) { + walletStoresRepository.getSync(userWalletId) + .flatMap { walletStore -> + walletStore.walletsData.map { it.currency } } - } - - remainingBlockchainsNetworks = remainingCurrencies.toBlockchainNetworks(userWallet.scanResponse.card) - - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = remainingCurrencies, - ) - } } - .flatMap { - remainingBlockchainsNetworks.updateWalletStores(userWallet) - } } - private fun List.toBlockchainNetworks(card: CardDTO): List { - val blockchainNetworks = arrayListOf() - val findDerivationPath: (currency: Currency) -> String? = { currency -> - currency.derivationPath - ?: currency.blockchain.derivationPath(card.derivationStyle) - ?.rawPath + private suspend fun saveUserCurrencies(card: CardDTO, currencies: List) { + withContext(Dispatchers.IO) { + userTokensRepository.saveUserTokens( + card = card, + tokens = currencies, + ) } + } - for (currency in this.sortedByDescending { it.isBlockchain() }) { + private fun List.addMissingBlockchains(card: CardDTO): List { + val newCurrencies = arrayListOf() + + for (currency in this.sortedByDescending { it is Currency.Blockchain }) { when (currency) { is Currency.Blockchain -> { - val blockchainNetwork = BlockchainNetwork( - blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = getTokens(currency), - ) - - blockchainNetworks.add(blockchainNetwork) + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) } - is Currency.Token -> { - val tokenBlockchainNetworkIndex = blockchainNetworks - .indexOfFirst { - it.blockchain == currency.blockchain && - it.derivationPath == currency.derivationPath - } - if (tokenBlockchainNetworkIndex == -1) { - blockchainNetworks.add( - BlockchainNetwork( + is Currency.Token -> { + val containsTokenBlockchain = newCurrencies.any { + it.isBlockchain() && it.blockchain == currency.blockchain + } + + if (containsTokenBlockchain) { + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) + } else { + val derivationPath = findDerivationPath(currency, card.derivationStyle) + newCurrencies.add( + Currency.Blockchain( blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = listOf(currency.token), + derivationPath = derivationPath, ), ) - } else { - val tokenBlockchainNetwork = blockchainNetworks[tokenBlockchainNetworkIndex] - if (currency.token in tokenBlockchainNetwork.tokens) { - continue - } else { - blockchainNetworks.add( - tokenBlockchainNetworkIndex, - tokenBlockchainNetwork.copy( - tokens = tokenBlockchainNetwork.tokens + currency.token, - ), - ) - } + newCurrencies.add( + currency.copy(derivationPath = derivationPath), + ) } } } } - return blockchainNetworks + return newCurrencies } - private suspend fun List.updateWalletStores( + private suspend fun updateWalletStores( userWallet: UserWallet, + blockchainNetworks: List, ): CompletionResult { val userWalletId = userWallet.walletId - return this - .also { blockchainNetworks -> - walletStoresRepository.deleteDifference( - userWalletId = userWalletId, - currentBlockchains = blockchainNetworks.map { it.blockchain }, - ) - } + return blockchainNetworks .map { blockchainNetwork -> - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = true, ) .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( @@ -194,11 +172,25 @@ internal class DefaultWalletCurrenciesManager( } } .fold() - .flatMap { - walletAmountsRepository.update( - userWallet = userWallet, - fiatCurrency = appCurrencyProvider(), - ) - } + } + + private fun Currency.updateDerivationPath(cardDerivationStyle: DerivationStyle?): Currency { + val findDerivationPath: () -> String? = { + findDerivationPath(this, cardDerivationStyle) + } + + return when (this) { + is Currency.Blockchain -> this.copy( + derivationPath = findDerivationPath(), + ) + + is Currency.Token -> this.copy( + derivationPath = findDerivationPath(), + ) + } + } + + private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? { + return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index 346a6108e8..e834cf0541 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -10,7 +10,7 @@ interface WalletStoresManager { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> - suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult suspend fun clear(): CompletionResult suspend fun fetch( @@ -23,5 +23,9 @@ interface WalletStoresManager { refresh: Boolean = false, ): CompletionResult + suspend fun updateAmounts( + userWallets: List, + ): CompletionResult + companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 041ea91557..219d7f4af1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -2,19 +2,19 @@ package com.tangem.tap.domain.walletStores.implementation import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository @@ -22,6 +22,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext @@ -40,12 +41,12 @@ internal class DefaultWalletStoresManager( override fun get(userWalletId: UserWalletId): Flow> { return walletStoresRepository.get(userWalletId) + .distinctUntilChanged() } - override suspend fun delete(userWalletsIds: List): CompletionResult { - val walletIds = userWalletsIds.map { UserWalletId(it) } - return walletStoresRepository.delete(walletIds) - .flatMap { walletManagersRepository.delete(walletIds) } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return walletStoresRepository.delete(userWalletsIds) + .flatMap { walletManagersRepository.delete(userWalletsIds) } } override suspend fun clear(): CompletionResult { @@ -55,7 +56,7 @@ internal class DefaultWalletStoresManager( override suspend fun fetch( userWallets: List, refresh: Boolean, - ): CompletionResult { + ): CompletionResult = withContext(Dispatchers.Default) { val fiatCurrency = appCurrencyProvider.invoke() val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency @@ -65,18 +66,18 @@ internal class DefaultWalletStoresManager( ) } - return userWallets + userWallets .mapNotNull { userWallet -> val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId) if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) { - fetchWalletsIfNeeded(userWallet, refresh) + fetchWalletsIfNeeded(userWallet) } else null } - .fold(initial = arrayListOf()) { acc, data -> + .fold(arrayListOf()) { acc, data -> acc.apply { add(data) } } .flatMap { - walletAmountsRepository.update(it, fiatCurrency) + walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency) } } @@ -87,22 +88,29 @@ internal class DefaultWalletStoresManager( return fetch(listOf(userWallet), refresh) } - private suspend fun fetchWalletsIfNeeded( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return if (userWallet.scanResponse.card.isMultiwalletAllowed) { - fetchMultiWallets(userWallet, refresh) + override suspend fun updateAmounts(userWallets: List): CompletionResult { + val fiatCurrency = appCurrencyProvider.invoke() + + return walletAmountsRepository.updateAmountsForUserWallets(userWallets, fiatCurrency) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + fiatCurrency = fiatCurrency, + ) + } + } + } + + private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult { + return if (userWallet.isMultiCurrency) { + fetchMultiWallets(userWallet) } else { - fetchSingleWallet(userWallet, refresh) + fetchSingleWallet(userWallet) } .map { userWallet } } - private suspend fun fetchMultiWallets( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { + private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult { val scanResponse = userWallet.scanResponse val userTokens = withContext(Dispatchers.IO) { userTokensRepository.getUserTokens(scanResponse.card) @@ -128,10 +136,9 @@ internal class DefaultWalletStoresManager( ) } - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = refresh, ) .flatMap { walletManager -> storeWalletStore(walletManager) @@ -149,13 +156,9 @@ internal class DefaultWalletStoresManager( } } - private suspend fun fetchSingleWallet( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return walletManagersRepository.findOrMake( + private suspend fun fetchSingleWallet(userWallet: UserWallet): CompletionResult { + return walletManagersRepository.findOrMakeSingleCurrencyWalletManager( userWallet = userWallet, - refresh = refresh, ) .flatMap { walletManager -> val userWalletId = userWallet.walletId diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt index 93c5c780c1..b1729a0b11 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -17,7 +17,7 @@ internal class DummyWalletStoresManager : WalletStoresManager { return emptyFlow() } - override suspend fun delete(userWalletsIds: List): CompletionResult { + override suspend fun delete(userWalletsIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -32,4 +32,8 @@ internal class DummyWalletStoresManager : WalletStoresManager { override suspend fun fetch(userWallets: List, refresh: Boolean): CompletionResult { return CompletionResult.Success(Unit) } + + override suspend fun updateAmounts(userWallets: List): CompletionResult { + return CompletionResult.Success(Unit) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt deleted file mode 100644 index 3c94784ec8..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.walletStores.implementation.utils - -import com.tangem.common.CompletionResult - -internal fun List>.fold(): CompletionResult { - return fold(Unit) { _, _ -> Unit } -} - -@Suppress("UNCHECKED_CAST") -internal inline fun List>.fold( - initial: R, - operation: (acc: R, data: D) -> R, -): CompletionResult { - var resultData = initial - for (result in this) { - when (result) { - is CompletionResult.Success -> { - resultData = operation(resultData, result.data) - } - is CompletionResult.Failure -> { - return result as CompletionResult.Failure - } - } - } - - return CompletionResult.Success(resultData) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index f46a552dfe..0af5b8f85e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -6,19 +6,39 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel interface WalletAmountsRepository { - suspend fun update( + + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallets list of [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( - userWallet: UserWallet, + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param walletStore [WalletStoreModel] to update + * @param userWallet [UserWallet] associated with provided [walletStore] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt index 9c8f9b1c13..0d8a2044be 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt @@ -8,12 +8,13 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.tokens.models.BlockchainNetwork interface WalletManagersRepository { - suspend fun findOrMake( + suspend fun findOrMakeMultiCurrencyWalletManager( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork? = null, - refresh: Boolean = false, + blockchainNetwork: BlockchainNetwork, ): CompletionResult + suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun delete( diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt index a5184fd40f..7b07a6eee0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface WalletStoresRepository { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List suspend fun contains(userWalletId: UserWalletId): Boolean diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index fd8a037dba..640c08fe67 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -9,9 +9,10 @@ import com.tangem.blockchain.extensions.Result.Failure import com.tangem.blockchain.extensions.Result.Success import com.tangem.common.CompletionResult import com.tangem.common.catching -import com.tangem.common.doOnSuccess +import com.tangem.common.core.TangemError import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.TangemTechService @@ -22,9 +23,9 @@ import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.walletStores.WalletStoresError -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates @@ -40,7 +41,7 @@ import com.tangem.tap.network.NetworkConnectivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -51,30 +52,30 @@ internal class DefaultWalletAmountsRepository( private val walletStoresStorage = WalletStoresStorage private val walletManagersStorage = WalletManagerStorage - override suspend fun update( + override suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult { return if (userWallets.isEmpty()) CompletionResult.Success(Unit) else withContext(Dispatchers.Default) { awaitAll( - async { fetchAmounts(userWallets) }, + async { fetchAmountsForUserWallets(userWallets) }, async { fetchFiatRates(userWallets, fiatCurrency) }, ) .fold() } } - override suspend fun update( + override suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult { - return update(listOf(userWallet), fiatCurrency) + return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } - override suspend fun update( - userWallet: UserWallet, + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult = withContext(Dispatchers.Default) { val walletId = userWallet.walletId @@ -84,33 +85,18 @@ internal class DefaultWalletAmountsRepository( async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) }, async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, ) .fold() } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallets( userWallets: List, - ): CompletionResult = coroutineScope { - userWallets.map { userWallet -> - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getSync(walletId) - - walletStores.map { walletStore -> - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } - } - } - .awaitAll() - .fold() - } + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() .fold() } @@ -120,7 +106,11 @@ internal class DefaultWalletAmountsRepository( ): CompletionResult { val walletsIds = userWallets.map { it.walletId } val walletStores = walletsIds - .flatMap { walletStoresStorage.getSync(it) } + .flatMap { + walletStoresStorage.getAll() + .first() + .getOrElse(it) { emptyList() } + } val currencies = walletStores .asSequence() @@ -138,21 +128,11 @@ internal class DefaultWalletAmountsRepository( return when (fiatRatesResult) { is Result.Success -> { - Timber.d( - """ - Fetched fiat rates - |- User wallets ids: $walletsIds - |- Coins ids: $coinsIds - """.trimIndent(), + updateWalletStoresWithFiatRates( + walletStores = walletStores, + fiatRates = fiatRatesResult.data.rates, ) - walletStores.forEach { walletStore -> - updateWithFiatRates( - walletStore = walletStore, - fiatRates = fiatRatesResult.data.rates, - ) - } - CompletionResult.Success(Unit) } is Result.Failure -> { @@ -175,102 +155,66 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallet( + userWallet: UserWallet, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + val walletStores = walletStoresStorage.getAll() + .first() + .getOrElse(walletId) { emptyList() } + + walletStores.map { walletStore -> + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + } + } + .awaitAll() + .fold() + } + + private suspend fun fetchAmountsForWalletStore( walletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, ): CompletionResult { - val hasMissedDerivations = with(walletStore.blockchainNetwork) { - derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath) + val hasMissedDerivations = with(walletStore) { + derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath) } - val blockchain = walletStore.blockchainNetwork.blockchain - val tokens = walletStore.blockchainNetwork.tokens.map { it.name } return when { - hasMissedDerivations -> { - Timber.e( - """ - Missed derivation - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithMissedDerivation( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } - walletManager == null -> { - Timber.e( - """ - Wallet manager is null - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithUnreachable( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } + hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) + walletManager == null -> updateWalletStoreWithUnreachable(walletStore) else -> { withInternetConnection { walletManager.update() } .map { updateWalletManagerWithAmounts(walletId, walletManager) } - .doOnSuccess { - Timber.d( - """ - Fetched amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), - ) - - updateWithAmounts( + .flatMap { + updateWalletStoreWithAmounts( walletStore = walletStore, - wallet = walletManager.wallet, + updatedWallet = walletManager.wallet, ) } + .flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) } .flatMapOnFailure { error -> - Timber.e( - error, - """ - Unable to fetch amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), + updateWalletStoreWithError( + walletStore = walletStore, + wallet = walletManager.wallet, + error = error, ) - - if (error is BlockchainSdkError) { - updateWithError( - walletStore = walletStore, - wallet = walletManager.wallet, - error = error, - ) - CompletionResult.Success(Unit) - } else { - CompletionResult.Failure(error) - } } } } } - private suspend fun fetchRentIfNeeded( + private suspend fun fetchWalletStoreRentIfNeeded( walletStore: WalletStoreModel, - walletManager: WalletManager?, + walletManager: WalletManager, ): CompletionResult { val rentProvider = walletManager as? RentProvider - - if (walletManager == null || rentProvider == null) { - return CompletionResult.Success(Unit) - } + ?: return CompletionResult.Success(Unit) when (val result = rentProvider.minimalBalanceForRentExemption()) { is Success -> { @@ -288,7 +232,7 @@ internal class DefaultWalletAmountsRepository( balance < rest } - updateWithRent( + updateWalletStoreWithRent( walletStore = walletStore, rent = if (setRent) { WalletStoreModel.WalletRent( @@ -333,76 +277,123 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithError( + private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, - error: BlockchainSdkError, + error: TangemError, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithError( - wallet = wallet, - error = error, - ) - }, - ) + Timber.e( + error, + """ + Unable to fetch amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + + if (error is BlockchainSdkError) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithError( + wallet = wallet, + error = error, + ) + }, + ) + } + + CompletionResult.Success(Unit) + } else { + CompletionResult.Failure(error) } } - private suspend fun updateWithAmounts( + private suspend fun updateWalletStoreWithAmounts( walletStore: WalletStoreModel, - wallet: Wallet, + updatedWallet: Wallet, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { - it.updateWithAmounts(wallet = wallet) + it.updateWithAmounts(wallet = updatedWallet) }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithMissedDerivation( + private suspend fun updateWalletStoreWithMissedDerivation( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Missed derivation + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithMissedDerivation() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithUnreachable( + private suspend fun updateWalletStoreWithUnreachable( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Wallet manager is null + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithUnreachable() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithFiatRates( - walletStore: WalletStoreModel, + private suspend fun updateWalletStoresWithFiatRates( + walletStores: List, fiatRates: Map, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched fiat rates + |- User wallets ids: ${walletStores.map { it.userWalletId }.distinct()} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + prevState.replaceWalletStores( + walletStoresToUpdate = walletStores, update = { it.updateWithFiatRates(rates = fiatRates) }, @@ -410,18 +401,28 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithRent( + private suspend fun updateWalletStoreWithRent( walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithRent(rent) - }, - ) + Timber.d( + """ + Fetched wallet rent + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + |- Rent: $rent + """.trimIndent(), + ) + + if (rent != walletStore.walletRent) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithRent(rent) + }, + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 600120ad42..3de7a3f6b1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -1,6 +1,10 @@ package com.tangem.tap.domain.walletStores.repository.implementation -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.hdWallet.DerivationPath @@ -18,6 +22,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber @@ -26,26 +31,31 @@ internal class DefaultWalletManagersRepository( ) : WalletManagersRepository { private val walletManagersStorage = WalletManagerStorage - override suspend fun findOrMake( + override suspend fun findOrMakeMultiCurrencyWalletManager( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork) + } + + override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork = null) + } + + private suspend fun findOrMakeInternal( userWallet: UserWallet, blockchainNetwork: BlockchainNetwork?, - refresh: Boolean, ): CompletionResult = withContext(Dispatchers.Default) { - if (refresh) { - deleteInternal(userWallet.walletId, blockchainNetwork?.blockchain) - makeAndStore(userWallet, blockchainNetwork) - } else { - val foundWalletManager = findWalletManager( - userWalletId = userWallet.walletId, - blockchain = blockchainNetwork?.blockchain, - ) + val foundWalletManager = findWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchainNetwork?.blockchain, + ) - foundWalletManager?.updateTokens( - scanResponse = userWallet.scanResponse, - blockchainNetwork = blockchainNetwork, - ) - ?: makeAndStore(userWallet, blockchainNetwork) - } + foundWalletManager?.updateTokens( + scanResponse = userWallet.scanResponse, + blockchainNetwork = blockchainNetwork, + ) + ?: makeAndStore(userWallet, blockchainNetwork) } private suspend fun makeAndStore( @@ -150,8 +160,11 @@ internal class DefaultWalletManagersRepository( return catching { val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.getPrimaryToken()) - if (tokens.isNotEmpty()) { - walletManager.addTokens(tokens) + if (tokens != cardTokens) { + cardTokens.clear() + if (tokens.isNotEmpty()) { + walletManager.cardTokens.addAll(tokens) + } } walletManager @@ -170,7 +183,7 @@ internal class DefaultWalletManagersRepository( userWalletId: UserWalletId, blockchain: Blockchain?, ): WalletManager? { - return walletManagersStorage.getAllSync()[userWalletId]?.let { userWalletManagers -> + return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers -> if (blockchain == null) userWalletManagers.firstOrNull() else userWalletManagers.find { it.wallet.blockchain == blockchain } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt index f12cd945cf..b9087070e4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt @@ -12,6 +12,8 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext internal class DefaultWalletStoresRepository : WalletStoresRepository { @@ -22,11 +24,15 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { } override fun get(userWalletId: UserWalletId): Flow> { - return walletStoresStorage.get(userWalletId) + return getAll().map { it[userWalletId].orEmpty() } + } + + override suspend fun getSync(userWalletId: UserWalletId): List { + return get(userWalletId).firstOrNull() ?: emptyList() } override suspend fun contains(userWalletId: UserWalletId): Boolean { - return walletStoresStorage.getSync(userWalletId).isNotEmpty() + return getSync(userWalletId).isNotEmpty() } override suspend fun delete(userWalletsIds: List): CompletionResult = catching { @@ -39,11 +45,13 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, currentBlockchains: List, ): CompletionResult = catching { - walletStoresStorage.update { prevStores -> - prevStores.apply { - this[userWalletId] = this[userWalletId] - ?.filter { it.blockchainNetwork.blockchain in currentBlockchains } - .orEmpty() + if (currentBlockchains != getSync(userWalletId).map { it.blockchain }) { + walletStoresStorage.update { prevStores -> + prevStores.apply { + this[userWalletId] = this[userWalletId] + ?.filter { it.blockchain in currentBlockchains } + .orEmpty() + } } } } @@ -65,24 +73,22 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, walletStore: WalletStoreModel, ): HashMap> = withContext(Dispatchers.Default) { - val prevStores = this@addOrUpdate - val walletStores = prevStores[userWalletId] + val currentWalletStores = this@addOrUpdate + val userWalletStores = currentWalletStores[userWalletId] - if (walletStores.isNullOrEmpty()) { - prevStores.apply { + if (userWalletStores.isNullOrEmpty()) { + currentWalletStores.apply { set(userWalletId, listOf(walletStore)) } } else { - val oldWalletStore = walletStores.find(walletStore::isSameWalletStore) - - if (oldWalletStore == null) { - prevStores.apply { - set(userWalletId, walletStores + walletStore) + val currentWalletStore = userWalletStores.find(walletStore::isSameWalletStore) + if (currentWalletStore == null) { + currentWalletStores.apply { + set(userWalletId, userWalletStores + walletStore) } } else { - prevStores.replaceWalletStore( - walletId = userWalletId, - walletStore = oldWalletStore, + currentWalletStores.replaceWalletStore( + walletStoreToUpdate = currentWalletStore, update = { it.updateWithSelf(walletStore) }, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index 39d4adfadc..7ad0e38363 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -125,10 +125,9 @@ internal fun WalletDataModel.updateWithSelf( status = when (val newStatus = newWalletData.status) { is WalletDataModel.Loading -> when (oldStatus) { is WalletDataModel.MissedDerivation -> WalletDataModel.Loading - else -> oldStatus.asRefreshing() + else -> oldStatus } is WalletDataModel.MissedDerivation, - is WalletDataModel.Refreshing, is WalletDataModel.NoAccount, is WalletDataModel.Unreachable, is WalletDataModel.SameCurrencyTransactionInProgress, diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 8d29c62825..445ff655f2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -4,16 +4,28 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.core.TangemError import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel +import timber.log.Timber internal inline fun HashMap>.replaceWalletStore( - walletId: UserWalletId, - walletStore: WalletStoreModel, + walletStoreToUpdate: WalletStoreModel, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): HashMap> { + return replaceWalletStores(listOf(walletStoreToUpdate), update) +} + +internal inline fun HashMap>.replaceWalletStores( + walletStoresToUpdate: List, update: (walletStore: WalletStoreModel) -> WalletStoreModel, ): HashMap> { return this.apply { - this[walletId] = this[walletId] - ?.replaceWalletStore(walletStore, update) - .orEmpty() + val currentWalletStores = this + walletStoresToUpdate + .groupBy { it.userWalletId } + .forEach { (userWalletId, walletStoresToUpdate) -> + currentWalletStores[userWalletId] = currentWalletStores[userWalletId] + ?.replaceWalletStores(walletStoresToUpdate, update) + .orEmpty() + } } } @@ -74,20 +86,39 @@ internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?) ) } -internal inline fun List.replaceWalletStore( - newWalletStore: WalletStoreModel, - update: (walletStore: WalletStoreModel) -> WalletStoreModel, -): List { - val mutableStores = ArrayList(this) - for ((index, walletStore) in this.withIndex()) { - if (walletStore.isSameWalletStore(newWalletStore)) { - mutableStores[index] = update(walletStore) - break - } - } - return mutableStores +internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { + return this.blockchain == other.blockchain && + this.derivationPath == other.derivationPath } -internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { - return blockchainNetwork == other.blockchainNetwork +private inline fun List.replaceWalletStores( + walletStoresToUpdate: List, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): List { + val mutableStores = ArrayList(this) + + walletStoresToUpdate + .asSequence() + .map { it.blockchain to it.derivationPath } + .forEach { (blockchain, derivationPath) -> + val index = mutableStores.indexOfFirst { + it.blockchain == blockchain && it.derivationPath == derivationPath + } + val currentWalletStore = mutableStores[index] + val updatedWalletStore = update(currentWalletStore) + + if (currentWalletStore != updatedWalletStore) { + Timber.d( + """ + Update wallet store in storage + |- User wallet ID: ${updatedWalletStore.userWalletId} + |- Blockchain: ${updatedWalletStore.blockchain} + """.trimIndent(), + ) + + mutableStores[index] = updatedWalletStore + } + } + + return mutableStores } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt index 1dae6db10e..3646e38f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -4,6 +4,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.domain.common.util.UserWalletId import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -16,8 +18,8 @@ internal object WalletManagerStorage { managers.tryEmit(hashMapOf()) } - suspend fun getAllSync(): Map> { - return managers.first() + fun getAll(): SharedFlow>> { + return managers.asSharedFlow() } private val mutex = Mutex() diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt index d4b6061dfc..93cdfb508f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -2,12 +2,11 @@ package com.tangem.tap.domain.walletStores.storage import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -19,20 +18,8 @@ internal object WalletStoresStorage { stores.tryEmit(hashMapOf()) } - fun getAll(): Flow>> { - return stores - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun get(userWalletId: UserWalletId): Flow> { - return stores - .mapLatest { stores -> - stores[userWalletId].orEmpty() - } - } - - suspend fun getSync(userWalletId: UserWalletId): List { - return stores.first().getOrElse(userWalletId) { emptyList() } + fun getAll(): SharedFlow>> { + return stores.asSharedFlow() } private val mutex = Mutex() 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 829103e4e7..319071642c 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 @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.userWalletId @@ -61,7 +62,7 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action) is DetailsAction.ShowDisclaimer -> { - val uri = store.state.detailsState.cardTermsOfUseUrl + val uri = state.cardTermsOfUseUrl if (uri != null) { store.dispatch(NavigationAction.OpenDocument(uri)) } @@ -71,19 +72,26 @@ class DetailsMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } is DetailsAction.CreateBackup -> { - store.state.detailsState.scanResponse?.let { + state.scanResponse?.let { store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId) + tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) .doOnSuccess { card -> - val currentCardId = store.state.globalState.scanResponse?.card - ?.userWalletId - ?.stringValue - if (card.userWalletId.stringValue == currentCardId) { + val isSameWallet = state.scanResponse?.card?.userWalletId + ?.equals(card.userWalletId) + ?: false + + // !!! Workaround !!! + // TODO: Remove after [REDACTED_JIRA] + val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() + ?.equals(state.scanResponse?.secondTwinPublicKey) + ?: false + + if (isSameWallet || isTwinned) { store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) } else { store.dispatchDialogShow( @@ -116,6 +124,7 @@ class DetailsMiddleware { scope.launch { tangemSdkManager.resetToFactorySettings(card.cardId) .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -255,10 +264,10 @@ class DetailsMiddleware { private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { if (state.saveAccessCodes == enable) return@launch if (enable) { + saveAccessCodes(state) if (!state.saveWallets) { saveCurrentWallet(state) } - saveAccessCodes(state) } else { deleteSavedAccessCodes() } @@ -334,12 +343,15 @@ class DetailsMiddleware { useBiometricsForAccessCode = false, ) - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveAccessCode, - enable = false, - ), - ) + tangemSdkManager.clearSavedUserCodes() + .doOnSuccess { + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveAccessCode, + enable = false, + ), + ) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 31941ed1ee..5a4d1bdc53 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -15,8 +15,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.layout.ContentScale @@ -38,10 +36,6 @@ fun CardSettingsScreen( modifier: Modifier = Modifier, ) { val needReadCard = state.cardDetails == null - val backgroundColor by rememberUpdatedState( - newValue = if (needReadCard) TangemTheme.colors.background.primary - else TangemTheme.colors.background.secondary, - ) SettingsScreensScaffold( content = { @@ -52,7 +46,7 @@ fun CardSettingsScreen( } }, titleRes = R.string.card_settings_title, - backgroundColor = backgroundColor, + backgroundColor = TangemTheme.colors.background.secondary, onBackClick = onBackPressed, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index 47946d1e73..1346fa4f24 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -25,6 +25,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -36,6 +38,10 @@ fun DetailsScreen( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { + SystemBarsEffect { + setSystemBarsColor(color = TangemColorPalette.Light1) + } + SettingsScreensScaffold( content = { Content(state = state, modifier = modifier) }, onBackClick = onBackPressed, diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index ca82ff1fee..5461c66441 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -9,9 +9,19 @@ sealed class DisclaimerAction : Action { val type: DisclaimerType, ) : DisclaimerAction() - data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + data class Show( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() - internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + + internal data class SetCallbacks( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + + object OnBackPressed : DisclaimerAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index 1b42d7b4fc..01e5de971a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -28,7 +28,7 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { } is DisclaimerAction.Show -> { handleUpdateState = state.type.createUpdateState() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback)) + store.dispatch(DisclaimerAction.SetCallbacks(action.onAcceptCallback, action.onDismissCallback)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } is DisclaimerAction.AcceptDisclaimer -> { @@ -36,7 +36,13 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { handleUpdateState = action.type.createUpdateState() store.dispatch(NavigationAction.PopBackTo()) state.onAcceptCallback?.invoke() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(null)) + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + } + is DisclaimerAction.OnBackPressed -> { + state.onDismissCallback?.invoke() + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + store.dispatch(NavigationAction.PopBackTo()) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 1b282877f2..766c6a42ca 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -19,8 +19,9 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { type = action.type, accepted = action.accepted, ) - is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy( + is DisclaimerAction.SetCallbacks -> disclaimerState.copy( onAcceptCallback = action.onAcceptCallback, + onDismissCallback = action.onDismissCallback, ) else -> disclaimerState } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index ee0700f5b5..d7ca859973 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -11,6 +11,7 @@ data class DisclaimerState( val accepted: Boolean = false, val type: DisclaimerType = DisclaimerType.Tangem, val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, ) : StateType sealed class DisclaimerType( diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 633ef10d6a..7761de53b6 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.redux.AppState @@ -29,6 +30,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs override fun onStart() { super.onStart() + setStatusBarColor(R.color.backgroundLightGray) store.subscribe(subscriber = this) { state -> state .skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState } @@ -62,4 +64,8 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs webView.loadUrl(state.type.uri.toString()) } + + override fun handleOnBackPressed() { + store.dispatch(DisclaimerAction.OnBackPressed) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index c879e6ca04..0c5b990b06 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -79,13 +79,17 @@ private fun readCard() = scope.launch { onProgressStateChange = { showProgress -> if (showProgress) { changeButtonState(ButtonState.PROGRESS) - } else { - changeButtonState(ButtonState.ENABLED) } + // else { //todo hide this because + // changeButtonState(ButtonState.ENABLED) + // } }, onScanStateChange = { scanInProgress -> store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, + onFailure = { + changeButtonState(ButtonState.ENABLED) + }, onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { @@ -93,21 +97,18 @@ private fun readCard() = scope.launch { userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) store.onCardScanned(scanResponse) } .doOnSuccess { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.onUserWalletSelected(userWallet) + scope.launch { store.onUserWalletSelected(userWallet) } } .doOnResult { + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } else { store.onCardScanned(scanResponse) + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index b1ce166222..9b5c5e9958 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,20 +1,16 @@ package com.tangem.tap.features.onboarding -import com.tangem.common.doOnSuccess import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onCardScanned -import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -59,17 +55,6 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { - // When should save user wallets but manager is locked, then unlock manager with card - preferencesStorage.shouldSaveUserWallets && - userWalletsListManager.isLockedSync -> scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.onUserWalletSelected(userWallet) - } - } // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 8853e2766e..ae60d9f637 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,6 +6,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -31,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -69,7 +71,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { fun updateScanResponse(response: ScanResponse) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response - CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response)) + CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanResponse(response)) } } @@ -151,10 +153,15 @@ private fun handle(action: Action, dispatch: DispatchFunction) { finishCardActivation() postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) } } - + TwinCardsStep.CreateFirstWallet -> { + scope.launch { + userWalletsListManager.delete( + listOf(getScanResponse().card.userWalletId), + ) + } + } TwinCardsStep.None, TwinCardsStep.Warning, - TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, TwinCardsStep.CreateThirdWallet, -> Unit @@ -294,13 +301,14 @@ private fun handle(action: Action, dispatch: DispatchFunction) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> { store.dispatchOnMain(GlobalAction.Onboarding.Stop) - OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( - scanResponse = scanResponse, - backupCardsIds = listOfNotNull(twinCardsState.twinCardsManager?.secondCardPublicKey), - ) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } CreateTwinWalletMode.RecreateWallet -> { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + if (preferencesStorage.shouldSaveUserWallets) { + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 9511376155..36e386325e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,6 +13,7 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget @@ -97,6 +98,11 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } + override fun onStart() { + super.onStart() + setStatusBarColor(R.color.backgroundWhite) + } + private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) = with(containerBinding) { imvFrontCard.hide() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index d11d5139fb..106ebfa9af 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -94,7 +94,6 @@ internal class SaveWalletMiddleware { saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } - .flatMap { userWalletsListManager.selectWallet(userWallet.walletId) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -105,12 +104,6 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.dispatchOnMain(SaveWalletAction.Save.Success) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 22d6c9138d..5bf0618fbb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -5,18 +5,26 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionError +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -28,13 +36,25 @@ import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction.RequestFee +import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdk +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -308,12 +328,15 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { } private suspend fun updateWallet(walletManager: WalletManager) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { + val wallet = walletManager.wallet walletCurrenciesManager.update( userWallet = selectedUserWallet, - blockchainNetwork = blockchainNetwork, + currency = Currency.Blockchain( + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), ) } else { store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 8465e9d228..b07a4fd3ca 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -221,7 +221,7 @@ class TokensMiddleware { val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) @@ -383,7 +383,7 @@ class TokensMiddleware { } } - val addedCurrencies = store.state.walletState.wallets.map { walletStore -> + val addedCurrencies = store.state.walletState.walletsStores.map { walletStore -> walletStore.walletsData.map { walletData -> walletData.currency } }.flatten().map { when (it) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index ea9a272f74..2fa1ab2971 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -57,7 +57,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) - toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + toolbar.setNavigationOnClickListener { handleOnBackPressed() } val onSaveChanges = { tokens: List, blockchains: List -> Analytics.send(ManageTokens.ButtonSaveChanges()) @@ -144,7 +144,6 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc } override fun handleOnBackPressed() { - super.handleOnBackPressed() store.dispatch(NavigationAction.PopBackTo()) store.dispatch(TokensAction.ResetState) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 6d751e8956..149fd2b517 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.analytics.Analytics @@ -81,6 +82,11 @@ fun CurrenciesScreen( } } + val statusBarColor = colorResource(id = R.color.backgroundLightGray) + SystemBarsEffect { + setSystemBarsColor(color = statusBarColor) + } + Scaffold( floatingActionButton = { if (tokensState.value.allowToAdd) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index be432defdc..0cc8039e96 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -12,11 +12,11 @@ import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.wallet.R import org.rekotlin.Action @@ -36,7 +36,7 @@ sealed class WalletAction : Action { data class LoadWallet( val blockchain: BlockchainNetwork? = null, - val walletManager: WalletManager? = null + val walletManager: WalletManager? = null, ) : WalletAction() { data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction() data class NoAccount( @@ -90,7 +90,8 @@ sealed class WalletAction : Action { val blockchain: BlockchainNetwork, ) : MultiWallet() - data class SelectWallet(val walletData: WalletData?) : MultiWallet() + data class SelectWallet(val currency: Currency?) : MultiWallet() + data class SetSingleWalletCurrency(val currency: Currency?) : MultiWallet() data class TryToRemoveWallet(val currency: Currency) : MultiWallet() data class RemoveWallet(val currency: Currency) : MultiWallet() @@ -128,7 +129,7 @@ sealed class WalletAction : Action { val wallet: Wallet? = null, val coinsList: List? = null, ) : WalletAction() { data class Success( - val fiatRates: Map + val fiatRates: Map, ) : WalletAction() object Failure : WalletAction() @@ -163,7 +164,7 @@ sealed class WalletAction : Action { object ChooseTradeActionDialog : DialogAction() data class ChooseCurrency(val amounts: List?) : DialogAction() data class RussianCardholdersWarningDialog( - val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null + val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() object Hide : DialogAction() @@ -181,12 +182,13 @@ sealed class WalletAction : Action { data class Buy( val checkUserLocation: Boolean = true, ) : TradeCryptoAction() + data class FinishSelling(val transactionId: String) : TradeCryptoAction() data class SendCrypto( val currencyId: String, val amount: String, val destinationAddress: String, - val transactionId: String + val transactionId: String, ) : TradeCryptoAction() } @@ -206,6 +208,9 @@ sealed class WalletAction : Action { } data class UserWalletChanged(val userWallet: UserWallet) : WalletAction() - data class WalletStoresChanged(val walletStores: List) : WalletAction() - data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction() + data class WalletStoresChanged(val walletStores: List) : WalletAction() { + data class UpdateWalletStores(val reduxWalletStores: List) : WalletAction() + } + + data class TotalFiatBalanceChanged(val balance: TotalBalance) : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b8413753cb..49773a9854 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -36,7 +36,7 @@ data class WalletState( val cardImage: Artwork? = null, val hashesCountVerified: Boolean? = null, val mainWarningsList: List = mutableListOf(), - val wallets: List = listOf(), + val walletsStores: List = listOf(), val isMultiwalletAllowed: Boolean = false, val cardCurrency: CryptoCurrencyName? = null, val selectedCurrency: Currency? = null, @@ -50,6 +50,12 @@ data class WalletState( val walletCardsCount: Int? = null, ) : StateType { + val walletsDataFromStores: List + get() = walletsStores.map { it.walletsData }.flatten() + + val selectedWalletData: WalletData? + get() = walletsDataFromStores.firstOrNull { it.currency == selectedCurrency } + // if you do not delegate - the application crashes on startup, // because twinCardsState has not been created yet val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ -> @@ -63,20 +69,17 @@ data class WalletState( get() = store.state.globalState.exchangeManager.featureIsSwitchedOn() val blockchains: List - get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain } + get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain } val currencies: List - get() = wallets.flatMap { it.walletsData }.map { it.currency } - - val walletsData: List - get() = wallets.flatMap { it.walletsData } + get() = walletsStores.flatMap { it.walletsData }.map { it.currency } val walletManagers: List - get() = wallets.mapNotNull { it.walletManager } + get() = walletsStores.mapNotNull { it.walletManager } - val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull() + val primaryWallet: WalletData? = walletsStores.firstOrNull()?.walletsData?.firstOrNull() - val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null + val primaryWalletManager: WalletManager? = if (walletsStores.isNotEmpty()) walletsStores[0].walletManager else null val shouldShowDetails: Boolean = primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && @@ -91,12 +94,12 @@ data class WalletState( } fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? { - return wallets.find { it.blockchainNetwork == blockchain }?.walletManager + return walletsStores.find { it.blockchainNetwork == blockchain }?.walletManager } fun getWalletData(blockchain: BlockchainNetwork?): WalletData? { if (blockchain == null) return null - return walletsData.find { + return walletsDataFromStores.find { it.currency is Currency.Blockchain && it.currency.blockchain == blockchain.blockchain && it.currency.derivationPath == blockchain.derivationPath @@ -105,7 +108,7 @@ data class WalletState( fun getWalletStore(currency: Currency?): WalletStore? { if (currency == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == currency.derivationPath && (it.blockchainNetwork.blockchain == currency.blockchain) } @@ -120,7 +123,7 @@ data class WalletState( fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? { if (blockchainNetwork == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && (it.blockchainNetwork.blockchain == blockchainNetwork.blockchain) } @@ -131,10 +134,6 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - fun getSelectedWalletData(): WalletData? { - return walletsData.find { it.currency == selectedCurrency } - } - private fun isPrimaryCurrency(walletData: WalletData): Boolean { return (walletData.currency is Currency.Blockchain && walletData.currency.blockchain == store.state.walletState.primaryBlockchain) @@ -142,10 +141,10 @@ data class WalletState( walletData.currency.token == store.state.walletState.primaryToken) } - fun replaceWalletInWallets(wallet: WalletStore?): List { - if (wallet == null) return wallets + fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List { + if (wallet == null) return walletsStores var changed = false - val updatedWallets = wallets.map { + val updatedWallets = walletsStores.map { if (it.blockchainNetwork == wallet.blockchainNetwork) { changed = true wallet @@ -153,7 +152,7 @@ data class WalletState( it } } - return if (changed) updatedWallets else wallets + wallet + return if (changed) updatedWallets else walletsStores + wallet } fun updateWalletData(walletData: WalletData?): WalletState { @@ -161,26 +160,23 @@ data class WalletState( return updateWalletsData(listOf(walletData)) } - fun updateWalletsData( - walletsData: List - ): WalletState { - + fun updateWalletsData(walletsData: List): WalletState { val walletStores = walletsData .map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) } .distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) } - return updateWalletStores(walletStores) + return updateWalletsStores(walletStores) } fun updateWalletStore(walletStore: WalletStore?): WalletState { - return copy(wallets = replaceWalletInWallets(walletStore)) + return copy(walletsStores = replaceWalletStoreInWalletsStores(walletStore)) .updateTotalBalance() .updateProgressState() } - private fun updateWalletStores(walletStores: List): WalletState { + private fun updateWalletsStores(walletStores: List): WalletState { val walletStoresMutable = walletStores.toMutableList() - val updatedWallets = wallets.map { oldWalletStore -> + val updatedWallets = walletsStores.map { oldWalletStore -> val walletStore = walletStoresMutable.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork } @@ -191,20 +187,20 @@ data class WalletState( oldWalletStore } } - return copy(wallets = updatedWallets + walletStoresMutable) + return copy(walletsStores = updatedWallets + walletStoresMutable) .updateTotalBalance() .updateProgressState() } - fun removeWallet(walletData: WalletData?): WalletState { + fun removeWalletData(walletData: WalletData?): WalletState { if (walletData == null) return this return when (val currency = walletData.currency) { is Currency.Blockchain -> { - val walletStores = wallets.filterNot { + val walletStores = walletsStores.filterNot { it.blockchainNetwork.blockchain == currency.blockchain && it.blockchainNetwork.derivationPath == currency.derivationPath } - copy(wallets = walletStores) + copy(walletsStores = walletStores) .updateTotalBalance() .updateProgressState() } @@ -223,23 +219,8 @@ data class WalletState( } } - fun replaceSomeWallets(newWallets: List): List { - val remainingWallets: MutableList = newWallets.toMutableList() - val updatedWallets = walletsData.map { wallet -> - val newWallet = newWallets - .firstOrNull { wallet.currency == it.currency } - if (newWallet == null) { - wallet - } else { - remainingWallets.remove(newWallet) - newWallet - } - } - return updatedWallets + remainingWallets - } - private fun updateTotalBalance(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -256,7 +237,7 @@ data class WalletState( } private fun updateProgressState(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -264,7 +245,7 @@ data class WalletState( this.copy( state = walletsData.findProgressState(), - error = this.error.takeIf { newProgressState == ProgressState.Error } + error = this.error.takeIf { newProgressState == ProgressState.Error }, ) } else this } @@ -276,6 +257,21 @@ data class WalletState( } } +fun List.replaceSomeWalletsData(newWallets: List): List { + val remainingWallets: MutableList = newWallets.toMutableList() + val updatedWallets = this.map { wallet -> + val newWallet = newWallets + .firstOrNull { wallet.currency == it.currency } + if (newWallet == null) { + wallet + } else { + remainingWallets.remove(newWallet) + newWallet + } + } + return updatedWallets + remainingWallets +} + enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error } enum class ErrorType { NoInternetConnection } @@ -287,7 +283,7 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { data class WalletAddresses( val selectedAddress: AddressData, - val list: List + val list: List, ) data class AddressData( @@ -301,7 +297,7 @@ data class AddressData( data class Artwork( val artworkId: String, - val artwork: Bitmap? = null + val artwork: Bitmap? = null, ) { companion object { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" @@ -374,7 +370,7 @@ data class WalletData( } } - private fun assembleTokenWarnings(walletWarnings: MutableList){ + private fun assembleTokenWarnings(walletWarnings: MutableList) { if (!currency.isToken()) return val blockchainFullName = currency.blockchain.fullName @@ -391,7 +387,7 @@ data class WalletData( data class WalletStore( val walletManager: WalletManager?, val blockchainNetwork: BlockchainNetwork, - val walletsData: List + val walletsData: List, ) { fun updateWallets(walletDataList: List): WalletStore { val relevantWalletDataList = walletDataList.filter { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt new file mode 100644 index 0000000000..dbb030f67f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.features.wallet.redux.middlewares + +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.store + +internal fun List.mapToReduxModels( + isMultiWalletAllowed: Boolean, +): List { + return this.map { walletStoreModel -> + walletStoreModel.mapToReduxModel(isMultiWalletAllowed) + } +} + +internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance { + return TotalBalance( + state = when (this) { + is TotalFiatBalance.Loading -> ProgressState.Loading + is TotalFiatBalance.Error -> ProgressState.Error + is TotalFiatBalance.Loaded -> ProgressState.Done + }, + fiatAmount = amount, + fiatCurrency = store.state.globalState.appCurrency, + ) +} + +internal fun WalletStoreModel.mapToReduxModel( + isMultiWalletAllowed: Boolean, +): WalletStore { + return WalletStore( + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, + walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletRent), + ) +} + +private fun List.mapToReduxModel( + isMultiWalletAllowed: Boolean, + walletRent: WalletStoreModel.WalletRent?, +): List { + return this.map { walletDataModel -> + with(walletDataModel) { + val amount = status.amount + val amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ) + val appCurrency = store.state.globalState.appCurrency + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + val fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrency.symbol) + val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) + + WalletData( + currency = currency, + walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> + WalletAddresses( + selectedAddress = selectedAddress, + list = walletAddresses, + ) + }, + existentialDepositString = existentialDeposit?.toPlainString(), + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + pendingTransactions = status.pendingTransactions, + mainButton = WalletMainButton.SendButton( + enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), + ), + walletRent = walletRent?.let { + WalletRent( + minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + ) + }, + currencyData = BalanceWidgetData( + status = when (status) { + is WalletDataModel.Loading -> BalanceStatus.Loading + is WalletDataModel.NoAccount -> BalanceStatus.NoAccount + is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress + is WalletDataModel.Unreachable -> BalanceStatus.Unreachable + is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation + is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline + }, + currency = currency.currencyName, + currencySymbol = currency.currencySymbol, + blockchainAmount = status.amount, + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + token = when { + !isMultiWalletAllowed && currency is Currency.Token -> { + TokenData( + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + tokenSymbol = currency.currencySymbol, + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + ) + } + else -> null + }, + amountToCreateAccount = (status as? WalletDataModel.NoAccount) + ?.amountToCreateAccount + ?.toString(), + errorMessage = status.errorMessage, + ), + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index bbe6f90378..9bba0011a5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -51,7 +51,7 @@ class MultiWalletMiddleware { handleAddingWalletManagers(globalState, action.walletManagers) } is WalletAction.MultiWallet.SelectWallet -> { - if (action.walletData != null) { + if (action.currency != null) { store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 5d6a9fc2d7..df69426a2c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -44,7 +44,7 @@ class TradeCryptoMiddleware { return } - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val card = store.state.globalState.scanResponse?.card ?: return val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -85,7 +85,7 @@ class TradeCryptoMiddleware { } private fun proceedSellAction() { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val appCurrency = store.state.globalState.appCurrency val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -107,7 +107,7 @@ class TradeCryptoMiddleware { } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index f94e6def86..fb6ff1dd3f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -17,7 +17,7 @@ class WalletDialogsMiddleware { store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog) } is WalletAction.DialogAction.ChooseTradeActionDialog -> { - store.state.walletState.getSelectedWalletData()?.let { + store.state.walletState.selectedWalletData?.let { Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) } store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index f6e0c47011..5dcd8b81bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -97,11 +97,7 @@ class WalletMiddleware { when (action) { is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> multiWalletMiddleware.handle( - action, - walletState, - globalState - ) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is WalletAction.DialogAction -> walletDialogMiddleware.handle(action) is WalletAction.LoadWallet -> { @@ -129,9 +125,7 @@ class WalletMiddleware { true, ), ) - store.dispatch( - action = WalletAction.LoadWallet.Success(action.wallet, action.blockchain) - ) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) } } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) @@ -150,7 +144,7 @@ class WalletMiddleware { action.coinsList != null -> action.coinsList else -> { if (walletState.isMultiwalletAllowed) { - walletState.walletsData.map { it.currency } + walletState.walletsDataFromStores.map { it.currency } } else { val derivationPath = walletState.primaryWallet?.currency?.derivationPath val primaryBlockchain = walletState.primaryBlockchain @@ -175,7 +169,7 @@ class WalletMiddleware { Timber.e( throwable, "Loading rates failed for [%s]", - currency.currencySymbol + currency.currencySymbol, ) } } @@ -188,14 +182,11 @@ class WalletMiddleware { } is WalletAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createWallet( - globalState.scanResponse?.card?.cardId - ) + val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId) when (result) { is CompletionResult.Success -> { - val scanNoteResponse = - globalState.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } + val scanResponse = globalState.scanResponse?.copy(card = result.data) + scanResponse?.let { store.onCardScanned(scanResponse) } } is CompletionResult.Failure -> {} } @@ -210,9 +201,7 @@ class WalletMiddleware { store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) scope.launch { - val response = OnlineCardVerifier().getCardInfo( - action.card.cardId, action.card.cardPublicKey - ) + val response = OnlineCardVerifier().getCardInfo(action.card.cardId, action.card.cardPublicKey) when (response) { is Result.Success -> { val actionList = listOf( @@ -237,11 +226,12 @@ class WalletMiddleware { refresh = action is WalletAction.LoadData.Refresh, ) } else { - val scanNoteResponse = globalState.scanResponse ?: return@launch - if (walletState.walletsData.isNotEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) + val scanResponse = globalState.scanResponse ?: return@launch + + if (walletState.walletsDataFromStores.isNotEmpty()) { + globalState.tapWalletManager.reloadData(scanResponse) } else { - globalState.tapWalletManager.loadData(scanNoteResponse) + globalState.tapWalletManager.loadData(scanResponse) } } } @@ -249,8 +239,8 @@ class WalletMiddleware { is NetworkStateChanged -> { store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - globalState.tapWalletManager.loadData(selectedUserWallet) + if (selectedUserWallet != null) { + scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } } else { globalState.scanResponse?.let { scanNoteResponse -> scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } @@ -295,6 +285,7 @@ class WalletMiddleware { } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { + updateWalletStores(action.walletStores, walletState) fetchTotalFiatBalance(action.walletStores, walletState) findMissedDerivations(action.walletStores) tryToShowAppRatingWarning(action.walletStores) @@ -303,12 +294,29 @@ class WalletMiddleware { } } + private fun updateWalletStores(wallStores: List, state: WalletState) { + scope.launch(Dispatchers.Default) { + if (!state.isMultiwalletAllowed) { + wallStores.firstOrNull()?.walletsData?.firstOrNull()?.let { + store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it.currency)) + } + } + + val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) + store.dispatchOnMain( + WalletAction.WalletStoresChanged.UpdateWalletStores( + reduxWalletStores = reduxWalletStores.toList(), + ), + ) + } + } + private fun fetchTotalFiatBalance(walletStores: List, state: WalletState) { scope.launch(Dispatchers.Default) { val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull( prevAmount = state.totalBalance?.fiatAmount, walletStores = walletStores, - ) + )?.mapToReduxModel() if (totalFiatBalance != null) { store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) @@ -361,7 +369,7 @@ class WalletMiddleware { } private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { - val selectedWalletData = state?.getSelectedWalletData() + val selectedWalletData = state?.selectedWalletData val currency = selectedWalletData?.currency val walletStore = state?.getWalletStore(currency) @@ -376,36 +384,34 @@ class WalletMiddleware { if (currency != null && state.isMultiwalletAllowed) { when (currency) { is Currency.Blockchain -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData.fiatRate, - walletManager = walletStore.walletManager + walletManager = walletStore.walletManager, ) } is Currency.Token -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) prepareSendActionForToken( amount = amountToSend, state = state, selectedWalletData = selectedWalletData, - walletStore = walletStore + walletStore = walletStore, ) } } } else { - if (amounts?.size ?: 0 > 1) { + if ((amounts?.size ?: 0) > 1) { WalletAction.DialogAction.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData?.fiatRate, - walletManager = walletStore?.walletManager + walletManager = walletStore?.walletManager, ) } } @@ -416,7 +422,7 @@ class WalletMiddleware { amount: Amount, state: WalletState?, selectedWalletData: WalletData?, - walletStore: WalletStore? + walletStore: WalletStore?, ): PrepareSendScreen { val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate val tokenRate = if (state?.isMultiwalletAllowed == true) { @@ -431,7 +437,7 @@ class WalletMiddleware { coinRate = coinRate, walletManager = walletStore?.walletManager, tokenAmount = amount, - tokenRate = tokenRate + tokenRate = tokenRate, ) } @@ -447,7 +453,7 @@ class WalletMiddleware { val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) val outgoingTxs = walletManager.wallet.getPendingTransactions( - PendingTransactionType.Outgoing + PendingTransactionType.Outgoing, ).filterByCoin() val rentExempt = result.data @@ -465,8 +471,8 @@ class WalletMiddleware { WalletAction.SetWalletRent( wallet = walletManager.wallet, minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") - ) + rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"), + ), ) } else { dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index e911c48e3c..b2ef29f6b3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -125,7 +125,8 @@ class WarningsMiddleware { if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null if (scanResponse.card.isMultiwalletAllowed) { - return if (scanResponse.card.hasSignedHashes()) { + val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) } + return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) { WarningMessagesManager.signedHashesMultiWalletWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -133,8 +134,7 @@ class WarningsMiddleware { } } - val validator = store.state.walletState.walletManagers.firstOrNull() - as? SignatureCountValidator + val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator return if (validator == null) { if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 3b174c915f..b1e6cebbac 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -9,9 +9,18 @@ import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.* -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.filterByToken +import com.tangem.tap.features.wallet.models.getPendingTransactions +import com.tangem.tap.features.wallet.models.removeUnknownTransactions +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -22,7 +31,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val wallets: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.mapNotNull { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -38,13 +47,13 @@ class MultiWalletReducer { status = BalanceStatus.Loading, currency = blockchain.blockchain.fullName, currencySymbol = blockchain.blockchain.currency, - token = cardToken + token = cardToken, ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( blockchain.blockchain, - blockchain.derivationPath + blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) @@ -52,18 +61,18 @@ class MultiWalletReducer { WalletStore( walletManager = walletManager, blockchainNetwork = blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) } val selectedCurrency = if (!state.isMultiwalletAllowed) { - wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { - state.selectedCurrency + state.selectedWalletData?.currency } state.copy( - wallets = wallets, - selectedCurrency = selectedCurrency + walletsStores = walletStores, + selectedCurrency = selectedCurrency, ) } is WalletAction.MultiWallet.AddBlockchain -> { @@ -80,14 +89,14 @@ class MultiWalletReducer { mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( action.blockchain.blockchain, - action.blockchain.derivationPath + action.blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) val walletStore = WalletStore( walletManager = walletManager, blockchainNetwork = action.blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) val newState = state.updateWalletStore(walletStore) @@ -125,36 +134,40 @@ class MultiWalletReducer { status = tokenBalanceStatus, amount = action.amount.value, amountFormatted = action.amount.value?.toFormattedCurrencyString( - action.amount.decimals, action.amount.currencySymbol + action.amount.decimals, action.amount.currencySymbol, ), fiatAmountFormatted = tokenWalletData.fiatRate?.let { action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol) } ?: UNKNOWN_AMOUNT_SIGN, - blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO + blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO, ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), currency = Currency.Token( token = action.token, blockchain = action.blockchain.blockchain, - derivationPath = action.blockchain.derivationPath + derivationPath = action.blockchain.derivationPath, ), - walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)) + walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), ) state.updateWalletData(newTokenWalletData) } is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) - is WalletAction.MultiWallet.SelectWallet -> - state.copy(selectedCurrency = action.walletData?.currency) + is WalletAction.MultiWallet.SelectWallet -> { + state.copy(selectedCurrency = action.currency) + } + is WalletAction.MultiWallet.SetSingleWalletCurrency -> { + state.copy(selectedCurrency = action.currency) + } is WalletAction.MultiWallet.TryToRemoveWallet -> state is WalletAction.MultiWallet.RemoveWallet -> { - state.removeWallet(state.getWalletData(action.currency)) + state.removeWalletData(state.getWalletData(action.currency)) } is WalletAction.MultiWallet.RemoveWallets -> { var updatedState = state - action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) } + action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } updatedState } is WalletAction.MultiWallet.SetPrimaryBlockchain -> @@ -188,7 +201,7 @@ class MultiWalletReducer { } private fun addTokens( - tokens: List, blockchain: BlockchainNetwork, state: WalletState + tokens: List, blockchain: BlockchainNetwork, state: WalletState, ): WalletState { val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } return state.updateWalletsData(wallets) @@ -206,10 +219,10 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat currencyData = BalanceWidgetData( status = BalanceStatus.Loading, currency = this.name, - currencySymbol = this.symbol + currencySymbol = this.symbol, ), walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), - currency = currency + currency = currency, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 79e84eec36..68e6080883 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -17,6 +17,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -106,18 +107,15 @@ class OnWalletLoadedReducer { mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), ) } - val newWallets = tokens + newWalletData - val wallets = walletState.replaceSomeWallets((newWallets)) + val newWalletsData = tokens + newWalletData + val walletsData = walletState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) - return walletState - .updateWalletsData(wallets) + return walletState.updateWalletsData(walletsData) } private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState - // val ratesRespository = store.state.globalState.tapWalletManager.ratesRepository - // val tokenCurrency = val fiatCurrencyName = store.state.globalState.appCurrency.code val token = wallet.getFirstToken() val tokenData = if (token != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 79470dae62..08a7def4bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -36,6 +36,7 @@ private fun List.mapToProgressState(): List { BalanceStatus.Unreachable, BalanceStatus.EmptyCard, BalanceStatus.UnknownBlockchain, + BalanceStatus.MissedDerivation, -> ProgressState.Error BalanceStatus.Loading, null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 61ea00554a..254fe48e3b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -3,29 +3,38 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.isZero import com.tangem.common.extensions.mapNotNullValues import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.TotalFiatBalance -import com.tangem.tap.domain.model.WalletDataModel -import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.store import org.rekotlin.Action @@ -63,7 +72,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.EmptyWallet -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -85,7 +94,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { - val wallets = newState.wallets + val wallets = newState.walletsStores .map { store -> store.copy( walletsData = store.walletsData.map { @@ -95,20 +104,19 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) }, - - ) + ) } newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, - wallets = wallets, + walletsStores = wallets, ) } is TapError.UnknownBlockchain -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -153,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS BalanceStatus.Loading } if (action.blockchain == null) { - val wallets = newState.wallets.map { walletStore -> + val wallets = newState.walletsStores.map { walletStore -> walletStore.copy( walletsData = walletStore.walletsData.map { walletData -> walletData.copy( @@ -170,7 +178,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } newState = newState.copy( state = ProgressState.Loading, - wallets = wallets, + walletsStores = wallets, ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState @@ -178,7 +186,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS walletManager.cardTokens.map { Currency.fromBlockchainNetwork(action.blockchain, it) } - val newWallets = newState.walletsData.filter { currencies.contains(it.currency) } + val newWalletsData = newState.walletsDataFromStores.filter { currencies.contains(it.currency) } .map { wallet -> wallet.copy( currencyData = wallet.currencyData.copy( @@ -189,8 +197,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS mainButton = WalletMainButton.SendButton(false), ) } - val wallets = newState.replaceSomeWallets(newWallets) - val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets) + val walletsData = newState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) + val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(walletsData) newState = newState.updateWalletStore(walletStore) } } @@ -260,7 +268,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS newState = newState.updateWalletsData(updatedWallets) val progressState = - if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) { + if (newState.walletsDataFromStores.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading } else { ProgressState.Done @@ -292,15 +300,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { - val selectedWalletData = newState.getWalletData(newState.selectedCurrency) - - val walletAddresses = - newState.getWalletData(selectedWalletData?.currency)?.walletAddresses - ?: return newState + val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses + ?: return newState val address = walletAddresses.list.firstOrNull { it.type == action.type } ?: return newState + newState = newState.updateWalletData( - selectedWalletData?.copy( + newState.selectedWalletData?.copy( walletAddresses = WalletAddresses( selectedAddress = address, list = walletAddresses.list, @@ -328,37 +334,41 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS val card = scanResponse.card newState = WalletState( cardId = card.cardId, - isMultiwalletAllowed = card.isMultiwalletAllowed, + isMultiwalletAllowed = isMultiCurrency, cardImage = Artwork( artworkId = artworkUrl, ), isTestnet = card.isTestCard, state = ProgressState.Loading, - wallets = newState.wallets, - showBackupWarning = card.isMultiwalletAllowed && + showBackupWarning = isMultiCurrency && card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + totalBalance = if (isMultiCurrency) { + TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + } else { + null + }, ) } - is WalletAction.WalletStoresChanged -> { + is WalletAction.WalletStoresChanged.UpdateWalletStores -> { newState = newState.copy( - wallets = action.walletStores.mapToReduxModel(newState.isMultiwalletAllowed), + walletsStores = action.reduxWalletStores, ) } is WalletAction.TotalFiatBalanceChanged -> { newState = newState.copy( - totalBalance = action.balance.mapToReduxModel(), + totalBalance = action.balance, ) } is WalletAction.LoadData.Success -> { val selectedCurrency = if (!newState.isMultiwalletAllowed) { - newState.wallets.firstOrNull() + newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency } else { - newState.selectedCurrency + newState.selectedWalletData?.currency } newState = newState.copy( @@ -377,117 +387,6 @@ private fun CardDTO.findCardsCount(): Int? { ?.takeIf { this.isMultiwalletAllowed } } -@JvmName("walletStoreModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, -): List { - return this.map { walletStoreModel -> - with(walletStoreModel) { - WalletStore( - walletManager = walletManager, - blockchainNetwork = blockchainNetwork, - walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletStoreModel.walletRent), - ) - } - } -} - -@JvmName("walletDataModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, - walletRent: WalletStoreModel.WalletRent?, -): List { - return this.map { walletDataModel -> - with(walletDataModel) { - val amount = status.amount - val amountFormatted = amount.toFormattedCurrencyString( - decimals = currency.decimals, - currency = currency.currencySymbol, - ) - val appCurrency = store.state.globalState.appCurrency - val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount - ?.takeIf { !status.isErrorStatus } - ?.toFormattedFiatValue(appCurrency.symbol) - val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) - - WalletData( - currency = currency, - walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> - WalletAddresses( - selectedAddress = selectedAddress, - list = walletAddresses, - ) - }, - existentialDepositString = existentialDeposit?.toPlainString(), - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - pendingTransactions = status.pendingTransactions, - mainButton = WalletMainButton.SendButton( - enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), - ), - walletRent = walletRent?.let { - WalletRent( - minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", - rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", - ) - }, - currencyData = BalanceWidgetData( - status = when (status) { - is WalletDataModel.Loading -> BalanceStatus.Loading - is WalletDataModel.NoAccount -> BalanceStatus.NoAccount - is WalletDataModel.Refreshing -> BalanceStatus.Refreshing - is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress - is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress - is WalletDataModel.Unreachable, - is WalletDataModel.MissedDerivation, - -> BalanceStatus.Unreachable - is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline - }, - currency = currency.currencyName, - currencySymbol = currency.currencySymbol, - blockchainAmount = status.amount, - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - token = when { - !isMultiWalletAllowed && currency is Currency.Token -> { - TokenData( - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - tokenSymbol = currency.currencySymbol, - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - ) - } - else -> null - }, - amountToCreateAccount = (status as? WalletDataModel.NoAccount) - ?.amountToCreateAccount - ?.toString(), - errorMessage = status.errorMessage, - ), - ) - } - } -} - -private fun TotalFiatBalance.mapToReduxModel(): TotalBalance { - return TotalBalance( - state = when (this) { - is TotalFiatBalance.Loading -> ProgressState.Loading - is TotalFiatBalance.Refreshing -> ProgressState.Refreshing - is TotalFiatBalance.Error -> ProgressState.Error - is TotalFiatBalance.Loaded -> ProgressState.Done - }, - fiatAmount = amount, - fiatCurrency = store.state.globalState.appCurrency, - ) -} - fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 71bdbb7b8c..4afc4c08db 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -16,7 +16,8 @@ enum class BalanceStatus { Refreshing, NoAccount, EmptyCard, - UnknownBlockchain + UnknownBlockchain, + MissedDerivation, } data class BalanceWidgetData( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index d737ac736b..a7f230dc35 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -1,16 +1,23 @@ package com.tangem.tap.features.wallet.ui import android.os.Bundle -import android.view.* +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.badoo.mvicore.modelWatcher +import com.tangem.common.doOnResult import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler @@ -18,26 +25,38 @@ import com.tangem.tap.common.TestActions import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.appendIfNotNull +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.getString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWallet -import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManagerSafe import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), @@ -48,6 +67,42 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind) + private val walletDataWatcher = modelWatcher { + WalletData::pendingTransactions { + showPendingTransactionsIfPresent(it) + } + WalletData::currency { + handleCurrencyIcon(it) + } + WalletData::currencyData { + setupBalanceData(it) + } + (WalletData::currencyData or WalletData::currency) { walletData -> + setupCurrency(walletData.currencyData, walletData.currency) + setupSwipeRefresh(walletData.currencyData, walletData.currency) + } + } + + private val walletStateWatcher = modelWatcher { + (WalletState::selectedCurrency or WalletState::selectedWalletData) { state -> + val selectedWalletData = state.selectedWalletData + if (selectedWalletData != null) { + walletDataWatcher.invoke(selectedWalletData) + setupButtons(selectedWalletData, state.isExchangeServiceFeatureOn) + setupAddressCard(selectedWalletData) + handleWarnings(selectedWalletData) + } + } + (WalletState::selectedCurrency or WalletState::isExchangeServiceFeatureOn) { state -> + if (state.selectedWalletData != null) { + setupButtons(state.selectedWalletData!!, state.isExchangeServiceFeatureOn) + } + } + (WalletState::state or WalletState::error) { state -> + setupNoInternetHandling(state.state, state.error) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -79,6 +134,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), store.unsubscribe(this) } + override fun onDestroy() { + walletDataWatcher.clear() + walletStateWatcher.clear() + super.onDestroy() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) @@ -129,50 +190,10 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun newState(state: WalletState) { if (activity == null || view == null) return - if (state.selectedCurrency == null) return - val selectedWallet = state.getSelectedWalletData() ?: return + if (state.selectedWalletData == null) return + walletStateWatcher.invoke(state) - - showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) - setupCurrency(selectedWallet.currencyData, selectedWallet.currency) - setupAddressCard(selectedWallet) - setupNoInternetHandling(state) - setupBalanceData(selectedWallet.currencyData) - setupButtons(selectedWallet, state.isExchangeServiceFeatureOn) - - handleCurrencyIcon(selectedWallet) - handleWarnings(selectedWallet) updateViewMeasurements() - - binding.srlWalletDetails.setOnRefreshListener { - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - Analytics.send(Token.Refreshed()) - val blockchainNetwork = BlockchainNetwork( - blockchain = selectedWallet.currency.blockchain, - derivationPath = selectedWallet.currency.derivationPath, - tokens = emptyList(), - ) - val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - walletCurrenciesManager.update(selectedUserWallet, blockchainNetwork) - } else { - store.dispatch( - WalletAction.LoadWallet( - blockchain = BlockchainNetwork( - selectedWallet.currency.blockchain, - selectedWallet.currency.derivationPath, - emptyList(), - ), - ), - ) - store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(selectedWallet.currency))) - } - } - } - - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - binding.srlWalletDetails.isRefreshing = false - } } private fun updateViewMeasurements() { @@ -203,6 +224,37 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } + private fun setupSwipeRefresh(currencyData: BalanceWidgetData, currency: Currency) { + binding.srlWalletDetails.setOnRefreshListener { + if (currencyData.status != BalanceStatus.Loading && currencyData.status != BalanceStatus.Refreshing) { + Analytics.send(Token.Refreshed()) + lifecycleScope.launch(Dispatchers.Default) { + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync + if (selectedUserWallet != null) { + walletCurrenciesManager.update(selectedUserWallet, currency) + .doOnResult { + withContext(Dispatchers.Main) { + binding.srlWalletDetails.isRefreshing = false + } + } + } else { + val blockchainNetwork = BlockchainNetwork( + blockchain = currency.blockchain, + derivationPath = currency.derivationPath, + tokens = emptyList(), + ) + + store.dispatch(WalletAction.LoadWallet(blockchainNetwork)) + store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(currency))) + } + } + } + } + + binding.srlWalletDetails.isRefreshing = currencyData.status == BalanceStatus.Loading || + currencyData.status == BalanceStatus.Refreshing + } + private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) { lWalletDetails.btnCopy.setOnClickListener { selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString -> @@ -231,9 +283,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), rvWarningMessages.show(warningDetails.isNotEmpty()) } - private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) { + private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) { ivCurrency.load( - currency = wallet.currency, + currency = currency, derivationStyle = store.state.globalState .scanResponse ?.card @@ -281,9 +333,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } - private fun setupNoInternetHandling(state: WalletState) { - if (state.state == ProgressState.Error) { - if (state.error == ErrorType.NoInternetConnection) { + private fun setupNoInternetHandling(progressState: ProgressState, errorType: ErrorType?) { + if (progressState == ProgressState.Error) { + if (errorType == ErrorType.NoInternetConnection) { binding.srlWalletDetails.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( text = R.string.wallet_notification_no_internet, @@ -350,7 +402,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_remove -> { - store.state.walletState.getSelectedWalletData()?.let { walletData -> + store.state.walletState.selectedWalletData?.let { walletData -> store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency)) true } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 962c7cd390..c0bde194bf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -15,6 +15,7 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -80,15 +81,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber state.select { it.walletState } } walletView.setFragment(this, binding) - viewModel.launch() } override fun onStop() { @@ -97,6 +101,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(D BalanceStatus.Unreachable -> { root.getString(R.string.wallet_balance_blockchain_unreachable) } + BalanceStatus.MissedDerivation -> { + root.getString(R.string.wallet_balance_missing_derivation) + } else -> null } @@ -95,7 +98,7 @@ class WalletAdapter : ListAdapter(D if (wallet.walletAddresses != null) { cardWallet.setOnClickListener { Analytics.send(Portfolio.TokenTapped()) - store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency)) } } else { cardWallet.setOnClickListener(null) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt index 4f384d33f7..857cdd2631 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt @@ -156,7 +156,7 @@ private class SolanaRentWarningActionEmitter { } private fun getBlockchainNetwork(): BlockchainNetwork { - val currency = store.state.walletState.getSelectedWalletData()!!.currency + val currency = store.state.walletState.selectedWalletData!!.currency return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 1ceda1b7b4..f7c8dc8a66 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -3,6 +3,8 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.badoo.mvicore.DiffStrategy +import com.badoo.mvicore.modelWatcher import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics @@ -33,6 +35,45 @@ class MultiWalletView : WalletView() { private lateinit var walletsAdapter: WalletAdapter + private val watcher = modelWatcher { + val totalBalanceStrategy: DiffStrategy = { old, new -> + old.cardId != new.cardId || + old.totalBalance != new.totalBalance || + old.state != new.state || + old.walletsStores.size != new.walletsStores.size + } + + // !!! Workaround !!! + // Checking state properties instead of state params can reduce application performance, + // but here it is necessary because the WalletStore has an unsuitable equals method + WalletState::walletsDataFromStores { + walletsAdapter.submitList(it) + } + WalletState::loadingUserTokens { + binding?.pbLoadingUserTokens?.show(it) + } + WalletState::walletCardsCount { walletCardsCount -> + binding?.let { + setupWalletCardNumber(it, walletCardsCount) + } + } + WalletState::missingDerivations { missingDerivations -> + binding?.let { + handleRescanWarning(it, missingDerivations.isNotEmpty()) + } + } + WalletState::showBackupWarning { showBackupWarnings -> + binding?.let { + handleBackupWarning(it, showBackupWarnings) + } + } + watch({ it }, totalBalanceStrategy) { walletState -> + binding?.let { + handleTotalBalance(it, walletState.totalBalance, walletState.state, walletState.walletsDataFromStores.size) + } + } + } + override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { setFragment(fragment, binding) onViewCreated() @@ -40,6 +81,7 @@ class MultiWalletView : WalletView() { } private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) { + watcher.clear() tvTwinCardNumber.hide() rvPendingTransaction.hide() lCardBalance.root.hide() @@ -68,13 +110,7 @@ class MultiWalletView : WalletView() { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size) - handleBackupWarning(binding, state.showBackupWarning) - handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) - setupWalletCardNumber(binding, state.walletCardsCount) - walletsAdapter.submitList(state.walletsData) - - binding.pbLoadingUserTokens.show(state.loadingUserTokens) + watcher.invoke(state) binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card @@ -91,7 +127,7 @@ class MultiWalletView : WalletView() { store.dispatch(TokensAction.AllowToAddTokens(true)) store.dispatch( TokensAction.SetAddedCurrencies( - wallets = state.walletsData, + wallets = state.walletsDataFromStores, derivationStyle = card.derivationStyle, ), ) @@ -139,21 +175,26 @@ class MultiWalletView : WalletView() { walletsCount: Int, ) = with(binding.lCardTotalBalance) { if (walletsCount == 0) { - root.isVisible = false + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { if (totalBalance == null) { - veilBalance.animateVisibility(show = true) - root.isVisible = progressState == ProgressState.Loading + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { root.isVisible = true - // Skip changes when on refreshing state if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { return@with } - veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) - tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + if (totalBalance.state == ProgressState.Loading) { + veilBalance.veil() + } else { + veilBalance.unVeil() + } tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( @@ -218,6 +259,11 @@ class MultiWalletView : WalletView() { rowButtons.btnSend.text = fragment?.getText(R.string.wallet_button_create_wallet) rowButtons.onSendClick = { store.dispatch(WalletAction.CreateWallet) } } + + override fun onDestroyFragment() { + super.onDestroyFragment() + watcher.clear() + } } private val WalletDetailsButtonsRow.btnBuy: Button diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index e765e738e8..44ff7329fc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -52,4 +52,4 @@ class SaltPaySingleWalletView : WalletView() { ), ).setup() } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index abca20330d..e8fc90a242 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -153,14 +153,12 @@ class SingleWalletView : WalletView() { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = - MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = - MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt index a3b09aab12..d7d7cbb405 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt @@ -17,6 +17,8 @@ abstract class WalletView { binding = null } + open fun onDestroyFragment() {} + abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) abstract fun onViewCreated() abstract fun onNewState(state: WalletState) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index 1417914782..b381b423c3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -1,9 +1,10 @@ package com.tangem.tap.features.walletSelector.redux +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.TotalFiatBalance data class UserWalletModel( - val id: String, + val id: UserWalletId, val name: String, val artworkUrl: String, val type: Type, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 957661126f..0985f4b68c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -34,20 +34,16 @@ internal sealed interface WalletSelectorAction : Action { } data class SelectWallet( - val walletId: String, - ) : WalletSelectorAction - - data class UnlockWalletWithCard( - val walletId: String, + val userWalletId: UserWalletId, ) : WalletSelectorAction data class RenameWallet( - val walletId: String, + val userWalletId: UserWalletId, val newName: String, ) : WalletSelectorAction data class RemoveWallets( - val walletIdsToRemove: List, + val userWalletsIds: List, ) : WalletSelectorAction object AddWallet : WalletSelectorAction { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index dd4c4ff88b..b6b31419de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,13 +19,16 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor -import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -59,18 +62,17 @@ internal class WalletSelectorMiddleware { addWallet() } is WalletSelectorAction.SelectWallet -> { - selectWallet(action.walletId) - } - is WalletSelectorAction.UnlockWalletWithCard -> { - unlockWalletWithCard(action.walletId) + selectWallet(action.userWalletId) } is WalletSelectorAction.RemoveWallets -> { - removeWallets(action.walletIdsToRemove, state) + deleteWallets(action.userWalletsIds, state) } is WalletSelectorAction.RenameWallet -> { - renameWallet(action.walletId, action.newName) + renameWallet(action.userWalletId, action.newName) + } + is WalletSelectorAction.ChangeAppCurrency -> { + refreshUserWalletsAmounts() } - is WalletSelectorAction.ChangeAppCurrency, is WalletSelectorAction.AddWallet.Success, is WalletSelectorAction.AddWallet.Error, is WalletSelectorAction.SelectedWalletChanged, @@ -96,13 +98,17 @@ internal class WalletSelectorMiddleware { private fun updateBalances(walletStores: Map>, state: WalletSelectorState) { walletStores.forEach { (walletId, walletStores) -> - scope.launch { - val updatedWallet = state.wallets - .find { it.id == walletId.stringValue } - ?.updateWalletStoresAndCalculateFiatBalance(walletStores) + scope.launch(Dispatchers.Default) { + val foundWallet = state.wallets + .find { it.id == walletId } - if (updatedWallet != null) { - store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + if (foundWallet != null) { + val updatedWallet = foundWallet + .updateWalletStoresAndCalculateFiatBalance(walletStores) + + if (foundWallet != updatedWallet) { + store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + } } } } @@ -135,45 +141,38 @@ internal class WalletSelectorMiddleware { .doOnSuccess { Analytics.send(MyWallets.CardWasScanned) - userWalletsListManager.selectWallet(userWallet.walletId) store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - updateAccessCodeRequestPolicy(userWallet) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(userWallet) } } } - private fun selectWallet(id: String) { + private fun selectWallet(userWalletId: UserWalletId) { scope.launch { - userWalletsListManager.selectWallet(UserWalletId(id)) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) - } - .doOnSuccess { selectedWallet -> - updateAccessCodeRequestPolicy(selectedWallet) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun unlockWalletWithCard(id: String) { - scope.launch { - userWalletsListManager.get(UserWalletId(id)) + userWalletsListManager.get(userWalletId) .flatMap { userWallet -> - updateUserWalletWithScannedCard(userWallet) - } - .flatMap { updatedUserWallet -> - unlockUserWallet(updatedUserWallet) + if (userWallet.isLocked) { + unlockUserWalletWithScannedCard(userWallet) + } else { + userWalletsListManager.selectWallet(userWalletId) + } } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } + .doOnSuccess { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) + } + } } } - private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) return tangemSdkManager.scanCard(userWallet.cardId) .map { scannedCard -> userWallet.copy( @@ -182,23 +181,26 @@ internal class WalletSelectorMiddleware { ), ) } - } - - private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult { - return userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) + .flatMap { updatedUserWallet -> + userWalletsListManager.save(updatedUserWallet, canOverride = true) + } + .doOnFailure { + tangemSdkManager.changeDisplayedCardIdNumbersCount( + scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse, + ) } } - private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + private fun deleteWallets(userWalletsIds: List, state: WalletSelectorState) { Analytics.send(MyWallets.Button.DeleteWalletTapped) scope.launch { - when (walletIdsToRemove.size) { + when (userWalletsIds.size) { state.wallets.size -> clearUserWallets() - else -> removeUserWallets(walletIdsToRemove, state) + else -> deleteUserWallets( + userWalletsIds = userWalletsIds, + currentSelectedWalletId = state.selectedWalletId, + ) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) @@ -206,11 +208,11 @@ internal class WalletSelectorMiddleware { } } - private fun renameWallet(walletId: String, newName: String) { + private fun renameWallet(userWalletId: UserWalletId, newName: String) { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(walletId = UserWalletId(walletId)) + userWalletsListManager.get(userWalletId) .map { it.copy(name = newName) } .flatMap { userWalletsListManager.save(it, canOverride = true) } .doOnFailure { error -> @@ -219,6 +221,50 @@ internal class WalletSelectorMiddleware { } } + private fun refreshUserWalletsAmounts() { + scope.launch { + walletStoresManager.updateAmounts( + userWallets = userWalletsListManager.userWallets.first(), + ) + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend fun clearUserWallets(): CompletionResult { + return userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .flatMap { tangemSdkManager.clearSavedUserCodes() } + .doOnSuccess { + // !!! Workaround !!! + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + delay(timeMillis = 280) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + private suspend fun deleteUserWallets( + userWalletsIds: List, + currentSelectedWalletId: UserWalletId?, + ): CompletionResult { + return userWalletsListManager.delete(userWalletsIds) + .flatMap { walletStoresManager.delete(userWalletsIds) } + .flatMap { deleteAccessCodes(userWalletsIds) } + .doOnSuccess { + val selectedWallet = userWalletsListManager.selectedUserWalletSync + + when { + selectedWallet == null -> { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } + currentSelectedWalletId != selectedWallet.walletId -> { + store.onUserWalletSelected(selectedWallet) + } + } + } + } + private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { @@ -236,37 +282,13 @@ internal class WalletSelectorMiddleware { ) } - private suspend fun clearUserWallets(): CompletionResult { - return userWalletsListManager.clear() - .flatMap { walletStoresManager.clear() } - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) - } - } + private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { + val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() + .asSequence() + .filter { it.walletId in userWalletsIds } + .flatMap { it.cardsInWallet } - private suspend fun removeUserWallets( - walletIdsToRemove: List, - state: WalletSelectorState, - ): CompletionResult { - val prevSelectedWalletId = state.selectedWalletId - return userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) - .flatMap { walletStoresManager.delete(walletIdsToRemove) } - .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync ?: return@doOnSuccess - val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue - - if (isSelectedWalletRemoved) { - updateAccessCodeRequestPolicy(selectedWallet) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) + return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 959c45c8d9..b64fe0a3ce 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -1,9 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.domain.common.CardDTO -import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import org.rekotlin.Action @@ -21,7 +19,7 @@ internal object WalletSelectorReducer { wallets = action.userWallets.updateWalletsModels(state.wallets), ) is WalletSelectorAction.SelectedWalletChanged -> state.copy( - selectedWalletId = action.selectedWallet.walletId.stringValue, + selectedWalletId = action.selectedWallet.walletId, ) is WalletSelectorAction.IsLockedChanged -> state.copy( isLocked = action.isLocked, @@ -56,7 +54,6 @@ internal object WalletSelectorReducer { ) is WalletSelectorAction.WalletStoresChanged, is WalletSelectorAction.SelectWallet, - is WalletSelectorAction.UnlockWalletWithCard, is WalletSelectorAction.RemoveWallets, is WalletSelectorAction.RenameWallet, -> state @@ -66,7 +63,7 @@ internal object WalletSelectorReducer { private fun List.updateWalletsModels(prevWallets: List): List { return this.map { userWallet -> prevWallets - .find { it.id == userWallet.walletId.stringValue } + .find { it.id == userWallet.walletId } ?.let { it.copy( name = userWallet.name, @@ -77,7 +74,7 @@ internal object WalletSelectorReducer { } ?: with(userWallet) { UserWalletModel( - id = walletId.stringValue, + id = walletId, name = name, artworkUrl = artworkUrl, type = getType(), @@ -92,12 +89,17 @@ internal object WalletSelectorReducer { userWalletModel: UserWalletModel, ): List { return ArrayList(this).apply { - replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id } + val index = indexOfFirst { it.id == userWalletModel.id } + if (index == -1) { + add(userWalletModel) + } else { + this[index] = userWalletModel + } } } private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { - return if (scanResponse.card.isMultiwalletAllowed) { + return if (isMultiCurrency) { UserWalletModel.Type.MultiCurrency( cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt index df6ec0867d..797cc04593 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt @@ -1,12 +1,13 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import org.rekotlin.StateType data class WalletSelectorState( val wallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedWalletId: UserWalletId? = null, val isLocked: Boolean = false, val fiatCurrency: FiatCurrency = FiatCurrency.Default, val isCardSavingInProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index 6694b8b663..e5dc088a55 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -31,8 +31,8 @@ internal fun WalletSelectorScreenState.updateWithNewState( return this.copy( multiCurrencyWallets = multiCurrencyWallets, singleCurrencyWallets = singleCurrencyWallets, - selectedWalletId = newState.selectedWalletId, - editingWalletsIds = editingWalletsIds.filter { it in walletsIds }, + selectedUserWalletId = newState.selectedWalletId, + editingUserWalletsIds = editingUserWalletsIds.filter { it in walletsIds }, isLocked = newState.isLocked, showUnlockProgress = newState.isUnlockInProgress, showAddCardProgress = newState.isCardSavingInProgress, @@ -70,7 +70,7 @@ private fun List.toUiModels( imageUrl = artworkUrl, balance = balance, isLocked = isLocked, - tokenName = type.blockchainName ?: "—", + tokenName = type.blockchainName, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 717f82ed16..4db33980de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -7,7 +7,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -39,18 +45,12 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment = emptyList(), val singleCurrencyWallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = false, - val editingWalletsIds: List = listOf(), + val editingUserWalletsIds: List = listOf(), val renameWalletDialog: RenameWalletDialog? = null, val showAddCardProgress: Boolean = false, val showUnlockProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index a7eaa3c4f4..1192b37c4a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.walletSelector.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.features.walletSelector.redux.WalletSelectorState @@ -35,40 +36,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { - store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId)) + editingUserWalletsIds.isNotEmpty() && isWalletLocked(userWalletId, this) -> Unit + editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { + editWallet(userWalletId) } - editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { - editWallet(walletId) + editingUserWalletsIds.isNotEmpty() && editingUserWalletsIds.contains(userWalletId) -> { + cancelWalletEditing(userWalletId) } - editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> { - cancelWalletEditing(walletId) - } - selectedWalletId != walletId -> { - store.dispatch(WalletSelectorAction.SelectWallet(walletId)) + selectedUserWalletId != userWalletId -> { + store.dispatch(WalletSelectorAction.SelectWallet(userWalletId)) } } } - fun walletLongClicked(walletId: String) = with(state.value) { - if (!isLocked && editingWalletsIds.isEmpty()) { - editWallet(walletId) + fun walletLongClicked(userWalletId: UserWalletId) = with(state.value) { + if (!isWalletLocked(userWalletId, this) && editingUserWalletsIds.isEmpty()) { + editWallet(userWalletId) } } fun cancelWalletsEditing() { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } } fun renameWallet() = with(state.value) { - if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) { - val editedWalletId = editingWalletsIds.first() + if (editingUserWalletsIds.isNotEmpty() && renameWalletDialog == null) { + val editedWalletId = editingUserWalletsIds.first() val editedWallet = (multiCurrencyWallets + singleCurrencyWallets) .find { it.id == editedWalletId } @@ -80,7 +79,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( renameWalletDialog = null, - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } }, @@ -103,8 +102,8 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( - editingWalletsIds = prevState.editingWalletsIds + walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds + userWalletId, ) } } - private fun cancelWalletEditing(walletId: String) { + private fun cancelWalletEditing(userWalletId: UserWalletId) { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = prevState.editingWalletsIds - walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds - userWalletId, ) } } + private fun isWalletLocked(userWalletId: UserWalletId, state: WalletSelectorScreenState): Boolean = with(state) { + multiCurrencyWallets.find { it.id == userWalletId }?.isLocked + ?: singleCurrencyWallets.find { it.id == userWalletId }?.isLocked ?: isLocked + } + private fun subscribeToStoreChanges() { store.subscribe(this) { appState -> appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 2fab91adc1..65349386dc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.walletSelector.ui.components +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -7,7 +8,7 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem internal object MockData { private val multiCurrencyUserWallet = MultiCurrencyUserWalletItem( - id = "wallet_1", + id = UserWalletId("wallet_1"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -20,7 +21,7 @@ internal object MockData { ) private val singleCurrencyUserWallet = SingleCurrencyUserWalletItem( - id = "wallet_4", + id = UserWalletId("wallet_4"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -34,11 +35,11 @@ internal object MockData { val state = WalletSelectorScreenState( multiCurrencyWallets = listOf( multiCurrencyUserWallet, - multiCurrencyUserWallet.copy(id = "wallet_2"), - multiCurrencyUserWallet.copy(id = "wallet_3", tokensCount = 2, cardsInWallet = 1), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_2")), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_3"), tokensCount = 2, cardsInWallet = 1), ), singleCurrencyWallets = listOf(singleCurrencyUserWallet), - selectedWalletId = multiCurrencyUserWallet.id, + selectedUserWalletId = multiCurrencyUserWallet.id, isLocked = false, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 6c57a43f00..7897e34ae6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text @@ -23,61 +23,84 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButtonIconRight -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState -import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletSelectorScreenContent( - modifier: Modifier = Modifier, state: WalletSelectorScreenState, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, onUnlockClick: () -> Unit, onAddCardClick: () -> Unit, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { - Column(modifier = modifier) { - Header( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - editingWalletsIds = state.editingWalletsIds, - onClearSelectedClick = onClearSelectedClick, - onEditSelectedWalletClick = onEditSelectedWalletClick, - onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, - ) - Column( - modifier = Modifier - .verticalScroll( - state = rememberScrollState(), - ), - ) { - WalletsList( - multiCurrencyWallets = state.multiCurrencyWallets, - singleCurrencyWallets = state.singleCurrencyWallets, - selectedWalletId = state.selectedWalletId, - checkedWalletIds = state.editingWalletsIds, + LazyColumn { + stickyHeader { + Header( + editingWalletsIds = state.editingUserWalletsIds, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_multi_header, wallets = state.multiCurrencyWallets) + } + + itemsIndexed( + items = state.multiCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, onWalletClick = onWalletClick, onWalletLongClick = onWalletLongClick, ) - SpacerH24() + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_single_header, wallets = state.singleCurrencyWallets) + } + + itemsIndexed( + items = state.singleCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, + onWalletClick = onWalletClick, + onWalletLongClick = onWalletLongClick, + ) + } + + item { Footer( modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), isLocked = state.isLocked, @@ -86,25 +109,28 @@ internal fun WalletSelectorScreenContent( onUnlockClick = onUnlockClick, onAddCardClick = onAddCardClick, ) - SpacerH16() } } } @Composable private fun Header( - modifier: Modifier = Modifier, - editingWalletsIds: List, + editingWalletsIds: List, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size) - val hasEditingWallets by remember { - derivedStateOf { editingWalletsSize > 0 } - } + val hasEditingWallets by remember { derivedStateOf { editingWalletsSize > 0 } } - Column(modifier = modifier) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + ) { Hand() Box( modifier = Modifier @@ -114,7 +140,9 @@ private fun Header( ) { if (hasEditingWallets) { EditWalletsBar( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), editingWalletsSize = editingWalletsSize, onClearSelectedClick = onClearSelectedClick, onEditSelectedWalletClick = onEditSelectedWalletClick, @@ -132,44 +160,15 @@ private fun Header( } } -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun WalletsList( - modifier: Modifier = Modifier, - multiCurrencyWallets: List, - singleCurrencyWallets: List, - selectedWalletId: String?, - checkedWalletIds: List, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, -) { - Column(modifier = modifier) { - val walletsSection = @Composable { wallets: List -> - wallets.forEachIndexed { index, wallet -> - if (index == 0) { - Text( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), - text = wallet.headerText.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - } - WalletItem( - modifier = Modifier - .combinedClickable( - onClick = { onWalletClick(wallet.id) }, - onLongClick = { onWalletLongClick(wallet.id) }, - ) - .padding(all = TangemTheme.dimens.spacing16), - wallet = wallet, - isSelected = wallet.id == selectedWalletId, - isChecked = wallet.id in checkedWalletIds, - ) - } - } - - walletsSection(multiCurrencyWallets) - walletsSection(singleCurrencyWallets) +private fun WalletsTitle(@StringRes textResId: Int, wallets: List<*>) { + if (wallets.isNotEmpty()) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = stringResource(id = textResId), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) } } @@ -278,11 +277,6 @@ private fun WalletSelectorScreenContentSample( .background(color = TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state.copy(isLocked = true), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, @@ -320,13 +314,8 @@ private fun WalletSelectorScreenContent_EditWallets_Sample( .background(TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state - .copy(editingWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), + .copy(editingUserWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, onUnlockClick = { /* no-op */ }, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 8ad4d7a2fc..3b6f021dba 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon @@ -26,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH2 import com.tangem.core.ui.components.SpacerW6 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -33,15 +36,23 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R import com.valentinilk.shimmer.shimmer +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletItem( - modifier: Modifier = Modifier, wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, ) { Row( - modifier = modifier, + modifier = Modifier + .combinedClickable( + onClick = { onWalletClick(wallet.id) }, + onLongClick = { onWalletLongClick(wallet.id) }, + ) + .height(72.dp) + .padding(all = TangemTheme.dimens.spacing16), verticalAlignment = Alignment.CenterVertically, ) { WalletCardImage( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index c546cd131e..ddd7518ca6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -1,21 +1,14 @@ package com.tangem.tap.features.walletSelector.ui.model -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.wallet.R +import com.tangem.domain.common.util.UserWalletId internal sealed interface UserWalletItem { - val id: String + val id: UserWalletId val name: String val imageUrl: String val balance: Balance val isLocked: Boolean - val headerText: TextReference - get() = when (this) { - is MultiCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_multi_header) - is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) - } - data class Balance( val amount: String, val isLoading: Boolean, @@ -23,7 +16,7 @@ internal sealed interface UserWalletItem { } internal data class MultiCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, @@ -33,7 +26,7 @@ internal data class MultiCurrencyUserWalletItem( ) : UserWalletItem internal data class SingleCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index fb5b8a626d..7b0f37e36e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -59,11 +59,6 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedUserWallet.hasAccessCode, - ) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) @@ -78,8 +73,7 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) + userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } @@ -100,6 +94,9 @@ internal class WelcomeMiddleware { private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) ScanCardProcessor.scan( onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index eaf21b8d83..d419aafffc 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -51,6 +51,18 @@ class PreferencesStorage(applicationContext: Application) { putBoolean(SAVE_ACCESS_CODES_KEY, value) } + var wasApplicationStopped: Boolean + get() = preferences.getBoolean(APPLICATION_STOPPED_KEY, false) + set(value) = preferences.edit { + putBoolean(APPLICATION_STOPPED_KEY, value) + } + + var shouldOpenWelcomeScreenOnResume: Boolean + get() = preferences.getBoolean(OPEN_WELCOME_ON_RESUME_KEY, false) + set(value) = preferences.edit { + putBoolean(OPEN_WELCOME_ON_RESUME_KEY, value) + } + fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) fun saveTwinsOnboardingShown() { @@ -74,6 +86,8 @@ class PreferencesStorage(applicationContext: Application) { private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" private const val SAVE_USER_WALLETS_KEY = "saveUserWallets" private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes" + private const val APPLICATION_STOPPED_KEY = "applicationStopped" + private const val OPEN_WELCOME_ON_RESUME_KEY = "openWelcomeOnResume" } } diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 2b4d40e0bb..7be322fc1e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -108,7 +108,7 @@ class DerivationManagerImpl( val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) } diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index ab388fa0cd..b3d1b54730 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 1467361b47..f1cdf9a73a 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> @@ -105,27 +105,27 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + android:id="@+id/barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="bottom" + app:constraint_referenced_ids="l_wallet_details" /> + android:id="@+id/row_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="28dp" + android:layout_marginBottom="32dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + app:layout_constraintVertical_bias="1" + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/item_currency_wallet_content.xml b/app/src/main/res/layout/item_currency_wallet_content.xml index 18249f4c63..aa767d069c 100644 --- a/app/src/main/res/layout/item_currency_wallet_content.xml +++ b/app/src/main/res/layout/item_currency_wallet_content.xml @@ -14,77 +14,77 @@ app:layout_constraintGuide_percent="0.5" /> + android:id="@+id/tv_currency" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toStartOf="@+id/tv_amount_fiat" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="Binance Smart Chain Optimal" /> + android:id="@+id/tv_amount_fiat" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="1230.43 $" /> + android:id="@+id/tv_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="2.002134 BTC" /> + android:id="@+id/tv_status" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="Unreachable..." /> + android:id="@+id/tv_exchange_rate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textColor="@color/text_secondary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="46 908 $" /> diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml index 3f47e6b612..ed26db5407 100644 --- a/app/src/main/res/layout/layout_card_total_balance.xml +++ b/app/src/main/res/layout/layout_card_total_balance.xml @@ -12,8 +12,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="18dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="18dp" - android:animateLayoutChanges="true"> + android:layout_marginBottom="18dp"> - + app:layout_constraintTop_toBottomOf="@id/tv_title" + app:veilLayout_baseColor="@color/lightGray0" + app:veilLayout_highlightColor="@color/lightGray1" + app:veilLayout_layout="@layout/card_total_balance_shimmer" + app:veilLayout_radius="4dp" + app:veilLayout_shimmerEnable="true" + app:veilLayout_veiled="true"> - - - - + tools:text="22 325.40 $" + tools:visibility="visible" /> + diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index c1375a7b76..54ee97cd6d 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -66,6 +66,7 @@ object Library { const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode + const val mviCoreWatcher = "com.github.badoo.mvicore:mvicore-diff:" + Versions.mviCore } object Tangem { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index e5985c63df..869eaf8f0b 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -55,12 +55,13 @@ object Versions { const val zendeskMessaging = "5.2.4" const val zxingQrBarcodeScanner = "1.9.8" const val zxingQrCode = "3.3.3" + const val mviCore = "1.3.1" // endregion Other libraries // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-173" + const val tangemCardSgk = "develop-178" // endregion Tangem // region Testing diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index bb7027a32f..046904ddaf 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index bacd616a66..4b3d386d9b 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index 34a7c383db..53bd5f260c 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index 14e74e4d88..c56fea1dcc 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -229,4 +229,5 @@ биометрией Включите биометрическую аутентификацию Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index d41e3ca81f..673ced5d90 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt index 2a7d4c0864..35dab6bca0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt @@ -18,7 +18,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.tangem.core.ui.R -import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { @@ -52,13 +51,6 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme return ComposeView(context).apply { setContent { TangemTheme { - val backgroundColor = TangemTheme.colors.background.plain - SystemBarsEffect { - setSystemBarsColor( - color = backgroundColor, - ) - } - ScreenContent( modifier = Modifier .fillMaxWidth() @@ -66,7 +58,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it } .background( - color = backgroundColor, + color = TangemTheme.colors.background.plain, shape = TangemTheme.shapes.bottomSheet, ), state = provideState().value, diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt new file mode 100644 index 0000000000..0de62569bb --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.fragments + +import android.view.WindowManager +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment + +fun Fragment.setStatusBarColor(@ColorRes colorResId: Int) { + with(requireActivity().window) { + clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + statusBarColor = ContextCompat.getColor(requireContext(), colorResId) + } +} \ No newline at end of file From 523f8975e37670b783126d980cf46858b209ebf0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 00:16:37 +0400 Subject: [PATCH 02/42] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/shop/TangemShopService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index c8bd0a5405..94c37e96d9 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -204,8 +204,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { } companion object { - const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2" - const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3" + const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S" + const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S" val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU) } } From 210c83d5d5ef96e399dfd45bff9a079634665e8b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 14:11:02 +0300 Subject: [PATCH 03/42] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 5 ++ .../redux/WalletSelectorMiddleware.kt | 68 +++++++++++-------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 01350c6c63..be8e4f65b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -234,6 +234,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + fun useBiometricsForAccessCode(): Boolean { + val policy = tangemSdk.config.userCodeRequestPolicy + return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index b6b31419de..c7d8495f6a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,6 +19,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -131,21 +132,45 @@ internal class WalletSelectorMiddleware { private fun addWallet() = scope.launch { Analytics.send(MyWallets.Button.ScanNewCard) - scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - .doOnSuccess { - Analytics.send(MyWallets.CardWasScanned) + // Update access code policy for access code saving when a card was scanned + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) + ScanCardProcessor.scan( + onSuccess = { scanResponse -> + saveUserWalletAndPopBackToWalletScreen(scanResponse) + .doOnFailure { error -> + // Rollback policy if card saving was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + }, + onFailure = { error -> + // Rollback policy if card scanning was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + }, + onWalletNotCreated = { + // No need to rollback policy, continue with the policy set before the card scan + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + }, + ) + } - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } - } + private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { + val userWallet = UserWalletBuilder(scanResponse).build() + + return userWalletsListManager.save(userWallet) + .doOnSuccess { + Analytics.send(MyWallets.CardWasScanned) + + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } } private fun selectWallet(userWalletId: UserWalletId) { @@ -265,23 +290,6 @@ internal class WalletSelectorMiddleware { } } - private suspend inline fun scanCardInternal( - crossinline onCardScanned: suspend (ScanResponse) -> Unit, - ) { - ScanCardProcessor.scan( - onSuccess = { - onCardScanned(it) - }, - onFailure = { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - }, - onWalletNotCreated = { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - }, - ) - } - private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() .asSequence() From f427c4508f92c774cab80acf9b2691742a0f6d26 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 15:07:51 +0300 Subject: [PATCH 04/42] Updated on 2026-08-14 --- .../tap/domain/tasks/product/ScanProductTask.kt | 16 ++++++++++------ .../tap/features/onboarding/OnboardingHelper.kt | 3 +++ .../com/tangem/domain/common/ScanResponse.kt | 5 ++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 252d3585e6..d028eb9a9e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -18,6 +18,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation @@ -73,7 +74,7 @@ class ScanProductTask( // it need because processorResult.data.card doesn't contains attestation result // and CardWallet.derivedKeys val processorScanResponseWithNewCard = processorResult.data.copy( - card = scanTaskResult.data + card = scanTaskResult.data, ) callback(CompletionResult.Success(processorScanResponseWithNewCard)) } @@ -96,7 +97,7 @@ private class ScanNoteProcessor : ProductCommandProcessor { override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit, ) { callback( CompletionResult.Success( @@ -145,7 +146,7 @@ private class ScanWalletProcessor( is CompletionResult.Success -> { PreflightReadTask( readMode = PreflightReadMode.FullCardRead, - cardId = card.cardId + cardId = card.cardId, ).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> startLinkingForBackupIfNeeded(card, session, callback) @@ -157,6 +158,7 @@ private class ScanWalletProcessor( } } } + private fun startLinkingForBackupIfNeeded( card: Card, session: CardSession, @@ -180,13 +182,15 @@ private class ScanWalletProcessor( deriveKeysIfNeeded(card, session, callback) } } + private fun deriveKeysIfNeeded( card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val productType = when (card.isSaltPay) { - true -> ProductType.SaltPay + val productType = when { + card.isSaltPay -> ProductType.SaltPay + card.isStart2Coin -> ProductType.Start2Coin else -> ProductType.Wallet } scope.launch { @@ -251,7 +255,7 @@ private class ScanWalletProcessor( Blockchain.RSK, Blockchain.Fantom, Blockchain.FantomTestnet, Blockchain.Avalanche, Blockchain.AvalancheTestnet, - ).map { BlockchainNetwork(it, card) } + ).map { BlockchainNetwork(it, card) }, ) } return blockchainsToDerive.distinct() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index f2227fcc32..85a26859f5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -37,6 +37,9 @@ class OnboardingHelper { } ProductType.Twins -> AppScreen.OnboardingTwins ProductType.SaltPay -> AppScreen.OnboardingWallet + ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( + "Onboarding for Start2Coin cards is not supported" + ) } } } diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index 56c6979d5e..ea2c525405 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -12,6 +12,7 @@ import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isSaltPayVisa import com.tangem.domain.common.TapWorkarounds.isSaltPayWallet +import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.isTestCard @@ -32,6 +33,7 @@ data class ScanResponse( ) : CommandResponse { fun getBlockchain(): Blockchain { return when (productType) { + ProductType.Start2Coin -> if (card.isTestCard) Blockchain.BitcoinTestnet else Blockchain.Bitcoin ProductType.SaltPay -> if (card.isTestCard) Blockchain.SaltPayTestnet else Blockchain.SaltPay ProductType.Note -> card.getTangemNoteBlockchain() ?: Blockchain.Unknown else -> { @@ -90,7 +92,7 @@ data class ScanResponse( } enum class ProductType { - Note, Twins, Wallet, SaltPay + Note, Twins, Wallet, SaltPay, Start2Coin } val Card.productType: ProductType @@ -98,6 +100,7 @@ val Card.productType: ProductType isTangemTwins -> ProductType.Twins isTangemNote -> ProductType.Note isSaltPay -> ProductType.SaltPay + isStart2Coin -> ProductType.Start2Coin else -> ProductType.Wallet } From 18ac1924769d00ab2c7e814a618fc31aeaabda0b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 16:42:58 +0300 Subject: [PATCH 05/42] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../configurable/config/ConfigManager.kt | 30 ++++++++++++++----- .../domain/configurable/config/JsonModels.kt | 4 ++- dependencies.gradle | 2 +- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a1658496e7..8453faa078 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 +Subproject commit 8453faa078220801858166081947972639f166db diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index bd530b0796..7db1d56007 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -1,6 +1,8 @@ package com.tangem.tap.domain.configurable.config import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.BlockchairCredentials +import com.tangem.blockchain.common.QuickNodeBscCredentials import com.tangem.tap.common.shop.shopify.ShopifyShop import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.domain.configurable.Loader @@ -66,12 +68,12 @@ class ConfigManager { config = config.copy( isTopUpEnabled = model.isTopUpEnabled, isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed + isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, ) defaultConfig = defaultConfig.copy( isTopUpEnabled = model.isTopUpEnabled, isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed + isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, ) } @@ -84,11 +86,17 @@ class ConfigManager { mercuryoWidgetId = values.mercuryoWidgetId, mercuryoSecret = values.mercuryoSecret, blockchainSdkConfig = BlockchainSdkConfig( - blockchairApiKey = values.blockchairApiKey, - blockchairAuthorizationToken = values.blockchairAuthorizationToken, + blockchairCredentials = BlockchairCredentials( + apiKey = values.blockchairApiKeys, + authToken = values.blockchairAuthorizationToken, + ), blockcypherTokens = values.blockcypherTokens, + quickNodeBscCredentials = QuickNodeBscCredentials( + apiKey = values.quiknodeApiKey, + subdomain = values.quiknodeSubdomain, + ), infuraProjectId = values.infuraProjectId, - tronGridApiKey = values.tronGridApiKey + tronGridApiKey = values.tronGridApiKey, ), appsFlyerDevKey = values.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, @@ -103,10 +111,16 @@ class ConfigManager { mercuryoWidgetId = values.mercuryoWidgetId, mercuryoSecret = values.mercuryoSecret, blockchainSdkConfig = BlockchainSdkConfig( - blockchairApiKey = values.blockchairApiKey, - blockchairAuthorizationToken = values.blockchairAuthorizationToken, + blockchairCredentials = BlockchairCredentials( + apiKey = values.blockchairApiKeys, + authToken = values.blockchairAuthorizationToken, + ), blockcypherTokens = values.blockcypherTokens, - infuraProjectId = values.infuraProjectId + quickNodeBscCredentials = QuickNodeBscCredentials( + apiKey = values.quiknodeApiKey, + subdomain = values.quiknodeSubdomain, + ), + infuraProjectId = values.infuraProjectId, ), appsFlyerDevKey = values.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 1c537e7909..3b036b1498 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -21,8 +21,10 @@ class ConfigValueModel( val mercuryoSecret: String, val moonPayApiKey: String, val moonPayApiSecretKey: String, - val blockchairApiKey: String?, + val blockchairApiKeys: List, val blockchairAuthorizationToken: String?, + val quiknodeSubdomain: String, + val quiknodeApiKey: String, val blockcypherTokens: Set?, val infuraProjectId: String?, val appsFlyerDevKey: String, diff --git a/dependencies.gradle b/dependencies.gradle index 02d685f18f..7a3f678bf1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'develop-139', + tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-144', // tangem_blockchain_sdk: '0.0.1', ] From a8776a7cb69c973c62a78d0510a2a42bb3a13b61 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 18:11:01 +0300 Subject: [PATCH 06/42] Updated on 2026-08-14 --- .../tap/features/wallet/ui/WalletFragment.kt | 7 +++--- .../core/ui/utils/OneTouchClickListener.kt | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index c0bde194bf..8a67196de1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -16,6 +16,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -110,9 +111,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Unit) : View.OnClickListener { + + private var lastClickTimeMs: Long = 0L + + override fun onClick(v: View?) { + if (SystemClock.elapsedRealtime() - lastClickTimeMs > CLICK_DELAY_MS) { + lastClickTimeMs = SystemClock.elapsedRealtime() + action() + } + } + + companion object { + private const val CLICK_DELAY_MS = 500L + } +} \ No newline at end of file From 8fa38f4a7af06228b621100d42e77ccc3daeea4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:30:21 +0300 Subject: [PATCH 07/42] Updated on 2026-08-14 --- .../com/tangem/tap/features/onboarding/OnboardingHelper.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 9b5c5e9958..775dca1ed3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -70,7 +70,10 @@ class OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + delay(timeMillis = 1_200) + store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, From 6d6fb133cde4752508a7f8f6d0f5adcdbdb25710 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 17:59:57 +0300 Subject: [PATCH 08/42] Updated on 2026-08-14 --- .../DefaultWalletManagersRepository.kt | 6 +---- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../redux/reducers/MultiWalletReducer.kt | 8 +++---- .../wallet/redux/reducers/WalletReducer.kt | 6 ++--- .../tap/features/wallet/ui/WalletViewModel.kt | 8 +++++-- .../redux/WalletSelectorMiddleware.kt | 24 ++++++++++++------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 3de7a3f6b1..6355f2f6d6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -106,11 +106,7 @@ internal class DefaultWalletManagersRepository( override suspend fun delete(userWalletIds: List): CompletionResult = catching { walletManagersStorage.update { prevManagers -> - prevManagers.apply { - userWalletIds.forEach { userWalletId -> - remove(userWalletId) - } - } + prevManagers.filterKeys { it !in userWalletIds } as HashMap> } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 5dcd8b81bd..243ab5eab6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -305,7 +305,7 @@ class WalletMiddleware { val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) store.dispatchOnMain( WalletAction.WalletStoresChanged.UpdateWalletStores( - reduxWalletStores = reduxWalletStores.toList(), + reduxWalletStores = reduxWalletStores, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index b1e6cebbac..7cb35da559 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -31,7 +31,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val walletStores: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -65,10 +65,10 @@ class MultiWalletReducer { ) } - val selectedCurrency = if (!state.isMultiwalletAllowed) { - walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency + val selectedCurrency = if (state.isMultiwalletAllowed) { + state.selectedCurrency } else { - state.selectedWalletData?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } state.copy( walletsStores = walletStores, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 254fe48e3b..d71909cfec 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -362,13 +362,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ) } is WalletAction.LoadData.Success -> { - val selectedCurrency = if (!newState.isMultiwalletAllowed) { + val selectedCurrency = if (newState.isMultiwalletAllowed) { + newState.selectedCurrency + } else { newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency - } else { - newState.selectedWalletData?.currency } newState = newState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt index df0b0537ed..a9fc0ad8cf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt @@ -8,8 +8,10 @@ import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -21,8 +23,10 @@ internal class WalletViewModel : ViewModel() { private fun bootstrapSelectedWalletStoresChanges() { userWalletsListManager.selectedUserWallet - .flatMapLatest { selectedWallet -> - walletStoresManager.get(selectedWallet.walletId) + .map { it.walletId } + .distinctUntilChanged() + .flatMapLatest { selectedUserWalletId -> + walletStoresManager.get(selectedUserWalletId) } .onEach { walletStores -> store.dispatch(WalletAction.WalletStoresChanged(walletStores)) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c7d8495f6a..cc6642743f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -277,26 +277,32 @@ internal class WalletSelectorMiddleware { .flatMap { walletStoresManager.delete(userWalletsIds) } .flatMap { deleteAccessCodes(userWalletsIds) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync when { - selectedWallet == null -> { + selectedUserWallet == null -> { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } - currentSelectedWalletId != selectedWallet.walletId -> { - store.onUserWalletSelected(selectedWallet) + + currentSelectedWalletId != selectedUserWallet.walletId -> { + store.onUserWalletSelected(selectedUserWallet) } } } } private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { - val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() - .asSequence() - .filter { it.walletId in userWalletsIds } - .flatMap { it.cardsInWallet } + val cardsIds = userWalletsListManager.userWallets.firstOrNull() + ?.asSequence() + ?.filter { it.walletId in userWalletsIds } + ?.flatMap { it.cardsInWallet } + ?.toSet() - return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + return if (cardsIds.isNullOrEmpty()) { + CompletionResult.Success(Unit) + } else { + tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + } } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( From cc57668491f01d7c28f5cd9e33844daf204b669b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 19:01:21 +0300 Subject: [PATCH 09/42] Updated on 2026-08-14 --- .../tap/features/wallet/redux/reducers/WalletReducer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index d71909cfec..2505702c20 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -20,7 +20,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork @@ -36,7 +35,6 @@ import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal @@ -344,8 +342,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + walletsStores = newState.walletsStores, totalBalance = if (isMultiCurrency) { - TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + newState.totalBalance } else { null }, From 420686b4d1aa94b8474b1819662aac54b614f484 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 07:54:53 +0300 Subject: [PATCH 10/42] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 7a3f678bf1..bba5e61adc 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-144', + tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-145', // tangem_blockchain_sdk: '0.0.1', ] From d10ad28fd36cdaf97754801e40c9b01869e7faa8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 12:09:10 +0300 Subject: [PATCH 11/42] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bba5e61adc..b0ee825545 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-145', + tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-147', // tangem_blockchain_sdk: '0.0.1', ] From 2d00fcb973455616926d6be16e1588b45871c2c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 14:49:41 +0300 Subject: [PATCH 12/42] Updated on 2026-08-14 --- .../domain/configurable/config/ConfigManager.kt | 14 +++++++++++--- .../tap/domain/configurable/config/JsonModels.kt | 2 ++ dependencies.gradle | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 7db1d56007..12e3d14a80 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain.configurable.config import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.BlockchairCredentials -import com.tangem.blockchain.common.QuickNodeBscCredentials +import com.tangem.blockchain.common.QuickNodeCredentials import com.tangem.tap.common.shop.shopify.ShopifyShop import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.domain.configurable.Loader @@ -91,10 +91,14 @@ class ConfigManager { authToken = values.blockchairAuthorizationToken, ), blockcypherTokens = values.blockcypherTokens, - quickNodeBscCredentials = QuickNodeBscCredentials( + quickNodeCredentials = QuickNodeCredentials( apiKey = values.quiknodeApiKey, subdomain = values.quiknodeSubdomain, ), + bscQuickNodeCredentials = QuickNodeCredentials( + apiKey = values.bscQuiknodeApiKey, + subdomain = values.bscQuiknodeSubdomain, + ), infuraProjectId = values.infuraProjectId, tronGridApiKey = values.tronGridApiKey, ), @@ -116,10 +120,14 @@ class ConfigManager { authToken = values.blockchairAuthorizationToken, ), blockcypherTokens = values.blockcypherTokens, - quickNodeBscCredentials = QuickNodeBscCredentials( + quickNodeCredentials = QuickNodeCredentials( apiKey = values.quiknodeApiKey, subdomain = values.quiknodeSubdomain, ), + bscQuickNodeCredentials = QuickNodeCredentials( + apiKey = values.bscQuiknodeApiKey, + subdomain = values.bscQuiknodeSubdomain, + ), infuraProjectId = values.infuraProjectId, ), appsFlyerDevKey = values.appsFlyerDevKey, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 3b036b1498..1966ac9303 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -25,6 +25,8 @@ class ConfigValueModel( val blockchairAuthorizationToken: String?, val quiknodeSubdomain: String, val quiknodeApiKey: String, + val bscQuiknodeSubdomain: String, + val bscQuiknodeApiKey: String, val blockcypherTokens: Set?, val infuraProjectId: String?, val appsFlyerDevKey: String, diff --git a/dependencies.gradle b/dependencies.gradle index b0ee825545..f31620d64c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-147', + tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-149', // tangem_blockchain_sdk: '0.0.1', ] From de6b0af2281de45df3ac84370d7fdfee4f392768 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:34 +0300 Subject: [PATCH 13/42] Updated on 2026-08-14 --- .../converters/BasicEventConverter.kt | 8 ++- .../common/feedback/AdditionalFeedbackInfo.kt | 4 +- .../com/tangem/tap/domain/model/UserWallet.kt | 20 ------ .../model/builders/UserWalletBuilder.kt | 23 +++--- .../model/builders/UserWalletIdBuilder.kt | 70 +++++++++++++++++++ .../tap/domain/tokens/UserTokensRepository.kt | 16 +++-- .../BiometricUserWalletsListManager.kt | 4 +- .../model/UserWalletEncryptionKey.kt | 2 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 19 ++--- .../details/redux/DetailsMiddleware.kt | 32 ++++----- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../twins/redux/TwinCardsMiddleware.kt | 4 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 2 + .../welcome/redux/WelcomeMiddleware.kt | 2 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 9 ++- .../tangem/domain/common/util/UserWalletId.kt | 16 +---- 18 files changed, 147 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt rename domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt => app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt (55%) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 45d55aac2a..52fa450434 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -3,11 +3,11 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState @@ -29,7 +29,9 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = scanResponse.card.userWalletId.stringValue + filterData = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.stringValue } } } @@ -43,7 +45,7 @@ class BasicTopUpEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null val data = BasicTopUpFilter.Data( - walletId = scanResponse.card.userWalletId.stringValue, + walletId = UserWalletIdBuilder.scanResponse(scanResponse).build()?.stringValue ?: "", cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 16e1bb559a..589597238f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -10,8 +10,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -54,7 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) - userWalletId = data.card.userWalletId.stringValue + userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: "" } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4dec0df7d1..a76fbb9d1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId @@ -33,23 +32,4 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() -} - -/** - * !!! Workaround !!! - * - * Calculate same [UserWalletId] for twins instead - * - * TODO: Remove after [REDACTED_JIRA] - * */ -fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false - if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false - if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - if (scanResponse.secondTwinPublicKey == other.scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..c993194983 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -8,7 +8,6 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper -import com.tangem.domain.common.util.userWalletId import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet @@ -49,16 +48,20 @@ class UserWalletBuilder( } } - suspend fun build(): UserWallet { + suspend fun build(): UserWallet? { return with(scanResponse) { - UserWallet( - walletId = card.userWalletId, - name = userWalletName, - artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), - cardsInWallet = backupCardsIds.plus(card.cardId), - scanResponse = this, - isMultiCurrency = isMultiCurrency, - ) + UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.let { + UserWallet( + walletId = it, + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), + cardsInWallet = backupCardsIds.plus(card.cardId), + scanResponse = this, + isMultiCurrency = isMultiCurrency, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt new file mode 100644 index 0000000000..1fdef7c37c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.Secp256k1 +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.domain.common.util.UserWalletId + +class UserWalletIdBuilder private constructor( + private val publicKey: ByteArray?, + private val pairTwinPublicKey: ByteArray? = null, +) { + fun build(): UserWalletId? { + val seed = if (publicKey != null) { + if (pairTwinPublicKey != null) { + Secp256k1.sum(publicKey, pairTwinPublicKey) + } else { + publicKey + } + } else null + + return seed?.let { + UserWalletId(value = calculateUserWalletId(it)) + } + } + + private fun calculateUserWalletId(seed: ByteArray?): ByteArray? { + val message = MESSAGE_FOR_WALLET_ID.toByteArray() + val keyHash = seed?.calculateSha256() + + return if (keyHash != null) { + message.calculateHmacSha256(keyHash) + } else null + } + + companion object { + private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" + + @Throws(IllegalArgumentException::class) + fun card(card: CardDTO): UserWalletIdBuilder { + require(!card.isTangemTwins) { + "For twin cards use scanResponse to ID calculation" + } + + return UserWalletIdBuilder(findPublicKey(card.wallets)) + } + + fun scanResponse(scanResponse: ScanResponse): UserWalletIdBuilder { + return UserWalletIdBuilder( + publicKey = findPublicKey(scanResponse.card.wallets), + pairTwinPublicKey = when (scanResponse.productType) { + ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() + ProductType.Note, + ProductType.Wallet, + ProductType.SaltPay, + -> null + }, + ) + } + + private fun findPublicKey(wallets: List): ByteArray? { + return wallets.firstOrNull() + ?.publicKey + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 6fb7de9ad7..b3efb6ce5b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -6,9 +6,9 @@ import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.NoDataError +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency @@ -24,7 +24,7 @@ class UserTokensRepository( private val networkService: UserTokensNetworkService, ) { suspend fun getUserTokens(card: CardDTO): List { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } } @@ -47,14 +47,14 @@ class UserTokensRepository( } suspend fun saveUserTokens(card: CardDTO, tokens: List) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return val userTokens = tokens.toUserTokensResponse() networkService.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } suspend fun removeUserTokens(card: CardDTO) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return val userTokens = emptyList().toUserTokensResponse() networkService.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) @@ -70,7 +70,7 @@ class UserTokensRepository( } suspend fun loadBlockchainsToDerive(card: CardDTO): List { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return emptyList() val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { @@ -103,6 +103,7 @@ class UserTokensRepository( coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } tokens } + else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) tokens.distinct() @@ -114,6 +115,11 @@ class UserTokensRepository( return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } + private fun getUserWalletId(card: CardDTO): String? { + return UserWalletIdBuilder.card(card).build() + ?.stringValue + } + companion object { const val SORT_DEFAULT_VALUE = "manual" const val GROUP_DEFAULT_VALUE = "none" diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..648323a70c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.model.isTwinnedWith import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -89,8 +88,7 @@ internal class BiometricUserWalletsListManager( } else { val isWalletSaved = state.value.userWallets .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + it.walletId == userWallet.walletId || it.cardsInWallet.contains(userWallet.cardId) } if (isWalletSaved) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index cdf1edbdfb..a5c20a7790 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 5918312b6e..158dc0db2b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -9,13 +9,13 @@ import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt similarity index 55% rename from domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 4dc70fd2bd..3ecfb70521 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -1,24 +1,27 @@ -package com.tangem.domain.common.util +package com.tangem.tap.domain.userWalletList.utils import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -val CardDTO.userWalletId: UserWalletId - get() = UserWalletId(findWalletPublicKey(wallets)) +internal val CardDTO.encryptionKey: ByteArray + @Throws(IllegalArgumentException::class) + get() { + val walletPublicKey = requireNotNull(findPublicKey(wallets)) { + "Wallet public key must not be null" + } -val CardDTO.encryptionKey: ByteArray - get() = findWalletPublicKey(wallets) - ?.let { calculateEncryptionKey(it) } - ?: error("Wallet ID not found") + return calculateEncryptionKey(walletPublicKey) + } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) } -private fun findWalletPublicKey(wallets: List): ByteArray? { +private fun findPublicKey(wallets: List): ByteArray? { return wallets.firstOrNull() ?.publicKey } 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 319071642c..2c5df4e058 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 @@ -4,10 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings @@ -20,6 +18,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -27,6 +26,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import com.tangem.wallet.R @@ -79,20 +79,16 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) - .doOnSuccess { card -> - val isSameWallet = state.scanResponse?.card?.userWalletId - ?.equals(card.userWalletId) - ?: false + tangemSdkManager.scanProduct(userTokensRepository) + .doOnSuccess { scanResponse -> + val currentUserWalletId = state.scanResponse + ?.let { UserWalletIdBuilder.scanResponse(it).build() } + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + val isSameWallet = currentUserWalletId == scannedUserWalletId - // !!! Workaround !!! - // TODO: Remove after [REDACTED_JIRA] - val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() - ?.equals(state.scanResponse?.secondTwinPublicKey) - ?: false - - if (isSameWallet || isTwinned) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + if (isSameWallet) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scanResponse.card)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -122,8 +118,10 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { + val userWalletId = UserWalletIdBuilder.card(card).build() + tangemSdkManager.resetToFactorySettings(card.cardId) - .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { userWalletsListManager.delete(listOfNotNull(userWalletId)) } .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -275,7 +273,7 @@ class DetailsMiddleware { private suspend fun saveCurrentWallet(state: DetailsState) { val scanResponse = state.scanResponse ?: return - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return userWalletsListManager.save(userWallet) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0c5b990b06..fdacaa5bd1 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -93,7 +93,7 @@ private fun readCard() = scope.launch { onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index ae60d9f637..b5bd3c7504 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -25,6 +24,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency @@ -156,7 +156,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { TwinCardsStep.CreateFirstWallet -> { scope.launch { userWalletsListManager.delete( - listOf(getScanResponse().card.userWalletId), + listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 106ebfa9af..60bc2e076e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -88,7 +88,7 @@ internal class SaveWalletMiddleware { scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() + .build() ?: return@launch val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index cc6642743f..c1f24294f4 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap @@ -162,6 +163,7 @@ internal class WalletSelectorMiddleware { private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { val userWallet = UserWalletBuilder(scanResponse).build() + ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) return userWalletsListManager.save(userWallet) .doOnSuccess { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 7b0f37e36e..e7cb2e01e7 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -71,7 +71,7 @@ internal class WelcomeMiddleware { private fun proceedWithCard(state: WelcomeState) = scope.launch { scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 8856cdc80d..e12ca9b9be 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -8,12 +8,12 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action @@ -54,7 +54,12 @@ class UserWalletManagerImpl( } override fun getWalletId(): String { - return appStateHolder.getActualCard()?.userWalletId?.stringValue ?: "" + return appStateHolder.getActualCard()?.let { + UserWalletIdBuilder.card(it) + .build() + ?.stringValue + } + ?: "" } override suspend fun isTokenAdded(currency: Currency): Boolean { diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 4e83c913ca..abf878da69 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -1,17 +1,15 @@ package com.tangem.domain.common.util -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.domain.common.extensions.calculateHmacSha256 class UserWalletId( val stringValue: String, ) { val value = stringValue.hexToBytes() - constructor(walletPublicKey: ByteArray?) : this( - stringValue = walletPublicKey?.let { calculateUserWalletId(it).toHexString() } ?: "", + constructor(value: ByteArray?) : this( + stringValue = value?.toHexString() ?: "", ) override fun equals(other: Any?): Boolean { @@ -32,12 +30,4 @@ class UserWalletId( "UserWalletId(${take(3)}...${takeLast(3)})" } } -} - -private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { - val message = MESSAGE_FOR_WALLET_ID.toByteArray() - val keyHash = publicKey.calculateSha256() - return message.calculateHmacSha256(keyHash) -} - -private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" \ No newline at end of file +} \ No newline at end of file From 9ee61f80579f285d5fb683b73c21c7058d878631 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:51 +0300 Subject: [PATCH 14/42] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 869eaf8f0b..89471ec92c 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,7 +61,7 @@ object Versions { // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-178" + const val tangemCardSgk = "develop-179" // endregion Tangem // region Testing From 6b3e84f9c34ee038f5b6e22888dc024374a1bd6a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:15:32 +0300 Subject: [PATCH 15/42] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletDetailsFragment.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index a7f230dc35..f212aac2dd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -132,12 +132,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onStop() { super.onStop() store.unsubscribe(this) - } - - override fun onDestroy() { walletDataWatcher.clear() walletStateWatcher.clear() - super.onDestroy() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { From 0c888d210f5274f05dddd3c280173cba8faeace3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:00:44 +0300 Subject: [PATCH 16/42] Updated on 2026-08-14 --- .../tap/domain/model/builders/UserWalletBuilder.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..ed8eb97623 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO @@ -40,7 +42,16 @@ class UserWalletBuilder( ProductType.Note -> false ProductType.Twins -> false ProductType.SaltPay -> false - ProductType.Wallet -> !card.isStart2Coin + ProductType.Wallet -> when { + card.isStart2Coin -> false + card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true + else -> { + val cardWallets = card.wallets + require(cardWallets.isNotEmpty()) { "Card wallets must not be empty" } + + cardWallets.first().curve == EllipticCurve.Secp256k1 + } + } } fun backupCardsIds(backupCardsIds: Set?) = this.apply { From 252df193d2fd0e19f7612ded53d47e99df43be04 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:40:03 +0300 Subject: [PATCH 17/42] Updated on 2026-08-14 --- .../AddressInfoBottomSheetDialog.kt | 30 +++++------------ .../wallet/ui/WalletDetailsFragment.kt | 18 +++++++++-- .../wallet/ui/wallet/SingleWalletView.kt | 32 ++++++++++++++----- .../layout/dialog_onboarding_address_info.xml | 2 +- app/src/main/res/layout/layout_address.xml | 11 +++++++ .../main/res/layout/layout_wallet_details.xml | 2 +- .../src/main/res/values-de/strings-app.xml | 3 +- .../src/main/res/values-fr/strings-app.xml | 3 +- .../src/main/res/values-it/strings-app.xml | 3 +- .../src/main/res/values-ru/strings-app.xml | 3 +- core/res/src/main/res/values/strings-app.xml | 3 +- 11 files changed, 65 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 4635716436..b56ddaf6cd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -11,8 +11,8 @@ import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchShare import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.store import com.tangem.wallet.R @@ -62,26 +62,12 @@ class AddressInfoBottomSheetDialog( Analytics.send(Token.Recieve.ButtonShareAddress()) store.dispatchShare(data.shareUrl) } - tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency) - } -} - -fun getQRReceiveMessage(context: Context, currency: Currency): String { - return when (currency) { - is Currency.Blockchain -> { - context.getString( - R.string.address_qr_code_message_format, - currency.blockchain.fullName, - currency.currencySymbol, - ) - } - is Currency.Token -> { - context.getString( - R.string.address_qr_code_message_token_format, - currency.token.name, - currency.currencySymbol, - currency.blockchain.fullName, - ) - } + val blockchain = stateDialog.currency.blockchain + tvReceiveMessage.text = tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index f212aac2dd..bd1608e7de 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -36,7 +36,6 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.ErrorType @@ -324,8 +323,21 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), ) } ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) - tvReceiveMessage.text = - getQRReceiveMessage(tvReceiveMessage.context, state.currency) + + tvReceiveMessage.text = when (val currency = state.currency) { + is Currency.Blockchain -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol, + currency.blockchain.fullName, + ) + is Currency.Token -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index e8fc90a242..18ebc3dd4b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -65,7 +66,7 @@ class SingleWalletView : WalletView() { setupTwinCards(state.twinCardsState, binding) setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn) - setupAddressCard(state.primaryWallet, binding) + setupAddressCard(state, binding) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) } @@ -146,35 +147,50 @@ class SingleWalletView : WalletView() { } } - private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) { - if (state.walletAddresses != null && state.currency is Currency.Blockchain) { + private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) { + val primaryWallet = state.primaryWallet + if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) { binding.lAddress.root.show() - if (state.shouldShowMultipleAddress()) { + if (primaryWallet.shouldShowMultipleAddress()) { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, primaryWallet.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { chipGroupAddressType.hide() } - tvAddress.text = state.walletAddresses.selectedAddress.address + tvAddress.text = primaryWallet.walletAddresses.selectedAddress.address tvExplore.setOnClickListener { store.dispatch( WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, + primaryWallet.walletAddresses.selectedAddress.exploreUrl, fragment!!.requireContext(), ), ) } + setupCardInfo(state) } else { binding.lAddress.root.hide() } } + + private fun setupCardInfo(state: WalletState) { + val textView = binding?.lAddress?.tvInfo + val blockchain = state.primaryWallet?.currency?.blockchain + if (textView != null && blockchain != null) { + textView.text = textView.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml index ca04440067..eadb8f7f36 100644 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -36,7 +36,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imv_qr_code" - tools:text="@string/address_qr_code_message_token_format" /> + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> + + + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings-app.xml index 9b09fb053e..bd2e4888dc 100644 --- a/core/res/src/main/res/values-de/strings-app.xml +++ b/core/res/src/main/res/values-de/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. OK, ich hab\'s! diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings-app.xml index 8d7d037a69..3d926e7f7b 100644 --- a/core/res/src/main/res/values-fr/strings-app.xml +++ b/core/res/src/main/res/values-fr/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, je l\'ai! diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings-app.xml index c5e3134366..aa4147d4ba 100644 --- a/core/res/src/main/res/values-it/strings-app.xml +++ b/core/res/src/main/res/values-it/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, ho capito! diff --git a/core/res/src/main/res/values-ru/strings-app.xml b/core/res/src/main/res/values-ru/strings-app.xml index 34b9be72db..142d4e305f 100644 --- a/core/res/src/main/res/values-ru/strings-app.xml +++ b/core/res/src/main/res/values-ru/strings-app.xml @@ -133,8 +133,7 @@ Успешно! Ваша карта активирована и готова к использованию Баланс - Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. - Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) в сети %s на этот адрес. Использование другой сети может привести к утрате средств. Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Понятно! diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings-app.xml index 4d8976d15d..80bfd3967b 100644 --- a/core/res/src/main/res/values/strings-app.xml +++ b/core/res/src/main/res/values/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, Got it! From c320f58ded1cbfc5e8ce250457f4011194233790 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:25:38 +0300 Subject: [PATCH 18/42] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 2 +- .../repository/UserWalletsKeysRepository.kt | 6 ++++++ .../UserWalletsPublicInformationRepository.kt | 2 -- .../BiometricUserWalletsKeysRepository.kt | 7 +++++++ .../DefaultUserWalletsPublicInformationRepository.kt | 4 ---- .../tap/features/details/redux/DetailsMiddleware.kt | 12 +++++++++--- .../tap/features/details/redux/DetailsReducer.kt | 3 +-- 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..fdd95f5f63 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -51,7 +51,7 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = publicInformationRepository.isNotEmpty() + get() = keysRepository.hasSavedEncryptionKeys() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index f10e0beeb5..bf96081cd4 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -32,4 +32,10 @@ internal interface UserWalletsKeysRepository { * @return [CompletionResult] of operation * */ suspend fun clear(): CompletionResult + + /** + * Determine if the user has saved user wallets + * @return [Boolean] true if user has saved wallets + * */ + fun hasSavedEncryptionKeys(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index f0387d3f0e..8765a6b804 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,6 +12,4 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult - - fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 5e57d10474..8de5a59457 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -17,6 +17,7 @@ import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( @@ -77,6 +78,12 @@ internal class BiometricUserWalletsKeysRepository( } } + override fun hasSavedEncryptionKeys(): Boolean { + return runBlocking { + getUserWalletsIds().isNotEmpty() + } + } + private suspend fun getAllInternal(): CompletionResult> { return getUserWalletsIds() .map { userWalletId -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46a016f8c0..7d1bd9333b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -72,10 +72,6 @@ internal class DefaultUserWalletsPublicInformationRepository( } } - override fun isNotEmpty(): Boolean { - return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true - } - @JvmName("saveWithPublicInformation") private suspend fun save( publicInformation: List, 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 319071642c..a170aebdfd 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 @@ -278,9 +278,6 @@ class DetailsMiddleware { val userWallet = UserWalletBuilder(scanResponse).build() userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Wallet saving failed") - } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) @@ -296,6 +293,9 @@ class DetailsMiddleware { store.onUserWalletSelected(userWallet) } + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } } private suspend fun deleteSavedWallets() { @@ -315,6 +315,9 @@ class DetailsMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved wallets") + } } private fun saveAccessCodes(state: DetailsState) { @@ -353,6 +356,9 @@ class DetailsMiddleware { ) } } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved access codes") + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index f00f0b46b4..11b737097a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -11,7 +11,6 @@ import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import org.rekotlin.Action import java.util.* @@ -57,7 +56,7 @@ private fun handlePrepareScreen( createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, isBiometricsAvailable = tangemSdkManager.canUseBiometry, - saveWallets = userWalletsListManager.hasSavedUserWallets, + saveWallets = preferencesStorage.shouldSaveUserWallets, saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, ) } From c48e7204eb3919864b482c993c2b30eb238f5cfd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Dec 2022 14:00:54 +0300 Subject: [PATCH 19/42] Updated on 2026-08-14 --- .../userWalletList/UserWalletsListManager.kt | 13 +++- .../BiometricUserWalletsListManager.kt | 71 ++++++++++++++----- .../DummyUserWalletsListManager.kt | 56 --------------- .../model/UserWalletEncryptionKey.kt | 7 -- .../repository/UserWalletsKeysRepository.kt | 6 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../BiometricUserWalletsKeysRepository.kt | 5 +- ...erWalletsSensitiveInformationRepository.kt | 5 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 11 +-- .../features/tokens/redux/TokensMiddleware.kt | 15 ++-- .../middlewares/MultiWalletMiddleware.kt | 19 ++--- .../redux/WalletSelectorMiddleware.kt | 4 +- .../ui/WalletSelectorViewModel.kt | 20 ++---- 13 files changed, 104 insertions(+), 130 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index dfd62c05f5..9e7619e7c5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,7 +19,7 @@ interface UserWalletsListManager { suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user wallet + * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card @@ -27,6 +27,17 @@ interface UserWalletsListManager { * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult + /** + * Same as [save] but not change selected user wallet ID + * and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved + * + * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] + * @param userWalletId update [UserWallet] with that [UserWalletId] + * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] + * @return [CompletionResult] of operation with updated [UserWallet] + * */ + suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 4eef3db4b7..db1db2ebbd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -10,6 +10,7 @@ import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletReposit import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,7 +85,7 @@ internal class BiometricUserWalletsListManager( override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return if (canOverride) { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } else { val isWalletSaved = state.value.userWallets .any { @@ -94,11 +95,25 @@ internal class BiometricUserWalletsListManager( if (isWalletSaved) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } } } + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult { + return get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false) + .map { updatedUserWallet } + } + } + override suspend fun delete(userWalletIds: List): CompletionResult { if (userWalletIds.isEmpty()) { return CompletionResult.Success(Unit) @@ -111,9 +126,12 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> + val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds } + prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, - userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, + userWallets = newUserWallets, + isLocked = newUserWallets.any { it.isLocked }, ) } } @@ -135,27 +153,23 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - - return keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - selectedUserWalletId = userWallet.walletId, - ) - } - } + private suspend fun saveInternal( + userWallet: UserWallet, + changeSelectedUserWallet: Boolean, + ): CompletionResult { + return saveEncryptionKeyIfNotNull(userWallet) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } .map { selectedUserWalletRepository.set(userWallet.walletId) } .flatMap { loadModels() } .doOnSuccess { state.update { prevState -> prevState.copy( + selectedUserWalletId = if (changeSelectedUserWallet) { + userWallet.walletId + } else { + prevState.selectedUserWalletId + }, isLocked = prevState.userWallets.any { it.isLocked }, ) } @@ -181,6 +195,27 @@ internal class BiometricUserWalletsListManager( } } + private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { + val encryptionKey = userWallet.scanResponse.card.encryptionKey + ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + + return if (encryptionKey != null) { + keysRepository.save(encryptionKey) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = prevState.encryptionKeys + .plus(encryptionKey) + .distinctBy { it.walletId }, + ) + } + } + .map { encryptionKey.encryptionKey } + } else { + CompletionResult.Success(data = null) + } + } + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt deleted file mode 100644 index e616c48758..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain.userWalletList.implementation - -import com.tangem.common.CompletionResult -import com.tangem.common.catching -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class DummyUserWalletsListManager : UserWalletsListManager { - override val userWallets: Flow> - get() = flowOf(emptyList()) - override val selectedUserWallet: Flow - get() = flowOf() - override val selectedUserWalletSync: UserWallet? - get() = null - override val isLocked: Flow - get() = flowOf(true) - override val isLockedSync: Boolean - get() = true - override val hasSavedUserWallets: Boolean - get() = false - - override suspend fun unlockWithBiometry(): CompletionResult { - return CompletionResult.Success(null) - } - - override fun lock() { - /* no-op */ - } - - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } - - override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun delete(userWalletIds: List): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun clear(): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index a5c20a7790..df0f473bae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,19 +2,12 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( val walletId: UserWalletId, val encryptionKey: ByteArray, ) { - constructor(userWallet: UserWallet) : this( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is UserWalletEncryptionKey) return false diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index bf96081cd4..99b62dc99d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -14,11 +14,11 @@ internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> /** - * Store the encryption keys for user wallets. Biometric authentication not required - * @param encryptionKeys List of encryption keys for user wallets + * Save the encryption key for user wallet. Biometric authentication not required + * @param encryptionKey [UserWalletEncryptionKey] to save * @return [CompletionResult] of operation * */ - suspend fun store(encryptionKeys: List): CompletionResult + suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult /** * Delete encryption keys for user wallets. Biometric authentication not required diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 7e03055150..fdae3e64a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal interface UserWalletsSensitiveInformationRepository { - suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult suspend fun getAll( encryptionKeys: List, ): CompletionResult> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 8de5a59457..11e558535d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -45,10 +45,9 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun store(encryptionKeys: List): CompletionResult { + override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult { return withContext(Dispatchers.IO) { - encryptionKeys.map { storeEncryptionKey(it) } - .fold() + storeEncryptionKey(encryptionKey) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 158dc0db2b..673ec51a10 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -38,11 +38,12 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } - override suspend fun save(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { + if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() - .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + .encryptAndStoreIv(userWallet.walletId.stringValue, encryptionKey) getAllEncrypted().toMutableMap() .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 3ecfb70521..d97af026cc 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -4,15 +4,8 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -internal val CardDTO.encryptionKey: ByteArray - @Throws(IllegalArgumentException::class) - get() { - val walletPublicKey = requireNotNull(findPublicKey(wallets)) { - "Wallet public key must not be null" - } - - return calculateEncryptionKey(walletPublicKey) - } +internal val CardDTO.encryptionKey: ByteArray? + get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index b07a4fd3ca..10a7d4e03a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -281,13 +281,16 @@ class TokensMiddleware { ) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val updatedUserWallet = selectedUserWallet.copy( - scanResponse = scanResponse, - ) - scope.launch { - userWalletsListManager.save(updatedUserWallet, canOverride = true) - .flatMap { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + .flatMap { updatedUserWallet -> walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, currenciesToAdd = currencyList, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 9bba0011a5..821f4971b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -179,21 +179,24 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard( - selectedWallet: UserWallet, + selectedUserWallet: UserWallet, state: WalletState?, ) = scope.launch { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( - cardId = selectedWallet.cardId, + cardId = selectedUserWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> - val userWallet = selectedWallet.copy( - scanResponse = scanResponse, + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, ) - - userWalletsListManager.save(userWallet, canOverride = true) - .doOnSuccess { - store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + .doOnSuccess { updatedUserWallet -> + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c1f24294f4..4772b501d7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -239,9 +239,7 @@ internal class WalletSelectorMiddleware { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(userWalletId) - .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.save(it, canOverride = true) } + userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 1192b37c4a..52a99013e3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -38,7 +38,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { editWallet(userWalletId) } @@ -52,7 +51,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber - store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( renameWalletDialog = null, @@ -137,11 +136,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } From 0263ee0a28a71d21f7a3c8d817434560cd15e474 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 21:19:13 +0300 Subject: [PATCH 20/42] Updated on 2026-08-14 --- .../DefaultWalletCurrenciesManager.kt | 11 +- .../repository/WalletAmountsRepository.kt | 6 + .../DefaultWalletAmountsRepository.kt | 165 ++++++++++-------- .../utils/WalletStoreOperations.kt | 6 +- .../middlewares/MultiWalletMiddleware.kt | 4 +- 5 files changed, 117 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 5fd2e25234..e000fe1c6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -64,7 +65,15 @@ internal class DefaultWalletCurrenciesManager( saveUserCurrencies(card, newCurrencies) } .flatMap { - walletAmountsRepository.updateAmountsForUserWallet( + val updatedBlockchains = updatedBlockchainNetworks + .map { it.blockchain } + val updatedWalletStores = walletStoresRepository.get(userWallet.walletId) + .firstOrNull() + ?.filter { it.blockchain in updatedBlockchains } + ?: return@flatMap CompletionResult.Success(Unit) + + walletAmountsRepository.updateAmountsForWalletStores( + walletStores = updatedWalletStores, userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index 0af5b8f85e..a054ed0ff2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -29,6 +29,12 @@ interface WalletAmountsRepository { fiatCurrency: FiatCurrency, ): CompletionResult + suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult + /** * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 640c08fe67..444d6fc480 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -41,7 +41,8 @@ import com.tangem.tap.network.NetworkConnectivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -60,7 +61,7 @@ internal class DefaultWalletAmountsRepository( else withContext(Dispatchers.Default) { awaitAll( async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, fiatCurrency) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, ) .fold() } @@ -73,46 +74,40 @@ internal class DefaultWalletAmountsRepository( return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } + override suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return if (walletStores.isEmpty()) CompletionResult.Success(Unit) + else withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } + } + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, userWallet: UserWallet, fiatCurrency: FiatCurrency, - ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - - awaitAll( - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) - }, - async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, - ) - .fold() - } - - private suspend fun fetchAmountsForUserWallets( - userWallets: List, - ): CompletionResult = withContext(Dispatchers.Default) { - userWallets.map { async { fetchAmountsForUserWallet(it) } } - .awaitAll() - .fold() + ): CompletionResult { + return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency) } private suspend fun fetchFiatRates( userWallets: List, + walletStores: List?, fiatCurrency: FiatCurrency, ): CompletionResult { - val walletsIds = userWallets.map { it.walletId } - val walletStores = walletsIds - .flatMap { - walletStoresStorage.getAll() - .first() - .getOrElse(it) { emptyList() } - } + val walletStoresInternal = walletStores ?: getWalletStores(userWallets) - val currencies = walletStores + val currencies = walletStoresInternal .asSequence() .flatMap { it.walletsData } .map { it.currency } @@ -129,7 +124,7 @@ internal class DefaultWalletAmountsRepository( return when (fiatRatesResult) { is Result.Success -> { updateWalletStoresWithFiatRates( - walletStores = walletStores, + walletStores = walletStoresInternal, fiatRates = fiatRatesResult.data.rates, ) @@ -145,7 +140,6 @@ internal class DefaultWalletAmountsRepository( error, """ Unable to fetch fiat rates - |- User wallets ids: $walletsIds |- Coins ids: $coinsIds """.trimIndent(), ) @@ -155,20 +149,34 @@ internal class DefaultWalletAmountsRepository( } } + private suspend fun fetchAmountsForUserWallets( + userWallets: List, + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() + .fold() + } + private suspend fun fetchAmountsForUserWallet( userWallet: UserWallet, ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId + val userWalletId = userWallet.walletId val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getAll() - .first() - .getOrElse(walletId) { emptyList() } + val walletStores = getWalletStores(listOf(userWallet)) + fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) + } + + private suspend fun fetchAmountForWalletStores( + userWalletId: UserWalletId, + scanResponse: ScanResponse, + walletStores: List, + ): CompletionResult = coroutineScope { walletStores.map { walletStore -> async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager) } } .awaitAll() @@ -176,7 +184,7 @@ internal class DefaultWalletAmountsRepository( } private suspend fun fetchAmountsForWalletStore( - walletId: UserWalletId, + userWalletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, @@ -186,11 +194,15 @@ internal class DefaultWalletAmountsRepository( } return when { - hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) - walletManager == null -> updateWalletStoreWithUnreachable(walletStore) + hasMissedDerivations -> { + updateWalletStoreWithMissedDerivation(walletStore) + } + walletManager == null -> { + updateWalletStoreWithUnreachable(walletStore) + } else -> { withInternetConnection { walletManager.update() } - .map { updateWalletManagerWithAmounts(walletId, walletManager) } + .map { updateWalletManagerWithAmounts(userWalletId, walletManager) } .flatMap { updateWalletStoreWithAmounts( walletStore = walletStore, @@ -248,35 +260,6 @@ internal class DefaultWalletAmountsRepository( return CompletionResult.Success(Unit) } - private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { - return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - val error = WalletStoresError.NoInternetConnection - Timber.e(error) - CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } - } - } - - private suspend fun updateWalletManagerWithAmounts( - walletId: UserWalletId, - walletManager: WalletManager, - ) = withContext(Dispatchers.Default) { - walletManagersStorage.update { prevManagers -> - val newManagersForUserWallet = prevManagers[walletId].orEmpty() - .toMutableList() - .apply { - replaceByOrAdd(walletManager) { - it.wallet.blockchain == it.wallet.blockchain - } - } - - prevManagers.apply { - set(walletId, newManagersForUserWallet) - } - } - } - private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, @@ -425,4 +408,44 @@ internal class DefaultWalletAmountsRepository( } } } + + private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { + return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + val error = WalletStoresError.NoInternetConnection + Timber.e(error) + CompletionResult.Failure(error) + } else withContext(Dispatchers.IO) { + catching { block() } + } + } + + private suspend fun updateWalletManagerWithAmounts( + userWalletId: UserWalletId, + walletManager: WalletManager, + ) = withContext(Dispatchers.Default) { + walletManagersStorage.update { prevManagers -> + val newManagersForUserWallet = prevManagers[userWalletId].orEmpty() + .toMutableList() + .apply { + replaceByOrAdd(walletManager) { + it.wallet.blockchain == it.wallet.blockchain + } + } + + prevManagers.apply { + set(userWalletId, newManagersForUserWallet) + } + } + } + + private suspend fun getWalletStores(userWallets: List): List { + return userWallets + .map { it.walletId } + .flatMap { userWalletId -> + walletStoresStorage.getAll() + .firstOrNull() + ?.get(userWalletId) + .orEmpty() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 445ff655f2..2aa7ffcf2d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -62,9 +62,11 @@ internal fun WalletStoreModel.updateWithSelf( ): WalletStoreModel { val oldStore = this return oldStore.copy( - walletManager = newWalletStore.walletManager, - walletRent = newWalletStore.walletRent, + derivationPath = newWalletStore.derivationPath, walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData), + walletRent = newWalletStore.walletRent, + blockchainNetwork = newWalletStore.blockchainNetwork, + walletManager = newWalletStore.walletManager, ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 821f4971b4..ba82d17ccd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalState @@ -181,7 +182,7 @@ class MultiWalletMiddleware { private fun scanAndUpdateCard( selectedUserWallet: UserWallet, state: WalletState?, - ) = scope.launch { + ) = scope.launch(Dispatchers.Default) { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( cardId = selectedUserWallet.cardId, @@ -196,6 +197,7 @@ class MultiWalletMiddleware { }, ) .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } From 0155e3c7420238e4cedd3e440bcd2751a8193fe0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 14:33:08 +0400 Subject: [PATCH 21/42] Updated on 2026-08-14 --- .../com/tangem/tap/features/tokens/redux/TokensMiddleware.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 10a7d4e03a..af6ab1607f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -149,7 +149,7 @@ class TokensMiddleware { && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { store.dispatchDebugErrorNotification("Nothing to save") - store.dispatch(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo()) return@launch } From 1f6d1fd2bd70b417ccf5947b078479565842a1b0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 12:28:17 +0300 Subject: [PATCH 22/42] Updated on 2026-08-14 --- .../wallet/ui/dialogs/ScanFailsDialog.kt | 2 +- .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-de/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-fr/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-it/strings_final.xml | 233 ------------------ .../{strings-app.xml => strings.xml} | 227 ++++++++++++++++- .../src/main/res/values-ru/strings_final.xml | 233 ------------------ .../values/{strings-app.xml => strings.xml} | 231 ++++++++++++++++- .../res/src/main/res/values/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 14 files changed, 1131 insertions(+), 1181 deletions(-) rename core/res/src/main/res/values-de/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-de/strings_final.xml rename core/res/src/main/res/values-de/{strings_plurals_final.xml => strings_plurals.xml} (100%) rename core/res/src/main/res/values-fr/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-fr/strings_final.xml rename core/res/src/main/res/values-fr/{strings_plurals_final.xml => strings_plurals.xml} (100%) rename core/res/src/main/res/values-it/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-it/strings_final.xml rename core/res/src/main/res/values-ru/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-ru/strings_final.xml rename core/res/src/main/res/values/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values/strings_final.xml rename core/res/src/main/res/values/{strings_plurals_final.xml => strings_plurals.xml} (100%) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index d3bc64fa17..cef955c28e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -24,7 +24,7 @@ class ScanFailsDialog { Analytics.send(IntroductionProcess.ButtonRequestSupport()) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } - setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } + setNeutralButton(R.string.common_cancel) { _, _ -> } setOnDismissListener { store.dispatchDialogHide() } }.create() } diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings.xml similarity index 51% rename from core/res/src/main/res/values-de/strings-app.xml rename to core/res/src/main/res/values-de/strings.xml index bd2e4888dc..9f2035db71 100644 --- a/core/res/src/main/res/values-de/strings-app.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Erneut versuchen + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Karte scannen + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Bilanz: %s + Share + Copy + Erfolg + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Ungültige Adresse + This wallet has already been saved, you can add another one + Entfernen + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Änderungen speichern Warnung @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Fehler OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml deleted file mode 100644 index 046904ddaf..0000000000 --- a/core/res/src/main/res/values-de/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Erneut versuchen - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Karte scannen - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Bilanz: %s - Share - Copy - Erfolg - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Ungültige Adresse - This wallet has already been saved, you can add another one - Entfernen - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-de/strings_plurals_final.xml b/core/res/src/main/res/values-de/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-de/strings_plurals_final.xml rename to core/res/src/main/res/values-de/strings_plurals.xml diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings.xml similarity index 51% rename from core/res/src/main/res/values-fr/strings-app.xml rename to core/res/src/main/res/values-fr/strings.xml index 3d926e7f7b..cf498616e2 100644 --- a/core/res/src/main/res/values-fr/strings-app.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Réessayer + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scannez la carte + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Solde : %s + Share + Copy + Avec succès + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Adresse incorrecte + This wallet has already been saved, you can add another one + Supprimer + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Sauvegarder les modifications Alerte @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Erreur OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml deleted file mode 100644 index 4b3d386d9b..0000000000 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Réessayer - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scannez la carte - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Solde : %s - Share - Copy - Avec succès - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Adresse incorrecte - This wallet has already been saved, you can add another one - Supprimer - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-fr/strings_plurals_final.xml b/core/res/src/main/res/values-fr/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-fr/strings_plurals_final.xml rename to core/res/src/main/res/values-fr/strings_plurals.xml diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings.xml similarity index 51% rename from core/res/src/main/res/values-it/strings-app.xml rename to core/res/src/main/res/values-it/strings.xml index aa4147d4ba..af7ce360a9 100644 --- a/core/res/src/main/res/values-it/strings-app.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Riprova + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scansiona carta + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Saldo: %s + Share + Copy + Con successo + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Indirizzo non valido + This wallet has already been saved, you can add another one + Rimuovere + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Mantieni le modifiche Avviso @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Errore OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml deleted file mode 100644 index 53bd5f260c..0000000000 --- a/core/res/src/main/res/values-it/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Riprova - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scansiona carta - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Saldo: %s - Share - Copy - Con successo - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Indirizzo non valido - This wallet has already been saved, you can add another one - Rimuovere - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-ru/strings-app.xml b/core/res/src/main/res/values-ru/strings.xml similarity index 51% rename from core/res/src/main/res/values-ru/strings-app.xml rename to core/res/src/main/res/values-ru/strings.xml index 142d4e305f..a715487104 100644 --- a/core/res/src/main/res/values-ru/strings-app.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 карты + 2 карты + Доставка + Бесплатно + У меня есть промо-код… + Итого + Другие способы оплаты + Купить сейчас + Встречайте\nTangem + Покупайте + Храните + Отправляйте + Расплачивайтесь + Обменивайте + Вкладывайте + Занимайте + Революционный аппаратный кошелек + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Все ключи в безопасности + До + трех карт + с одним кошельком + Тысячи криптовалют + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Поддержка DeFi + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Кошелек для каждого + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Купить + Поиск токенов + Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. + Эта функция недоступна в демонстрационном режиме + Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. + Доступные сети + Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Внимание + Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. + Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. + Адрес контракта скопирован! + Адрес контракта + Обязательное поле + Пожалуйста, выберите сеть + Количество знаков после запятой должно быть корректным числом не больше %d + Адрес контракта некорректен + Путь деривации некорректен + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Этот токен/сеть уже находится в вашем списке + Знаков после запятой + Сеть + Не выбрано + Например, USD Coin + Название токена + Например, USDC + Символ токена + Деривация по BIP44 + По-умолчанию + Сервер недоступен, повторите попытку позднее + Баланс + В сумме учтены не все монеты + Токены + Управление токенами + Нет цены + Сеть недоступна + Скрыть токен + Скрыть %s + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Невозможно скрыть %s + Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. + Бэкап кошелька не был произведен + Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру + Повторить + Чат + Сеть %s + Я понял + Да + Нет + Карты банков РФ в данный момент не принимаются + У вас есть карта банка другой страны или платежной системы UnionPay? + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Настройки карты + Тип безопасности + Смена кода доступа + Код доступа будет изменен только на данной карте + Продолжить + Tangem Bot + Приготовьте свою карту + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Сканировать + Настройки приложения + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + WalletConnect + Подключение к Dapps + Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Сбросить карту + Сброс к заводским настройкам + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. + Выберите сеть + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + C возвращением! + Используйте %s или код доступа для входа в приложение + Войти с %s + Сканировать карту + Доступ в приложение + Войдите в приложение и следите за своим балансом без сканирования карты + Код доступа + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Мои кошельки + Мультивалютные + Одновалютные + Добавить новый кошелек + Переименование кошелька + Имя кошелька + Разблокировать все с %s + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Внимание + Приложите карту с логотипом Visa + Недостаточно средств для активации + Пожалуйста обратитесь в службу поддержки + Ввод одинаковых цифр является не безопасным + Данный Код доступа может быть легко взломан + Код доступа + Подключиться + Верификация клиента + Подтвердите свою личность + Установить Код доступа + Зарегистрироваться + Верифицировать (Utorg) + Обновить + Подключите свою карту + Подтвердите свою личность + Подтверждение личности в процессе + Подключите вашу карту к децентрализованной платежной системе + Для начала работы с картой вам необходимо завершить процесс подтверждения личности + Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. + Код доступа + Установите Код доступа для вашей SaltPay карты + Чат поддержки + Пожалуйста, удерживайте карту до завершения операции + Для начала процесса бэкапа вам необходимо добавить Tangem карту + Бэкап карта не добавлена + Бэкап карта создана + Завершите процесс бэкапа создав код доступа + Приготовьте SaltPay карту + Приложите SaltPay карту + Приложите Tangem карту + Чат + Запросить %s + Для начала работы просто запросите начисление wxDai на свой кошелек + Запросить + Поздравляем! Ваша платежная крипто карта теперь активирована! + Запрашивается + Это займет несколько секунд + Что-то пошло не так + Более подробная информация отправлена на ваш адрес электронной почты. + Вы хотите выйти из процесса активации? + В этом случае вам будет необходимо начать процесс заново. + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Реферальная программа + Приведи друга в Tangem + Вы + Получите + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг + Ваш друг + Получит + %s скидку + при покупке карточки на сайте tangem.com + Ваши друзья купили + Ваш персональный код + Участвовать + условия участия + Нажимая на эту кнопку вы принимаете + Вы приняли + в реферальной программе + Внутренняя ошибка: не удается найти менеджер кошельков + Баланс: %s + Поделиться + Копировать + Успешно + Дать разрешение + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Количество %s + Ваш кошелек + Отправитель + Подтвердить + Обмен %s на + Обмен + Недостаточно средств + Дать разрешение + Разрешить и обменять + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! + Купи Tangem Wallet со скидкой!\n%s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + Неверный адрес + Этот кошелек уже был сохранен, вы можете добавить другой + Удалить + Privacy policy + Включите биометрическую аутентификацию + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включить + %d выбрано + биометрическую аутентификацию + биометрией Tangem Сохранить изменения Предупреждение @@ -97,7 +315,6 @@ Не могу отправить транзакцию Причина: %s У вас возникли трудности со сканированием карты? - Отмена Обратиться в поддержку Отправить отзыв Очень круто! @@ -226,6 +443,12 @@ Ошибка Ок Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Вы хотите использовать биометрию? + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Использовать биометрию + Включите биометрическую аутентификацию + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту %s diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml deleted file mode 100644 index c56fea1dcc..0000000000 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 карты - 2 карты - Доставка - Бесплатно - У меня есть промо-код… - Итого - Другие способы оплаты - Купить сейчас - Встречайте\nTangem - Покупайте - Храните - Отправляйте - Расплачивайтесь - Обменивайте - Вкладывайте - Занимайте - Революционный аппаратный кошелек - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Все ключи в безопасности - До - трех карт - с одним кошельком - Тысячи криптовалют - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Поддержка DeFi - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Кошелек для каждого - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Купить - Поиск токенов - Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. - Эта функция недоступна в демонстрационном режиме - Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. - Доступные сети - Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. - Внимание - Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. - Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. - Адрес контракта скопирован! - Адрес контракта - Обязательное поле - Пожалуйста, выберите сеть - Количество знаков после запятой должно быть корректным числом не больше %d - Адрес контракта некорректен - Путь деривации некорректен - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Этот токен/сеть уже находится в вашем списке - Знаков после запятой - Сеть - Не выбрано - Например, USD Coin - Название токена - Например, USDC - Символ токена - Деривация по BIP44 - По-умолчанию - Сервер недоступен, повторите попытку позднее - Баланс - В сумме учтены не все монеты - Токены - Управление токенами - Нет цены - Сеть недоступна - Скрыть токен - Скрыть %s - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Невозможно скрыть %s - Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Бэкап кошелька не был произведен - Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру - Повторить - Чат - Сеть %s - Я понял - Да - Нет - Карты банков РФ в данный момент не принимаются - У вас есть карта банка другой страны или платежной системы UnionPay? - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Настройки карты - Тип безопасности - Смена кода доступа - Код доступа будет изменен только на данной карте - Продолжить - Tangem Bot - Приготовьте свою карту - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Сканировать - Настройки приложения - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - WalletConnect - Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Сбросить карту - Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. - Выберите сеть - Отсканируйте карту - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - C возвращением! - Используйте %s или код доступа для входа в приложение - Войти с %s - Сканировать карту - Сохраните ваш кошелек - Вы хотите использовать %s? - Вы хотите использовать биометрию? - Доступ в приложение - Войдите в приложение и следите за своим балансом без сканирования карты - Код доступа - Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Использовать %s - Использовать биометрию - Новый функционал - Мои кошельки - Мультивалютные - Одновалютные - Добавить новый кошелек - Переименование кошелька - Имя кошелька - Разблокировать все с %s - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Внимание - Приложите карту с логотипом Visa - Недостаточно средств для активации - Пожалуйста обратитесь в службу поддержки - Ввод одинаковых цифр является не безопасным - Данный Код доступа может быть легко взломан - Код доступа - Подключиться - Верификация клиента - Подтвердите свою личность - Установить Код доступа - Зарегистрироваться - Верифицировать (Utorg) - Обновить - Подключите свою карту - Подтвердите свою личность - Подтверждение личности в процессе - Подключите вашу карту к децентрализованной платежной системе - Для начала работы с картой вам необходимо завершить процесс подтверждения личности - Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. - Код доступа - Установите Код доступа для вашей SaltPay карты - Чат поддержки - Пожалуйста, удерживайте карту до завершения операции - Для начала процесса бэкапа вам необходимо добавить Tangem карту - Бэкап карта не добавлена - Бэкап карта создана - Завершите процесс бэкапа создав код доступа - Приготовьте SaltPay карту - Приложите SaltPay карту - Приложите Tangem карту - Чат - Запросить %s - Для начала работы просто запросите начисление wxDai на свой кошелек - Запросить - Поздравляем! Ваша платежная крипто карта теперь активирована! - Запрашивается - Это займет несколько секунд - Что-то пошло не так - Более подробная информация отправлена на ваш адрес электронной почты. - Вы хотите выйти из процесса активации? - В этом случае вам будет необходимо начать процесс заново. - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Реферальная программа - Приведи друга в Tangem - Вы - Получите - на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг - Ваш друг - Получит - %s скидку - при покупке карточки на сайте tangem.com - Ваши друзья купили - Ваш персональный код - Участвовать - условия участия - Нажимая на эту кнопку вы принимаете - Вы приняли - в реферальной программе - Внутренняя ошибка: не удается найти менеджер кошельков - Баланс: %s - Поделиться - Копировать - Успешно - Дать разрешение - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Количество %s - Ваш кошелек - Отправитель - Подтвердить - Обмен %s на - Обмен - Недостаточно средств - Дать разрешение - Разрешить и обменять - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. - Персональный код скопирован! - Купи Tangem Wallet со скидкой!\n%s - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. - Неверный адрес - Этот кошелек уже был сохранен, вы можете добавить другой - Удалить - Privacy policy - Включите биометрическую аутентификацию - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включить - Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. - %d выбрано - биометрическую аутентификацию - биометрией - Включите биометрическую аутентификацию - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App - Отсканируйте карту - diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings.xml similarity index 51% rename from core/res/src/main/res/values/strings-app.xml rename to core/res/src/main/res/values/strings.xml index 80bfd3967b..4027017a83 100644 --- a/core/res/src/main/res/values/strings-app.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Retry + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scan card + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated! + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Balance: %s + Share + Copy + Success + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Invalid address + This wallet has already been saved, you can add another one + Delete + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Save changes Warning @@ -54,7 +272,7 @@ Long Tap This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode - Before executing any command entailing a change of the card state you will have to enter the passcode. + Before executing any command entailing a change of the card state, you will have to enter the passcode. Access code You will have to submit the correct access code before scanning the card This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Error OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml deleted file mode 100644 index 673ced5d90..0000000000 --- a/core/res/src/main/res/values/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Retry - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change Access Code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the Card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scan card - Save your wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check your email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Balance: %s - Share - Copy - Success - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Invalid address - This wallet has already been saved, you can add another one - Delete - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values/strings_plurals_final.xml b/core/res/src/main/res/values/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values/strings_plurals_final.xml rename to core/res/src/main/res/values/strings_plurals.xml From a0d7410df266d4da85ed92a64481e3b3bb139d4b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Dec 2022 11:37:56 +0300 Subject: [PATCH 23/42] Updated on 2026-08-14 --- .../cardsettings/CardSettingsScreenState.kt | 19 ++--- .../ui/cardsettings/CardSettingsViewModel.kt | 13 ++- .../details/ui/resetcard/ResetCardScreen.kt | 79 ++++++++----------- .../ui/resetcard/ResetCardScreenState.kt | 3 + .../ui/resetcard/ResetCardViewModel.kt | 6 ++ core/res/src/main/res/values-de/strings.xml | 6 +- core/res/src/main/res/values-fr/strings.xml | 6 +- core/res/src/main/res/values-it/strings.xml | 6 +- core/res/src/main/res/values-ru/strings.xml | 10 +-- core/res/src/main/res/values/strings.xml | 10 +-- 10 files changed, 83 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index e7354df5c5..927780750e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -15,18 +15,18 @@ data class CardSettingsScreenState( ) sealed class CardInfo( - val titleRes: TextReference, val subtitle: TextReference, val clickable: Boolean = false, + val titleRes: TextReference, + val subtitle: TextReference, + val clickable: Boolean = false, ) { class CardId(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_cid), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class Issuer(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_issuer), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class SignedHashes(hashes: String) : CardInfo( @@ -34,10 +34,7 @@ sealed class CardInfo( subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes), ) - class SecurityMode( - securityOption: SecurityOption, - clickable: Boolean, - ) : CardInfo( + class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_security_mode), subtitle = TextReference.Res(securityOption.toTitleRes()), clickable = clickable, @@ -49,9 +46,9 @@ sealed class CardInfo( clickable = true, ) - object ResetToFactorySettings : CardInfo( + class ResetToFactorySettings(subtitle: TextReference.Res) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = TextReference.Res(R.string.card_settings_reset_card_to_factory_footer), + subtitle = subtitle, clickable = true, ) } 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 44f5cdb8d2..ce9da7f8b0 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 @@ -8,6 +8,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { @@ -46,7 +47,17 @@ class CardSettingsViewModel(private val store: Store) { cardDetails.add(CardInfo.ChangeAccessCode) } if (state.resetCardAllowed) { - cardDetails.add(CardInfo.ResetToFactorySettings) + cardDetails.add( + CardInfo.ResetToFactorySettings( + subtitle = TextReference.Res( + if (state.card.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, + ), + ), + ) } CardSettingsScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 85d53a13eb..944e4c715f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -32,77 +32,64 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun ResetCardScreen( - state: ResetCardScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { - SettingsScreensScaffold( - content = { ResetCardView(state = state, modifier = modifier) }, - onBackClick = onBackPressed, - backgroundColor = Color.Transparent, - ) +fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { + SettingsScreensScaffold( + content = { ResetCardView(state = state) }, + onBackClick = onBackPressed, + backgroundColor = Color.Transparent, + ) } @Composable -fun ResetCardView( - state: ResetCardScreenState, - modifier: Modifier = Modifier, -) { +fun ResetCardView(state: ResetCardScreenState) { Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.SpaceBetween, ) { - Box( - modifier = modifier, - ) { + Box { Image( painter = painterResource(id = R.drawable.ic_reset_background), - contentDescription = "", - modifier = modifier.offset(y = (-82).dp), + contentDescription = null, + modifier = Modifier.offset(y = (-82).dp), ) ScreenTitle(titleRes = R.string.card_settings_reset_card_to_factory) } - Spacer( - modifier = modifier.weight(1f), - ) + Spacer(modifier = Modifier.weight(1f)) Column( - modifier = modifier - .offset(y = (-32).dp), + modifier = Modifier.offset(y = (-32).dp), verticalArrangement = Arrangement.Bottom, ) { Text( text = stringResource(id = R.string.common_attention), - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.headline3, color = colorResource(id = R.color.text_primary_1), ) - Spacer(modifier = modifier.size(24.dp)) + Spacer(modifier = Modifier.size(24.dp)) Text( - text = stringResource(id = R.string.reset_card_to_factory_message), - modifier = modifier - .padding(start = 20.dp, end = 20.dp), + text = stringResource(id = state.descriptionResId), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.body1, color = colorResource(id = R.color.text_secondary), ) - Spacer(modifier = modifier.size(28.dp)) + Spacer(modifier = Modifier.size(28.dp)) Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .clickable( - onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, - ) + onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, + ) .padding(top = 16.dp, bottom = 16.dp), ) { IconToggleButton( checked = state.accepted, onCheckedChange = state.onAcceptWarningToggleClick, - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), ) { Icon( painter = painterResource( @@ -124,15 +111,13 @@ fun ResetCardView( text = stringResource(id = R.string.reset_card_to_factory_warning_message), style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), - modifier = modifier - .padding(end = 20.dp), + modifier = Modifier.padding(end = 20.dp), ) } - Spacer(modifier = modifier.size(16.dp)) + Spacer(modifier = Modifier.size(16.dp)) Box( - modifier = modifier - .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 32.dp), ) { DetailsMainButton( title = stringResource(id = R.string.reset_card_to_factory_button_title), @@ -146,8 +131,14 @@ fun ResetCardView( @Composable @Preview -fun ResetCardScreenPreview( - -) { - ResetCardScreen(state = ResetCardScreenState(onAcceptWarningToggleClick = {}, accepted = true) {}, {}) +fun ResetCardScreenPreview() { + ResetCardScreen( + state = ResetCardScreenState( + descriptionResId = R.string.reset_card_without_backup_to_factory_message, + accepted = false, + onAcceptWarningToggleClick = {}, + onResetButtonClick = {}, + ), + onBackPressed = {}, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index f658eac463..54693e7557 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -1,6 +1,9 @@ package com.tangem.tap.features.details.ui.resetcard +import androidx.annotation.StringRes + data class ResetCardScreenState( + @StringRes val descriptionResId: Int, val accepted: Boolean = false, val onAcceptWarningToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, 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 15f891dba3..1606631d68 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 @@ -3,12 +3,18 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class ResetCardViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): ResetCardScreenState { return ResetCardScreenState( + descriptionResId = if (state?.card?.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, accepted = state?.resetConfirmed ?: false, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 9f2035db71..0bee218f2b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. Tippen um zu signieren - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Tippen Sie um den Zugangscode zu ändern Tippen Sie um den Passcode zu ändern Nutzungsbedingungen diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index cf498616e2..237e0965f6 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Cette carte n\'est pas conçue pour fonctionner avec Tangem La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. Touchez pour signer - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe Conditions d\'utilisation diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index af7ce360a9..d6b0c63ee0 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Questa carta non è progettata per funzionare con Tangem La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. Avvicina per firmare - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Avvicina per modificare il codice di accesso Avvicina per modificare la password Termini del servizio diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index a715487104..177687671b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -99,11 +99,11 @@ Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. WalletConnect Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить. + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа. Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку Сбросить карту Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть Отсканируйте карту Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту @@ -279,9 +279,9 @@ Эта карта не предназначена для работы с этим приложением Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. Нажмите, чтобы подписать - Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. - Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. - Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции Условия использования Нет соединения с интернетом PayString не поддерживается блокчейном diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4027017a83..f2d00c18af 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,9 +279,9 @@ This card is not designed to work with this app The card you scanned is a development card. Don\'t accept it as a payment. Tap to sign - To create wallet, connect your phone and the card exactly as it shown above - To change the access code, connect your phone and the card exactly as it shown above - To change the passcode, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation Terms of Service No internet connection PayString unsupported by blockchain From 3e0385dc7f5977eb2d09c18344e41704e5ce42af Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 10:40:02 +0300 Subject: [PATCH 24/42] Updated on 2026-08-14 --- .../redux/reducers/MultiWalletReducer.kt | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 7cb35da559..afb523a5ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -1,12 +1,15 @@ package com.tangem.tap.features.wallet.redux.reducers +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.common.extensions.guard +import com.tangem.tap.common.extensions.dispatchToastNotification import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency @@ -25,6 +28,8 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager +import com.tangem.wallet.R import java.math.BigDecimal class MultiWalletReducer { @@ -106,16 +111,22 @@ class MultiWalletReducer { newState } } - is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, state) - } - is WalletAction.MultiWallet.AddToken -> { - addTokens(listOf(action.token), action.blockchain, state) - } + is WalletAction.MultiWallet.AddTokens -> addTokens(action.tokens, action.blockchain, state) + is WalletAction.MultiWallet.AddToken -> addTokens(listOf(action.token), action.blockchain, state) is WalletAction.MultiWallet.TokenLoaded -> { val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) - val walletManager = state.getWalletManager(currency).guard { - throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be not NULL") + val walletManager = state.getWalletManager(currency) + if (walletManager == null) { + if (userWalletsListManager.hasSavedUserWallets) { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Welcome)) + } else { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) + } + FirebaseCrashlytics.getInstance().recordException( + IllegalStateException("MultiWallet.TokenLoaded: walletManager is null"), + ) + store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) + return state } val wallet = walletManager.wallet val pendingTransactions = wallet.getPendingTransactions() @@ -170,41 +181,28 @@ class MultiWalletReducer { action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } updatedState } - is WalletAction.MultiWallet.SetPrimaryBlockchain -> - state.copy(primaryBlockchain = action.blockchain) - - is WalletAction.MultiWallet.SetPrimaryToken -> - state.copy(primaryToken = action.token) + is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain) + is WalletAction.MultiWallet.SetPrimaryToken -> state.copy(primaryToken = action.token) is WalletAction.MultiWallet.SaveCurrencies -> state - is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy( - showBackupWarning = action.show, - ) - is WalletAction.MultiWallet.AddMissingDerivations -> state.copy( - missingDerivations = action.blockchains, - ) + is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) is WalletAction.MultiWallet.BackupWallet -> state - is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy( - state = ProgressState.Loading, - ) + is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading) } } private fun findWalletRent(walletStore: WalletStore?): WalletRent? { - return walletStore?.walletsData?.firstOrNull { - it.walletRent != null - }?.walletRent + return walletStore?.walletsData?.firstOrNull { it.walletRent != null }?.walletRent } private fun getExistentialDeposit(walletManager: WalletManager?): String? { return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()?.toPlainString() } -} -private fun addTokens( - tokens: List, blockchain: BlockchainNetwork, state: WalletState, -): WalletState { - val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } - return state.updateWalletsData(wallets) + private fun addTokens(tokens: List, blockchain: BlockchainNetwork, state: WalletState): WalletState { + val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } + return state.updateWalletsData(wallets) + } } fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? { From 7d56fc1efa8ccc56ff5c80a049bc975fe1a5702c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 15:28:55 +0300 Subject: [PATCH 25/42] Updated on 2026-08-14 --- .../tap/common/redux/navigation/NavigationMiddleware.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 871ef1ea5c..28357c1efd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -57,11 +57,8 @@ val navigationMiddleware: Middleware = { _, state -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Settings.ACTION_BIOMETRIC_ENROLL } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { - Settings.ACTION_FINGERPRINT_ENROLL - } else -> { - Settings.ACTION_SETTINGS + Settings.ACTION_SECURITY_SETTINGS } } val intent = Intent(settingsAction).apply { From 92b721ecad128e62d3aa03d35b8e52b3ec4a421e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 16:29:09 +0400 Subject: [PATCH 26/42] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 8a67196de1..f01805dd39 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.ui +import android.content.Context import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -9,6 +10,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionInflater @@ -60,6 +62,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber() + override fun onAttach(context: Context) { + super.onAttach(context) + activity?.lifecycleScope?.launchWhenCreated { + viewModel.launch() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -82,7 +91,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Date: Sat, 31 Dec 2022 15:34:47 +0300 Subject: [PATCH 27/42] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f31620d64c..9233ced603 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'AND-2684-develop-139-hot-fix-for-3.55-release-149', + tangem_blockchain_sdk: 'AND-2752-build-149-hot-fix-for-3.55-150', // tangem_blockchain_sdk: '0.0.1', ] From 8f6200b7d50e61acc37cf9853d3608e79e4b7eee Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 8 Jan 2023 20:34:56 +0300 Subject: [PATCH 28/42] Updated on 2026-08-14 --- .../tap/domain/scanCard/ScanCardProcessor.kt | 32 ++++++++++++------- .../disclaimer/redux/DisclaimerAction.kt | 15 ++++----- .../disclaimer/redux/DisclaimerMiddleware.kt | 14 ++++---- .../disclaimer/redux/DisclaimerReducer.kt | 8 +++-- .../disclaimer/redux/DisclaimerState.kt | 7 ++-- .../redux/WalletSelectorMiddleware.kt | 14 +++++--- .../tap/persistence/DisclaimerPrefStorage.kt | 5 +++ 7 files changed, 57 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index d940a338ad..42e199481b 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -22,6 +22,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback import com.tangem.tap.features.disclaimer.redux.DisclaimerType import com.tangem.tap.features.disclaimer.redux.isAccepted import com.tangem.tap.features.onboarding.OnboardingHelper @@ -48,7 +49,8 @@ object ScanCardProcessor { cardId: String? = null, onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, - onWalletNotCreated: suspend (() -> Unit) = {}, + onWalletNotCreated: suspend () -> Unit = {}, + disclaimerWillShow: () -> Unit = {}, onFailure: suspend (error: TangemError) -> Unit = {}, onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, ) = withMainContext { @@ -83,6 +85,8 @@ object ScanCardProcessor { showDisclaimerIfNeed( scanResponse = scanResponse1, onProgressStateChange = onProgressStateChange, + disclaimerWillShow = disclaimerWillShow, + onFailure = onFailure, nextHandler = { scanResponse2 -> onScanSuccess( scanResponse = scanResponse2, @@ -128,8 +132,10 @@ object ScanCardProcessor { private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, + crossinline disclaimerWillShow: () -> Unit = {}, crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline nextHandler: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, ) { val disclaimerType = DisclaimerType.get(scanResponse) store.dispatchOnMain(DisclaimerAction.SetDisclaimerType(disclaimerType)) @@ -138,18 +144,22 @@ object ScanCardProcessor { nextHandler((scanResponse)) } else scope.launch(Dispatchers.Main) { delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() store.dispatchOnMain( DisclaimerAction.Show( - onAcceptCallback = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismissCallback = { - scope.launch(Dispatchers.Main) { - onProgressStateChange(false) - } - }, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onProgressStateChange(false) + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index 5461c66441..395137228c 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -10,18 +10,17 @@ sealed class DisclaimerAction : Action { ) : DisclaimerAction() data class Show( - val onAcceptCallback: VoidCallback? = null, - val onDismissCallback: VoidCallback? = null, + val callback: DisclaimerCallback? = null, ) : DisclaimerAction() data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() - internal data class SetCallbacks( - val onAcceptCallback: VoidCallback? = null, - val onDismissCallback: VoidCallback? = null, - ) : DisclaimerAction() - object OnBackPressed : DisclaimerAction() -} \ No newline at end of file +} + +data class DisclaimerCallback( + val onAccept: VoidCallback? = null, + val onDismiss: VoidCallback? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index 01e5de971a..a604d3275f 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -28,33 +28,31 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { } is DisclaimerAction.Show -> { handleUpdateState = state.type.createUpdateState() - store.dispatch(DisclaimerAction.SetCallbacks(action.onAcceptCallback, action.onDismissCallback)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } is DisclaimerAction.AcceptDisclaimer -> { action.type.accept() handleUpdateState = action.type.createUpdateState() store.dispatch(NavigationAction.PopBackTo()) - state.onAcceptCallback?.invoke() - store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + state.callback?.onAccept?.invoke() } is DisclaimerAction.OnBackPressed -> { - state.onDismissCallback?.invoke() - store.dispatch(DisclaimerAction.SetCallbacks(null, null)) store.dispatch(NavigationAction.PopBackTo()) - + state.callback?.onDismiss?.invoke() } } } private fun DisclaimerType.accept() = when (this) { - DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted = true DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted = true + DisclaimerType.Start2Coin -> preferencesStorage.disclaimerPrefStorage.hasStart2CoinTosAccepted = true + DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted = true } fun DisclaimerType.isAccepted(): Boolean = when (this) { - DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted + DisclaimerType.Start2Coin -> preferencesStorage.disclaimerPrefStorage.hasStart2CoinTosAccepted + DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted } private fun DisclaimerType.createUpdateState(): DisclaimerAction.UpdateState { diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 766c6a42ca..d44d02e993 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -15,13 +15,15 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { val disclaimerState = state.disclaimerState return when (action) { + is DisclaimerAction.Show -> disclaimerState.copy( + callback = action.callback, + ) is DisclaimerAction.UpdateState -> disclaimerState.copy( type = action.type, accepted = action.accepted, ) - is DisclaimerAction.SetCallbacks -> disclaimerState.copy( - onAcceptCallback = action.onAcceptCallback, - onDismissCallback = action.onDismissCallback, + is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy( + callback = null, ) else -> disclaimerState } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index d7ca859973..d8dcbddd2a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -1,23 +1,23 @@ package com.tangem.tap.features.disclaimer.redux import android.net.Uri -import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.domain.common.TapWorkarounds.isStart2Coin import org.rekotlin.StateType data class DisclaimerState( val accepted: Boolean = false, val type: DisclaimerType = DisclaimerType.Tangem, - val onAcceptCallback: VoidCallback? = null, - val onDismissCallback: VoidCallback? = null, + val callback: DisclaimerCallback? = null, ) : StateType sealed class DisclaimerType( val uri: Uri, ) { object Tangem : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html")) + object Start2Coin : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html")) object SaltPay : DisclaimerType(Uri.parse("https://tangem.com/soltpay_tos.html")) companion object { @@ -25,6 +25,7 @@ sealed class DisclaimerType( fun get(card: CardDTO): DisclaimerType = when { card.isSaltPay -> SaltPay + card.isStart2Coin -> Start2Coin else -> Tangem } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index cc6642743f..6d284c8b95 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -138,7 +138,16 @@ internal class WalletSelectorMiddleware { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) + ScanCardProcessor.scan( + onWalletNotCreated = { + // No need to rollback policy, continue with the policy set before the card scan + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + }, + disclaimerWillShow = { + store.dispatchOnMain(NavigationAction.PopBackTo()) + }, onSuccess = { scanResponse -> saveUserWalletAndPopBackToWalletScreen(scanResponse) .doOnFailure { error -> @@ -152,11 +161,6 @@ internal class WalletSelectorMiddleware { tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) }, - onWalletNotCreated = { - // No need to rollback policy, continue with the policy set before the card scan - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - }, ) } diff --git a/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt index 7c016eaa5d..ffffa7ba40 100644 --- a/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt @@ -14,12 +14,17 @@ class DisclaimerPrefStorage( get() = preferences.getBoolean(KEY_TANGEM_TOS, false) set(value) = preferences.edit { putBoolean(KEY_TANGEM_TOS, value) } + var hasStart2CoinTosAccepted: Boolean + get() = preferences.getBoolean(KEY_START_2_COIN_TOS, false) + set(value) = preferences.edit { putBoolean(KEY_START_2_COIN_TOS, value) } + var hasSaltPayTosAccepted: Boolean get() = preferences.getBoolean(KEY_SALT_PAY_TOS, false) set(value) = preferences.edit { putBoolean(KEY_SALT_PAY_TOS, value) } companion object { private const val KEY_TANGEM_TOS = "tangem_tos_accepted" + private const val KEY_START_2_COIN_TOS = "s2c_tos_accepted" private const val KEY_SALT_PAY_TOS = "saltPay_tos_accepted" } } \ No newline at end of file From 6587833faddb55a5e2390d922002ece20f7a3126 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 15:19:25 +0300 Subject: [PATCH 29/42] Updated on 2026-08-14 --- .../tangem/tap/domain/extensions/WalletManagerFactory.kt | 2 +- .../tangem/tap/domain/model/builders/UserWalletBuilder.kt | 3 ++- .../tap/domain/model/builders/UserWalletIdBuilder.kt | 1 + .../products/wallet/saltPay/GnosisRegistrator.kt | 1 - buildSrc/src/main/java/Versions.kt | 2 +- .../java/com/tangem/domain/common/SaltPayWorkaround.kt | 7 ------- .../src/main/java/com/tangem/domain/common/ScanResponse.kt | 4 +--- .../java/com/tangem/domain/common/extensions/Blockchain.kt | 4 +--- 8 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 874e562a2a..989793e59a 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -143,7 +143,7 @@ fun WalletManagerFactory.makeSaltPayWalletManager( scanResponse: ScanResponse, ): EthereumWalletManager { val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay && blockchain != Blockchain.SaltPayTestnet) + if (blockchain != Blockchain.SaltPay) throw IllegalArgumentException() val token = SaltPayWorkaround.tokenFrom(blockchain) diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 9148486bc6..22cc834b2a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -29,9 +29,9 @@ class UserWalletBuilder( ProductType.Note -> "Note" ProductType.Twins -> "Twin" ProductType.SaltPay -> "SaltPay" + ProductType.Start2Coin -> "Start2Coin" ProductType.Wallet -> when { card.isBackupNotAllowed -> "Tangem card" - card.isStart2Coin -> "Start2Coin" else -> "Wallet" } } @@ -41,6 +41,7 @@ class UserWalletBuilder( ProductType.Note -> false ProductType.Twins -> false ProductType.SaltPay -> false + ProductType.Start2Coin -> false ProductType.Wallet -> when { card.isStart2Coin -> false card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt index 1fdef7c37c..ebe875ad66 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -57,6 +57,7 @@ class UserWalletIdBuilder private constructor( ProductType.Note, ProductType.Wallet, ProductType.SaltPay, + ProductType.Start2Coin, -> null }, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 771fca7d4b..f0319a11ba 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -39,7 +39,6 @@ class GnosisRegistrator( private val otpProcessorContractAddress: String = when (walletManager.wallet.blockchain) { Blockchain.SaltPay -> "0x3B4397C817A26521Df8bD01a949AFDE2251d91C2" - Blockchain.SaltPayTestnet -> "0x710BF23486b549836509a08c184cE0188830f197" else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") } diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 89471ec92c..1970c7140f 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,7 +59,7 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-141" + const val tangemBlockchainSdk = "develop-151" const val tangemCardSgk = "develop-179" // endregion Tangem diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index c397160c2d..39378b30a9 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -15,13 +15,6 @@ object SaltPayWorkaround { decimals = 18, id = "wrapped-xdai", ) - Blockchain.SaltPayTestnet -> Token( - name = "WXDAI Test", - symbol = "MyERC20", - contractAddress = "0x69cca8D8295de046C7c14019D9029Ccc77987A48", - decimals = 0, - id = "wrapped-xdai", - ) else -> throw IllegalArgumentException() } } diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index cfcd3089ff..c5576f6037 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -11,8 +11,6 @@ import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isSaltPayVisa import com.tangem.domain.common.TapWorkarounds.isSaltPayWallet -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.operations.CommandResponse @@ -33,7 +31,7 @@ data class ScanResponse( fun getBlockchain(): Blockchain { return when (productType) { ProductType.Start2Coin -> if (card.isTestCard) Blockchain.BitcoinTestnet else Blockchain.Bitcoin - ProductType.SaltPay -> if (card.isTestCard) Blockchain.SaltPayTestnet else Blockchain.SaltPay + ProductType.SaltPay -> Blockchain.SaltPay ProductType.Note -> card.getTangemNoteBlockchain() ?: Blockchain.Unknown else -> { val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 0fde777794..c684dbe3f5 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -48,7 +48,6 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "optimistic-ethereum/test" -> Blockchain.OptimismTestnet "dash" -> Blockchain.Dash "sxdai" -> Blockchain.SaltPay - "sxdai/test" -> Blockchain.SaltPayTestnet else -> null } } @@ -101,7 +100,6 @@ fun Blockchain.toNetworkId(): String { Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" Blockchain.SaltPay -> "sxdai" - Blockchain.SaltPayTestnet -> "sxdai/test" } } @@ -133,7 +131,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" - Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "xdai" + Blockchain.SaltPay -> "xdai" Blockchain.Unknown -> "unknown" } } From 95ca2b1249023e5cdb98b9f324c04ddfc5d86816 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 12:09:33 +0300 Subject: [PATCH 30/42] Updated on 2026-08-14 --- .../details/ui/details/DetailsScreenState.kt | 2 +- .../details/ui/details/DetailsViewModel.kt | 6 +- .../com/tangem/core/ui/components/Cards.kt | 17 ++---- .../appbar/AppBarWithAdditionalButtons.kt | 14 ++--- .../components/appbar/AppBarWithBackButton.kt | 13 ++-- .../components/atoms/BottomSheetIndicator.kt | 61 ------------------- .../referral/presentation/build.gradle.kts | 1 + .../feature/referral/ReferralFragment.kt | 3 +- .../ui/AgreementBottomSheetContent.kt | 10 ++- .../feature/referral/ui/AgreementText.kt | 16 ++--- .../referral/ui/NonParticipateBottomBlock.kt | 6 +- .../referral/ui/ParticipateBottomBlock.kt | 19 +++--- .../feature/referral/ui/ReferralScreen.kt | 49 +++++++-------- 13 files changed, 64 insertions(+), 153 deletions(-) delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index e55525ae1d..065dc0dfc8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -23,7 +23,7 @@ enum class SettingsElement( WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), Chat(R.drawable.ic_chat, R.string.details_chat), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), - // ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), + ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency), AppSettings(R.drawable.ic_settings, R.string.app_settings_title), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 319524eccd..af845bb449 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -95,9 +95,9 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { // TODO: To be available later } - // SettingsElement.ReferralProgram -> { - // store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) - // } + SettingsElement.ReferralProgram -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) + } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 90d5ee1ce4..0ece569d8d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -6,18 +6,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.textColor @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable @@ -48,7 +43,7 @@ fun Preview_SimpleInfoCard_InDarkTheme() { fun SmallInfoCard(startText: String, endText: String) { Surface( shape = RoundedCornerShape(TangemTheme.dimens.radius12), - color = MaterialTheme.colors.secondary, + color = TangemTheme.colors.background.secondary, elevation = TangemTheme.dimens.elevation2, ) { Row( @@ -60,19 +55,19 @@ fun SmallInfoCard(startText: String, endText: String) { vertical = TangemTheme.dimens.spacing12, ), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Text( text = startText, - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + color = TangemTheme.colors.text.tertiary, maxLines = 1, - style = MaterialTheme.typography.subtitle2 + style = TangemTheme.typography.subtitle2, ) Text( text = endText, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + color = TangemTheme.colors.text.primary1, maxLines = 1, - style = MaterialTheme.typography.body2 + style = TangemTheme.typography.body2, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index 6416a8343f..c4a74e3fb9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -7,21 +7,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.models.AdditionalButton -import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.iconColor -import com.tangem.core.ui.res.textColor /** * App bar with title and two additional buttons @@ -50,7 +44,7 @@ fun AppBarWithAdditionalButtons( painter = painterResource(id = startButton.iconRes), contentDescription = null, modifier = Modifier.size(TangemTheme.dimens.size24), - tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + tint = TangemTheme.colors.icon.primary1, ) } } @@ -58,9 +52,9 @@ fun AppBarWithAdditionalButtons( Text( text = text, modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + color = TangemTheme.colors.text.primary1, maxLines = 1, - style = MaterialTheme.typography.subtitle1, + style = TangemTheme.typography.subtitle1, ) if (endButton != null) { @@ -69,7 +63,7 @@ fun AppBarWithAdditionalButtons( painter = painterResource(id = endButton.iconRes), contentDescription = null, modifier = Modifier.size(TangemTheme.dimens.size24), - tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + tint = TangemTheme.colors.icon.primary1, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 3f4c5efa9e..376c242719 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,6 +1,5 @@ package com.tangem.core.ui.components.appbar -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -8,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -29,10 +27,9 @@ import com.tangem.core.ui.res.TangemTheme * >Figma component */ @Composable -fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { +fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit, modifier: Modifier = Modifier) { Row( - modifier = Modifier - .background(MaterialTheme.colors.primary) + modifier = modifier .fillMaxWidth() .padding(all = dimensionResource(R.dimen.spacing16)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), @@ -44,14 +41,14 @@ fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { modifier = Modifier .size(size = dimensionResource(R.dimen.size24)) .clickable { onBackClick() }, - tint = MaterialTheme.colors.onPrimary, + tint = TangemTheme.colors.icon.primary1, ) if (!text.isNullOrBlank()) { Text( text = text, - color = MaterialTheme.colors.onPrimary, + color = TangemTheme.colors.text.primary1, maxLines = 1, - style = MaterialTheme.typography.subtitle1, + style = TangemTheme.typography.subtitle1, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt deleted file mode 100644 index 907e68d0a2..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.tangem.core.ui.components.atoms - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.R -import com.tangem.core.ui.res.IconColorType -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.iconColor - -/** - * Bottom sheet indicator - * - * @see - * Figma Component - */ -@Deprecated(message = "Use Hand component instead") -@Composable -fun BottomSheetIndicator() { - Box( - modifier = Modifier - .fillMaxWidth() - .height(TangemTheme.dimens.size20), - contentAlignment = Alignment.Center, - ) { - Box( - modifier = Modifier - .width(TangemTheme.dimens.size32) - .height(TangemTheme.dimens.size4) - .background( - color = MaterialTheme.colors.iconColor(type = IconColorType.INACTIVE), - shape = RoundedCornerShape(TangemTheme.dimens.radius2), - ), - ) - } -} - -@Preview(heightDp = 20, showBackground = true) -@Composable -fun Preview_BottomSheetIndicator_InLightTheme() { - TangemTheme(isDark = false) { - BottomSheetIndicator() - } -} - -@Preview(heightDp = 20, showBackground = true) -@Composable -fun Preview_BottomSheetIndicator_InDarkTheme() { - TangemTheme(isDark = true) { - BottomSheetIndicator() - } -} \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index a94eecb86b..124a76eb5e 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(AndroidX.appCompat) implementation(AndroidX.fragmentKtx) implementation(AndroidX.lifecycleViewModelKtx) + implementation(Library.materialComponent) /** Compose */ implementation(Compose.foundation) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index 6af2cc4e25..a378ae77c4 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -1,7 +1,6 @@ package com.tangem.feature.referral import android.os.Bundle -import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,6 +8,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.transition.TransitionInflater import com.tangem.feature.referral.presentation.R import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen @@ -31,6 +31,7 @@ class ReferralFragment : Fragment() { activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager))) return ComposeView(inflater.context).apply { + isTransitionGroup = true setContent { ReferralScreen(stateHolder = viewModel.uiState) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index 5f15d77781..cb652db965 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -9,17 +9,15 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons -import com.tangem.core.ui.components.atoms.BottomSheetIndicator +import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @@ -32,11 +30,11 @@ import com.tangem.feature.referral.presentation.R internal fun AgreementBottomSheetContent(url: String) { Column( modifier = Modifier - .background(color = MaterialTheme.colors.secondary) + .background(color = TangemTheme.colors.background.secondary) .fillMaxWidth() .height(LocalConfiguration.current.screenHeightDp.dp - TangemTheme.dimens.spacing16), ) { - BottomSheetIndicator() + Hand() Column(modifier = Modifier.verticalScroll(rememberScrollState())) { AppBarWithAdditionalButtons(text = stringResource(id = R.string.details_referral_title)) AgreementHtmlView(url = url) @@ -47,7 +45,7 @@ internal fun AgreementBottomSheetContent(url: String) { @Composable private fun AgreementHtmlView(url: String) { AndroidView( - modifier = Modifier.background(MaterialTheme.colors.secondary), + modifier = Modifier.background(TangemTheme.colors.background.secondary), factory = { WebView(it).apply { layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index cb77fb06d7..57cba8a1dd 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -18,8 +16,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R @Composable @@ -30,7 +26,7 @@ internal fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit modifier = Modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.spacing54), - style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center), + style = TangemTheme.typography.caption.copy(textAlign = TextAlign.Center), maxLines = 2, onClick = { val clickableSpanStyle = requireNotNull(agreementText.spanStyles.getOrNull(1)) @@ -44,13 +40,13 @@ internal fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit @Composable private fun annotatedAgreementString(firstPart: String): AnnotatedString { return buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) { + withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { append("$firstPart ") } - withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.ACCENT))) { + withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { append(stringResource(id = R.string.common_terms_and_conditions)) } - withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) { + withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { append(" ${stringResource(id = R.string.referral_tos_suffix)}") } } @@ -60,7 +56,7 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { @Composable fun Preview_AgreementText_InLightTheme() { TangemTheme(isDark = false) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) { AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) } } @@ -70,7 +66,7 @@ fun Preview_AgreementText_InLightTheme() { @Composable fun Preview_AgreementText_InDarkTheme() { TangemTheme(isDark = true) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) { AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 29e86058e8..5fcbfc52ed 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -3,10 +3,8 @@ package com.tangem.feature.referral.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryEndIconButton @@ -33,7 +31,7 @@ internal fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onPartici @Composable fun Preview_NonParticipateBottomBlock_InLightTheme() { TangemTheme(isDark = false) { - Column(Modifier.background(MaterialTheme.colors.primary)) { + Column(Modifier.background(TangemTheme.colors.background.primary)) { NonParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } } @@ -43,7 +41,7 @@ fun Preview_NonParticipateBottomBlock_InLightTheme() { @Composable fun Preview_NonParticipateBottomBlock_InDarkTheme() { TangemTheme(isDark = true) { - Column(Modifier.background(MaterialTheme.colors.primary)) { + Column(Modifier.background(TangemTheme.colors.background.primary)) { NonParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 0027b80634..33e376ad8b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -28,8 +27,6 @@ import androidx.core.content.ContextCompat.startActivity import com.tangem.core.ui.components.PrimaryStartIconButton import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R @OptIn(ExperimentalComposeUiApi::class) @@ -55,7 +52,7 @@ internal fun ParticipateBottomBlock( endText = pluralStringResource( id = R.plurals.referral_wallets_purchased_count, count = purchasedWalletCount, - purchasedWalletCount + purchasedWalletCount, ), ) PersonalCodeCard(code = code) @@ -73,7 +70,7 @@ private fun PersonalCodeCard(code: String) { shape = RoundedCornerShape(TangemTheme.dimens.radius12), ) .background( - color = MaterialTheme.colors.secondary, + color = TangemTheme.colors.background.secondary, shape = RoundedCornerShape(TangemTheme.dimens.radius12), ) .fillMaxWidth() @@ -83,15 +80,15 @@ private fun PersonalCodeCard(code: String) { ) { Text( text = stringResource(id = R.string.referral_promo_code_title), - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + color = TangemTheme.colors.text.tertiary, maxLines = 1, - style = MaterialTheme.typography.subtitle2, + style = TangemTheme.typography.subtitle2, ) Text( text = code, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + color = TangemTheme.colors.text.primary1, maxLines = 1, - style = MaterialTheme.typography.h2, + style = TangemTheme.typography.h2, ) } } @@ -143,7 +140,7 @@ private fun Context.shareText(text: String) { @Composable fun Preview_ParticipateBottomBlock_InLightTheme() { TangemTheme(isDark = false) { - Column(Modifier.background(MaterialTheme.colors.primary)) { + Column(Modifier.background(TangemTheme.colors.background.primary)) { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", @@ -159,7 +156,7 @@ fun Preview_ParticipateBottomBlock_InLightTheme() { @Composable fun Preview_ParticipateBottomBlock_InDarkTheme() { TangemTheme(isDark = true) { - Column(Modifier.background(MaterialTheme.colors.primary)) { + Column(Modifier.background(TangemTheme.colors.background.primary)) { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 39d5296a12..92713b0fb2 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -21,7 +22,6 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.Snackbar import androidx.compose.material.SnackbarDuration import androidx.compose.material.SnackbarHost @@ -56,15 +56,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.ButtonColorType -import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemColorPalette -import com.tangem.core.ui.res.TangemColorPalette.Black import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.buttonColor -import com.tangem.core.ui.res.iconColor -import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.models.ReferralStateHolder import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState @@ -131,14 +124,15 @@ private fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked CompositionLocalProvider(LocalOverscrollConfiguration provides null) { LazyColumn( modifier = Modifier - .background(color = MaterialTheme.colors.primary) - .fillMaxWidth(), + .background(color = TangemTheme.colors.background.secondary) + .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { stickyHeader { AppBarWithBackButton( text = stringResource(R.string.details_referral_title), onBackClick = stateHolder.headerState.onBackClicked, + modifier = Modifier.background(color = TangemTheme.colors.background.secondary), ) } item { Header() } @@ -172,10 +166,10 @@ private fun Header() { Text( text = stringResource(id = R.string.referral_title), modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, maxLines = 2, - style = MaterialTheme.typography.h2, + style = TangemTheme.typography.h2, ) VerticalSpacer(spaceResId = R.dimen.spacing16) } @@ -262,7 +256,7 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> Box( modifier = Modifier .background( - color = MaterialTheme.colors.buttonColor(type = ButtonColorType.SECONDARY), + color = TangemTheme.colors.button.secondary, shape = RoundedCornerShape(TangemTheme.dimens.radius16), ) .size(TangemTheme.dimens.size56), @@ -272,7 +266,7 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> painter = painterResource(id = iconResId), contentDescription = null, modifier = Modifier.size(TangemTheme.dimens.size28), - tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + tint = TangemTheme.colors.icon.primary1, ) } infoBlock() @@ -285,7 +279,7 @@ private fun InfoForYou(award: String, networkName: String, address: String? = nu Text( text = buildAnnotatedString { append(stringResource(id = R.string.referral_point_currencies_description_prefix)) - withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1))) { + withStyle(SpanStyle(color = TangemTheme.colors.text.primary1)) { append(" $award ") } append( @@ -296,8 +290,8 @@ private fun InfoForYou(award: String, networkName: String, address: String? = nu ), ) }, - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), - style = MaterialTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, ) } } @@ -317,8 +311,8 @@ private fun InfoForYourFriend(discount: String) { append(" ") append(stringResource(id = R.string.referral_point_discount_description_suffix)) }, - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), - style = MaterialTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, ) } } @@ -328,8 +322,8 @@ private fun ConditionInfo(title: String, subtitleContent: @Composable () -> Unit Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2)) { Text( text = title, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), - style = MaterialTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, ) subtitleContent() } @@ -350,6 +344,7 @@ private fun ShimmerInfo() { .width(TangemTheme.dimens.size102) .height(TangemTheme.dimens.size16) .background(TangemColorPalette.White), + // .background(Color(0xFFF8F8F8)), ) Box( modifier = Modifier @@ -377,9 +372,9 @@ private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { modifier = Modifier.fillMaxWidth(), actionOnNewLine = true, shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), - backgroundColor = Black, - contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - actionColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + backgroundColor = TangemColorPalette.Black, + contentColor = TangemTheme.colors.text.primary2, + actionColor = TangemTheme.colors.text.primary2, elevation = TangemTheme.dimens.elevation3, ) }, @@ -430,7 +425,7 @@ private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { Box( modifier = Modifier .onSizeChanged { snackbarSize = it.width } - .background(Black, RoundedCornerShape(size = TangemTheme.dimens.radius8)) + .background(TangemColorPalette.Black, RoundedCornerShape(size = TangemTheme.dimens.radius8)) .shadow( TangemTheme.dimens.elevation3, RoundedCornerShape(size = TangemTheme.dimens.radius8), @@ -442,8 +437,8 @@ private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { ) { Text( text = it.message, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - style = MaterialTheme.typography.body2, + color = TangemTheme.colors.text.primary2, + style = TangemTheme.typography.body2, ) } }, From 80bfc4444a3a3d48a0cf4fadad566fb0f9b852ec Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 12:22:13 +0300 Subject: [PATCH 31/42] Updated on 2026-08-14 --- core/res/src/main/res/values/strings_plurals.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/res/src/main/res/values/strings_plurals.xml b/core/res/src/main/res/values/strings_plurals.xml index 72449e9325..f7c61fa277 100644 --- a/core/res/src/main/res/values/strings_plurals.xml +++ b/core/res/src/main/res/values/strings_plurals.xml @@ -16,5 +16,6 @@ %d wallets %d wallet + %d wallets From f7b0479994a260b0f5a2fa41bf444eb1151ad97b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 12:37:32 +0300 Subject: [PATCH 32/42] Updated on 2026-08-14 --- .../tap/features/details/ui/details/DetailsViewModel.kt | 1 + .../tangem/feature/referral/viewmodels/ReferralViewModel.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index af845bb449..2cb657c083 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -39,6 +39,7 @@ class DetailsViewModel(private val store: Store) { SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null + SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null else -> it } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index 21bd4b1057..ded54643ff 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -107,9 +107,9 @@ internal class ReferralViewModel @Inject constructor( private fun ReferralData.getDiscountValue(): String { val discountSymbol = when (discountType) { DiscountType.PERCENTAGE -> "%" - DiscountType.VALUE -> error("Value doesn't support") + DiscountType.VALUE -> this.getToken().symbol } - return "$discount $discountSymbol" + return "$discount$discountSymbol" } private fun ReferralData.getToken() = requireNotNull(tokens.firstOrNull()) { "Token list is empty" } From e86de005bff62789f0b77973ac97227304016d9d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 12:46:24 +0300 Subject: [PATCH 33/42] Updated on 2026-08-14 --- .../com/tangem/feature/referral/converters/ReferralConverter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index 7e468c0500..af7910fee0 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -19,7 +19,7 @@ class ReferralConverter @Inject constructor() : Converter Date: Wed, 28 Dec 2022 15:37:05 +0300 Subject: [PATCH 34/42] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/MainActivity.kt | 1 + app/src/main/java/com/tangem/tap/TapApplication.kt | 3 ++- .../java/com/tangem/tap/proxy/AppStateHolder.kt | 4 ++++ .../com/tangem/tap/proxy/DerivationManagerImpl.kt | 13 +++++++++++-- .../java/com/tangem/datasource/di/NetworkModule.kt | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 5ef7c985a6..da2ecd172f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -84,6 +84,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac context = applicationContext, tangemSdkManager = tangemSdkManager, ) + appStateHolder.userWalletsListManager = userWalletsListManager userWalletsListManagerSafe = userWalletsListManager lockUserWalletsTimer = LockUserWalletsTimer(owner = this) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 0d483b43ee..50ded416e6 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -128,7 +128,6 @@ class TapApplication : Application(), ImageLoaderFactory { middleware = AppState.getMiddleware(), state = AppState(), ) - appStateHolder.mainStore = store if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) @@ -155,7 +154,9 @@ class TapApplication : Application(), ImageLoaderFactory { context = this, tangemTechService = store.state.domainNetworks.tangemTechService, ) + appStateHolder.mainStore = store appStateHolder.userTokensRepository = userTokensRepository + appStateHolder.walletStoresManager = walletStoresManager } //todo refactor: move to datasource and provide via DI diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 11cfdee61f..cfe9b9cb17 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -5,6 +5,8 @@ import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.features.wallet.redux.WalletState import org.rekotlin.Store @@ -19,6 +21,8 @@ class AppStateHolder { var userTokensRepository: UserTokensRepository? = null var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null + var walletStoresManager: WalletStoresManager? = null + var userWalletsListManager: UserWalletsListManager? = null fun getActualCard(): CardDTO? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 7be322fc1e..265b0aa182 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -89,6 +89,8 @@ class DerivationManagerImpl( } scope.launch { + val selectedWallet = appStateHolder.userWalletsListManager?.selectedUserWalletSync + val result = appStateHolder.tangemSdkManager?.derivePublicKeys( scanResponse.card.cardId, derivations, @@ -108,6 +110,14 @@ class DerivationManagerImpl( val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) + if (selectedWallet != null) { + val userWallet = selectedWallet.copy( + scanResponse = updatedScanResponse, + ) + + appStateHolder.userWalletsListManager?.save(userWallet, canOverride = true) + appStateHolder.walletStoresManager?.fetch(userWallet, true) + } appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) @@ -115,8 +125,7 @@ class DerivationManagerImpl( is CompletionResult.Failure -> { appStateHolder.mainStore?.dispatchDebugErrorNotification( TapError.CustomError( - "Error adding " + - "tokens", + "Error derivation", ), ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index d662a98721..522c6dd904 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -60,7 +60,7 @@ class NetworkModule { .build(), ), ) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .baseUrl(PROD_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() .create(ReferralApi::class.java) From 999504520c3afb1f9db7a7f72e4ad7a53fdf1fe9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 19:20:45 +0300 Subject: [PATCH 35/42] Updated on 2026-08-14 --- .../feature/referral/ui/ReferralScreen.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 92713b0fb2..361157353b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -300,14 +301,16 @@ private fun InfoForYou(award: String, networkName: String, address: String? = nu private fun InfoForYourFriend(discount: String) { ConditionInfo(title = stringResource(id = R.string.referral_point_discount_title)) { Text( - text = buildString { + text = buildAnnotatedString { append(stringResource(id = R.string.referral_point_discount_description_prefix)) - append( - String.format( - stringResource(id = R.string.referral_point_discount_description_value), - " $discount", - ), - ) + withStyle(SpanStyle(color = TangemTheme.colors.text.primary1)) { + append( + String.format( + stringResource(id = R.string.referral_point_discount_description_value), + " $discount", + ), + ) + } append(" ") append(stringResource(id = R.string.referral_point_discount_description_suffix)) }, @@ -358,14 +361,14 @@ private fun ShimmerInfo() { // TODO() Replace component with component from ds @Composable -private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { +private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { if (errorSnackbar != null) { val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } val coroutineScope = rememberCoroutineScope() SnackbarHost( hostState = snackbarHostState, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing56), + modifier = Modifier.align(Alignment.BottomCenter), snackbar = { Snackbar( snackbarData = it, @@ -407,7 +410,7 @@ private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { // TODO() Replace component with component from ds @Composable -private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { +private fun BoxScope.CopySnackbarHost(isCopyButtonPressed: MutableState) { if (isCopyButtonPressed.value) { val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } val coroutineScope = rememberCoroutineScope() @@ -419,7 +422,8 @@ private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { SnackbarHost( hostState = snackbarHostState, modifier = Modifier - .padding(top = TangemTheme.dimens.spacing56, start = (width - snackbarWidth).div(2)) + .align(Alignment.BottomCenter) + .padding(start = (width - snackbarWidth).div(2), bottom = dimensionResource(R.dimen.spacing12)) .fillMaxWidth(), snackbar = { Box( From c407e854fcd830723c52b20bc6169e54eed10ae7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 10:31:35 +0300 Subject: [PATCH 36/42] Updated on 2026-08-14 --- .../tangem/datasource/api/referral/models/ReferralResponse.kt | 2 -- .../com/tangem/feature/referral/converters/ReferralConverter.kt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt index 3066ab45a7..f58c6570f6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -1,8 +1,6 @@ package com.tangem.datasource.api.referral.models import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.joda.time.DateTime /** * Main response class for referral API diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index af7910fee0..00925bfd13 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -36,7 +36,7 @@ class ReferralConverter @Inject constructor() : Converter Date: Fri, 30 Dec 2022 12:26:50 +0300 Subject: [PATCH 37/42] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/extensions/Card.kt | 6 ++++++ .../tap/features/details/ui/details/DetailsViewModel.kt | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index fd46ad2fde..486a9f6b89 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -31,6 +31,12 @@ val CardDTO.isMultiwalletAllowed: Boolean val CardDTO.isHdWalletAllowedByApp: Boolean get() = settings.isHDWalletAllowed && !isSaltPay +val CardDTO.isTangemWallet: Boolean + get() = settings.isBackupAllowed + && settings.isHDWalletAllowed + && firmwareVersion >= FirmwareVersion.MultiWalletAvailable + && !isSaltPay + fun CardDTO.hasSignedHashes(): Boolean { return wallets.any { (it.totalSignedHashes ?: 0) > 0 } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 2cb657c083..88abe746fc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.extensions.isTangemWallet import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.disclaimer.redux.DisclaimerAction @@ -39,7 +40,7 @@ class DetailsViewModel(private val store: Store) { SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null - SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null + SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isTangemWallet == true) it else null else -> it } } From 8f9eb793d7f493406a63c93a2204affe664040f0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 18:25:18 +0300 Subject: [PATCH 38/42] Updated on 2026-08-14 --- .../tap/features/details/ui/details/DetailsScreenState.kt | 2 +- .../tap/features/details/ui/details/DetailsViewModel.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 065dc0dfc8..e55525ae1d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -23,7 +23,7 @@ enum class SettingsElement( WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), Chat(R.drawable.ic_chat, R.string.details_chat), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), - ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), + // ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency), AppSettings(R.drawable.ic_settings, R.string.app_settings_title), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 88abe746fc..f6b932c755 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -40,7 +40,7 @@ class DetailsViewModel(private val store: Store) { SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null - SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isTangemWallet == true) it else null + // SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isTangemWallet == true) it else null else -> it } } @@ -97,9 +97,9 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { // TODO: To be available later } - SettingsElement.ReferralProgram -> { - store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) - } + // SettingsElement.ReferralProgram -> { + // store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) + // } } } From 5d448b9500cb998b4dcd53ed44c641a6997a5b6f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 11:42:49 +0300 Subject: [PATCH 39/42] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../main/java/com/tangem/tap/MainActivity.kt | 2 +- .../java/com/tangem/tap/TapApplication.kt | 2 +- .../common/analytics/AnalyticsEventsLogger.kt | 4 +- .../tap/common/analytics/AnalyticsFactory.kt | 5 +- .../analytics/api/AnalyticsHandlerBuilder.kt | 19 ++++++++ .../tap/common/analytics/events/Basic.kt | 2 + .../tap/common/analytics/events/Chat.kt | 2 + .../common/analytics/events/DetailsScreen.kt | 2 + .../analytics/events/IntroductionProcess.kt | 2 + .../tap/common/analytics/events/MainScreen.kt | 2 + .../common/analytics/events/ManageTokens.kt | 1 + .../tap/common/analytics/events/MyWallets.kt | 2 + .../tap/common/analytics/events/Onboarding.kt | 2 + .../tap/common/analytics/events/Portfolio.kt | 2 + .../tap/common/analytics/events/Settings.kt | 1 + .../tap/common/analytics/events/Shop.kt | 1 + .../tap/common/analytics/events/Token.kt | 1 + .../common/analytics/events/WalletConnect.kt | 2 + .../analytics/filters/BasicSignInFilter.kt | 6 +-- .../analytics/filters/BasicTopUpFilter.kt | 6 +-- .../filters/ShopPurchasedEventFilter.kt | 6 +-- .../amplitude/AmplitudeAnalyticsHandler.kt | 2 +- .../handlers/amplitude/AmplitudeClient.kt | 2 +- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 2 +- .../handlers/appsFlyer/AppsFlyerClient.kt | 2 +- .../firebase/FirebaseAnalyticsHandler.kt | 28 +++++------ .../handlers/firebase/FirebaseClient.kt | 4 +- .../BatchIdParamsInterceptor.kt | 4 +- .../tap/common/shop/TangemShopService.kt | 2 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 2 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 2 +- .../walletconnect/WalletConnectManager.kt | 2 +- .../walletconnect/WalletConnectSdkHelper.kt | 2 +- .../details/redux/DetailsMiddleware.kt | 2 +- .../ui/cardsettings/CardSettingsViewModel.kt | 2 +- .../details/ui/details/DetailsFragment.kt | 2 +- .../details/ui/details/DetailsViewModel.kt | 3 +- .../ui/walletconnect/WalletConnectScreen.kt | 2 +- .../tangem/tap/features/home/HomeFragment.kt | 2 +- .../AddressInfoBottomSheetDialog.kt | 2 +- .../products/note/OnboardingNoteFragment.kt | 2 +- .../note/redux/OnboardingNoteMiddleware.kt | 2 +- .../OnboardingOtherCardsFragment.kt | 2 +- .../redux/OnboardingOtherCardsMiddleware.kt | 2 +- .../twins/redux/TwinCardsMiddleware.kt | 2 +- .../products/twins/ui/TwinsCardsFragment.kt | 2 +- .../redux/OnboardingWalletMiddleware.kt | 2 +- .../redux/OnboardingSaltPayMiddleware.kt | 2 +- .../saltPay/ui/OnboardingSaltPayView.kt | 2 +- .../wallet/ui/OnboardingWalletFragment.kt | 2 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../send/redux/middlewares/SendMiddleware.kt | 2 +- .../tap/features/send/ui/SendFragment.kt | 2 +- .../tap/features/shop/ui/ShopFragment.kt | 2 +- .../addCustomToken/AddCustomTokenFragment.kt | 2 +- .../features/tokens/redux/TokensMiddleware.kt | 2 +- .../features/tokens/ui/AddTokensFragment.kt | 2 +- .../tokens/ui/compose/CurrenciesScreen.kt | 2 +- .../middlewares/AppCurrencyMiddleware.kt | 2 +- .../middlewares/MultiWalletMiddleware.kt | 2 +- .../middlewares/TradeCryptoMiddleware.kt | 2 +- .../middlewares/WalletDialogsMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../wallet/ui/WalletDetailsFragment.kt | 2 +- .../tap/features/wallet/ui/WalletFragment.kt | 2 +- .../wallet/ui/adapters/WalletAdapter.kt | 2 +- .../ui/adapters/WarningMessagesAdapter.kt | 2 +- ...sianCardholdersWarningBottomSheetDialog.kt | 2 +- .../wallet/ui/dialogs/ScanFailsDialog.kt | 2 +- .../wallet/ui/wallet/MultiWalletView.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 2 +- .../ui/WalletSelectorBottomSheetFragment.kt | 2 +- core/analytics/.gitignore | 1 + core/analytics/build.gradle.kts | 48 +++++++++++++++++++ core/analytics/src/main/AndroidManifest.xml | 2 + .../com/tangem/core}/analytics/Analytics.kt | 19 ++++---- .../tangem/core/analytics}/AnalyticsEvent.kt | 4 +- .../tangem/core}/analytics/api/ClientApi.kt | 2 +- .../core}/analytics/api/EventFilterApi.kt | 4 +- .../core}/analytics/api/EventHandlerApi.kt | 20 +------- .../analytics/api/ParamsInterceptorApi.kt | 4 +- .../core/analytics/di/AnalyticsModule.kt | 20 ++++++++ .../FeatureCoroutineExceptionHandler.kt | 10 ++-- domain/build.gradle.kts | 1 + .../com/tangem/domain/redux/ReStoreHub.kt | 2 +- settings.gradle.kts | 1 + 87 files changed, 223 insertions(+), 121 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt create mode 100644 core/analytics/.gitignore create mode 100644 core/analytics/build.gradle.kts create mode 100644 core/analytics/src/main/AndroidManifest.xml rename {app/src/main/java/com/tangem/tap/common => core/analytics/src/main/java/com/tangem/core}/analytics/Analytics.kt (82%) rename {app/src/main/java/com/tangem/tap/common/analytics/events => core/analytics/src/main/java/com/tangem/core/analytics}/AnalyticsEvent.kt (71%) rename {app/src/main/java/com/tangem/tap/common => core/analytics/src/main/java/com/tangem/core}/analytics/api/ClientApi.kt (86%) rename {app/src/main/java/com/tangem/tap/common => core/analytics/src/main/java/com/tangem/core}/analytics/api/EventFilterApi.kt (89%) rename {app/src/main/java/com/tangem/tap/common => core/analytics/src/main/java/com/tangem/core}/analytics/api/EventHandlerApi.kt (54%) rename {app/src/main/java/com/tangem/tap/common => core/analytics/src/main/java/com/tangem/core}/analytics/api/ParamsInterceptorApi.kt (78%) create mode 100644 core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt rename {domain/src/main/java/com/tangem/domain/common => core/utils/src/main/java/com/tangem/utils/coroutines}/FeatureCoroutineExceptionHandler.kt (61%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1876b0038c..34d26a35ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -109,6 +109,7 @@ dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) implementation(project(":domain")) implementation(project(":common")) + implementation(project(":core:analytics")) implementation(project(":core:res")) implementation(project(":core:ui")) implementation(project(":core:datasource")) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index da2ecd172f..5efe9e9397 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,7 +10,7 @@ import androidx.core.view.WindowInsetsControllerCompat import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar import com.tangem.TangemSdk -import com.tangem.domain.common.FeatureCoroutineExceptionHandler +import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import com.tangem.operations.backup.BackupService import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 50ded416e6..4ffc45da6d 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -17,7 +17,7 @@ import com.tangem.domain.common.LogConfig import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader import com.tangem.tap.common.IntentHandler -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.filters.BasicSignInFilter diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt index 0865d2aa81..6b5a1fc3a2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.common.analytics.api.ErrorEventLogger -import com.tangem.tap.common.analytics.api.EventLogger +import com.tangem.analytics.api.ErrorEventLogger +import com.tangem.analytics.api.EventLogger import timber.log.Timber class AnalyticsEventsLogger( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt index 8936adfc3c..ed2e3868b5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt @@ -1,8 +1,9 @@ package com.tangem.tap.common.analytics -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.ParamsInterceptor import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.api.ParamsInterceptor /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt new file mode 100644 index 0000000000..6d23925143 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.common.analytics.api + +import android.app.Application +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.domain.common.AnalyticsHandlersLogConfig +import com.tangem.tap.domain.configurable.config.Config + +interface AnalyticsHandlerBuilder { + fun build(data: Data): AnalyticsHandler? + + data class Data( + val application: Application, + val config: Config, + val isDebug: Boolean, + val logConfig: AnalyticsHandlersLogConfig, + val jsonConverter: MoshiJsonConverter, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index 80afe6d3f8..ecdedd9761 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt index a26109ae2a..96fa307105 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt index 1ec8d81533..a0d34306c9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt index 4b3eb7f431..0d799d379f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index a42cc3568e..6f718755f0 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index f944f58d24..55ed5f97b9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.tap.common.extensions.filterNotNull diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt index 0fd08ddd88..2b660fa11d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + sealed class MyWallets( event: String, params: Map = mapOf(), diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index 92f55074e9..91a3ae0741 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt index 6747c2172d..71383c9f9a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 728662bdb9..c9b0419a49 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.features.details.ui.details.SocialNetwork /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt index 9634f2929b..788f557537 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 7445e31f33..99badb9998 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index 1ff587b2aa..b3e4b835be 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.core.analytics.AnalyticsEvent + /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt index a582ea34f1..09c26d8d7d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics.filters -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsHandler -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.events.Basic /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt index be780ccd2d..de89bf9e7e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt @@ -1,9 +1,9 @@ package com.tangem.tap.common.analytics.filters import com.tangem.common.extensions.guard -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsHandler -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.persistence.ToppedUpWalletStorage diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt index dffa123240..738d3fed8f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics.filters -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsHandler -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index c76fe21d38..a1daeb7ccf 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.handlers.amplitude -import com.tangem.tap.common.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AmplitudeAnalyticsHandler( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt index d90ed084b5..80f8359025 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt @@ -4,7 +4,7 @@ import android.app.Application import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient import com.tangem.common.Converter -import com.tangem.tap.common.analytics.api.EventLogger +import com.tangem.analytics.api.EventLogger import org.json.JSONObject /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index c3968d9d72..a0b6cb378f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer -import com.tangem.tap.common.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AppsFlyerAnalyticsHandler( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt index 6a7aa7cfed..b8271cc6af 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer import android.content.Context import com.appsflyer.AppsFlyerLib -import com.tangem.tap.common.analytics.api.EventLogger +import com.tangem.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index 109e61d0a5..2172fe9aee 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -1,10 +1,10 @@ package com.tangem.tap.common.analytics.handlers.firebase -import com.tangem.tap.common.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter -import com.tangem.tap.common.analytics.events.AnalyticsEvent class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, @@ -17,18 +17,18 @@ class FirebaseAnalyticsHandler( } override fun send(event: AnalyticsEvent) { - when (event.error) { - null -> super.send(event) - else -> { - val errorConverter = AnalyticsErrorConverter() - if (!errorConverter.canBeHandled(event.error)) return + val error = event.error + if (error == null) { + super.send(event) + } else { + val errorConverter = AnalyticsErrorConverter() + if (!errorConverter.canBeHandled(error)) return - val errorParams = errorConverter.convert(event.error).toMutableMap() - errorParams["Category"] = event.category - errorParams["Event"] = event.event - errorParams.putAll(event.params) - send(event.error, errorParams) - } + val errorParams = errorConverter.convert(error).toMutableMap() + errorParams["Category"] = event.category + errorParams["Event"] = event.event + errorParams.putAll(event.params) + send(error, errorParams) } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 07ed4c4551..3db2ed90bc 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.ktx.analytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import com.tangem.tap.common.analytics.api.ErrorEventLogger -import com.tangem.tap.common.analytics.api.EventLogger +import com.tangem.analytics.api.ErrorEventLogger +import com.tangem.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt index ffe48cf198..b7e7ec6fbf 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.paramsInterceptor -import com.tangem.tap.common.analytics.api.ParamsInterceptor -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.core.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam /** diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 808cb5bbbc..47951bfb84 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -4,7 +4,7 @@ import android.app.Application import android.content.Intent import com.google.android.gms.wallet.PaymentData import com.shopify.buy3.Storefront -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.shop.data.ProductType diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index be8e4f65b2..cf8a13681c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -30,7 +30,7 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTask diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index d940a338ad..0f0c84e44e 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -11,7 +11,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.backupService -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor import com.tangem.tap.common.extensions.dispatchDialogShow diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 37e488ccf8..d2b77a628c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index def1cb4a92..2ad236bd6c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -24,7 +24,7 @@ import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString 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 9fe984d9c9..fda0a60ea4 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 @@ -6,7 +6,7 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow 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 ce9da7f8b0..6920e51129 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 @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.cardsettings import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.getTwinCardIdForUser -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt index 4cecec1a8f..56023ab1f8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsState diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index f6b932c755..42fbc5b252 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.details import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo @@ -11,7 +11,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.extensions.isMultiwalletAllowed -import com.tangem.tap.domain.extensions.isTangemWallet import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.disclaimer.redux.DisclaimerAction diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 499f31ddcf..a08153c345 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.common.extensions.getFromClipboard diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 450d428367..aef9f11d9d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment import com.google.accompanist.appcompattheme.AppCompatTheme -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index b56ddaf6cd..0372ae6ee4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 558c71a9f6..c4b34eba62 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -9,7 +9,7 @@ import androidx.core.view.isVisible import androidx.transition.TransitionManager import coil.load import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 7119dfaf0d..e63a4a9b7c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index b0b8ae343d..5439b76612 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -8,7 +8,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import coil.load -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.redux.navigation.ShareElement diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index 91536792a9..d554befc5f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppState diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index b5bd3c7504..4492881a57 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -7,7 +7,7 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 36e386325e..9d7b99a2a6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -18,7 +18,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.AndroidAssetReader -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.getDrawableCompat diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 37e629800a..6c35be3ead 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -9,7 +9,7 @@ import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 4d6c4a3c00..ce3390eba1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -10,7 +10,7 @@ import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext import com.tangem.datasource.api.paymentology.KYCStatus import com.tangem.datasource.api.paymentology.RegistrationResponse -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index fcbcfb74ff..569e87ee0f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -14,7 +14,7 @@ import coil.size.Scale import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton import com.tangem.common.extensions.guard -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.compose.PinCodeWidget import com.tangem.tap.common.extensions.configureSettings diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 2ca75121a9..04fc588577 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -19,7 +19,7 @@ import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.inflate diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 60bc2e076e..2dfa79cb24 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Onboarding diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 5bf0618fbb..c72e9f9a67 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -17,7 +17,7 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogShow diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index a4f93f7949..5c5a0b924d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -16,7 +16,7 @@ import com.google.android.material.textfield.TextInputEditText import com.tangem.Message import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.getFromClipboard diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 338561267b..1f32a5305e 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -12,7 +12,7 @@ import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt index 3954882be5..0c03a6869d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.platform.ComposeView import com.google.accompanist.appcompattheme.AppCompatTheme import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.features.BaseStoreFragment diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index af6ab1607f..7a4a1a1b2a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -21,7 +21,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.assetReader -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 2fa1ab2971..499ed8e2f5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -13,7 +13,7 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.google.accompanist.appcompattheme.AppCompatTheme import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchNotification diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 149fd2b517..09ddf515fc 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -29,7 +29,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.Keyboard diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index c60632f60e..5b05b971d0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -3,7 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.CurrenciesResponse import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.entities.FiatCurrency diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index ba82d17ccd..87341ef9c0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index df69426a2c..0bb74bbf6b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDebugErrorNotification diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index fb6ff1dd3f..262f1780fc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 243ab5eab6..548d0ccf4f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -11,7 +11,7 @@ import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index bd1608e7de..50f53888b7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -22,7 +22,7 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.TestActions -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.appendIfNotNull diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index f01805dd39..860939d53c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -21,7 +21,7 @@ import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter import com.tangem.tap.common.analytics.converters.BasicTopUpEventConverter import com.tangem.tap.common.analytics.events.MainScreen diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index b07db256f5..fb19f281bc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -7,7 +7,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.tangem.domain.common.TapWorkarounds.derivationStyle -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index dd3bcee91c..774bfc2aee 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -7,7 +7,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.play.core.review.ReviewManagerFactory -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.extensions.getActivity diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index b8022e2724..ca665749bb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -4,7 +4,7 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.analytics.events.Token diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index cef955c28e..3251bce185 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.wallet.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.ScanFailsEmail diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index f7c8dc8a66..108ad69cdd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -7,7 +7,7 @@ import com.badoo.mvicore.DiffStrategy import com.badoo.mvicore.modelWatcher import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.animateVisibility diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 4772b501d7..5565d38455 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -8,7 +8,7 @@ import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 4db33980de..96bba1df0e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.fragment.app.viewModels import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.analytics.Analytics +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent diff --git a/core/analytics/.gitignore b/core/analytics/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/analytics/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts new file mode 100644 index 0000000000..639fbbc622 --- /dev/null +++ b/core/analytics/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) + + /** Core shouldn't depends on core, but in case with utils and logging its necessary */ + implementation(project(":core:utils")) +} \ No newline at end of file diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..9270c6ee97 --- /dev/null +++ b/core/analytics/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt similarity index 82% rename from app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt index 8a6ee52f27..20fcb92c92 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt @@ -1,14 +1,13 @@ -package com.tangem.tap.common.analytics +package com.tangem.core.analytics -import com.tangem.domain.common.FeatureCoroutineExceptionHandler -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler -import com.tangem.tap.common.analytics.api.AnalyticsFilterHolder -import com.tangem.tap.common.analytics.api.AnalyticsHandler -import com.tangem.tap.common.analytics.api.AnalyticsHandlerHolder -import com.tangem.tap.common.analytics.api.ParamsInterceptor -import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.api.AnalyticsFilterHolder +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.AnalyticsHandlerHolder +import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.core.analytics.api.ParamsInterceptorHolder +import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt b/core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt similarity index 71% rename from app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt index b2db44fe52..e23a503460 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt @@ -1,9 +1,9 @@ -package com.tangem.tap.common.analytics.events +package com.tangem.core.analytics /** [REDACTED_AUTHOR] */ -sealed class AnalyticsEvent( +open class AnalyticsEvent( val category: String, val event: String, var params: Map = mapOf(), diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt similarity index 86% rename from app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt index 3b5b0720f2..d4a9b22842 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common.analytics.api +package com.tangem.analytics.api /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt similarity index 89% rename from app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt index 04d6dbbb01..8381b2b983 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt @@ -1,6 +1,6 @@ -package com.tangem.tap.common.analytics.api +package com.tangem.core.analytics.api -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt similarity index 54% rename from app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt index 344402f7f1..ca6721037c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt @@ -1,10 +1,6 @@ -package com.tangem.tap.common.analytics.api +package com.tangem.core.analytics.api -import android.app.Application -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.common.analytics.events.AnalyticsEvent -import com.tangem.tap.domain.configurable.config.Config +import com.tangem.core.analytics.AnalyticsEvent /** [REDACTED_AUTHOR] @@ -35,16 +31,4 @@ interface ErrorEventHandler { interface AnalyticsHandlerHolder { fun addHandler(name: String, handler: AnalyticsHandler) fun removeHandler(name: String): AnalyticsHandler? -} - -interface AnalyticsHandlerBuilder { - fun build(data: Data): AnalyticsHandler? - - data class Data( - val application: Application, - val config: Config, - val isDebug: Boolean, - val logConfig: AnalyticsHandlersLogConfig, - val jsonConverter: MoshiJsonConverter, - ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt similarity index 78% rename from app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt rename to core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt index 245ef32da5..e2f9dd5860 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt @@ -1,6 +1,6 @@ -package com.tangem.tap.common.analytics.api +package com.tangem.core.analytics.api -import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.core.analytics.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt new file mode 100644 index 0000000000..512686e026 --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt @@ -0,0 +1,20 @@ +package com.tangem.core.analytics.di + +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.api.AnalyticsEventHandler +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class AnalyticsModule { + + @Singleton + @Provides + fun provideAnalyticsHandler(): AnalyticsEventHandler { + return Analytics //todo replace after refactoring calling Analytics in whole project + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/FeatureCoroutineExceptionHandler.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt similarity index 61% rename from domain/src/main/java/com/tangem/domain/common/FeatureCoroutineExceptionHandler.kt rename to core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt index e6dde98009..71cea0fb21 100644 --- a/domain/src/main/java/com/tangem/domain/common/FeatureCoroutineExceptionHandler.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt @@ -1,9 +1,10 @@ -package com.tangem.domain.common +package com.tangem.utils.coroutines import kotlinx.coroutines.CoroutineExceptionHandler -import timber.log.Timber import java.io.PrintWriter import java.io.StringWriter +import java.util.logging.Level +import java.util.logging.Logger /** [REDACTED_AUTHOR] @@ -16,7 +17,10 @@ class FeatureCoroutineExceptionHandler { val sw = StringWriter() throwable.printStackTrace(PrintWriter(sw)) val exceptionAsString: String = sw.toString() - Timber.e("CoroutineException: from: %s, exception: %s", from, exceptionAsString) + //it delegates logging to android Logger, cause cant use timber in java module + Logger.getLogger("CoroutineExceptHandler").log( + Level.INFO, "CoroutineException: from: $from, exception: $exceptionAsString", + ) throw throwable } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index e365d69c25..93519446be 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -72,6 +72,7 @@ android { dependencies { implementation(project(":core:datasource")) + implementation(project(":core:utils")) implementation(project(":common")) /** Tangem libraries */ diff --git a/domain/src/main/java/com/tangem/domain/redux/ReStoreHub.kt b/domain/src/main/java/com/tangem/domain/redux/ReStoreHub.kt index a869c18177..fc2f0df299 100644 --- a/domain/src/main/java/com/tangem/domain/redux/ReStoreHub.kt +++ b/domain/src/main/java/com/tangem/domain/redux/ReStoreHub.kt @@ -1,8 +1,8 @@ package com.tangem.domain.redux import android.webkit.ValueCallback -import com.tangem.domain.common.FeatureCoroutineExceptionHandler import com.tangem.domain.redux.global.DomainGlobalState +import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import kotlinx.coroutines.* import org.rekotlin.Action import org.rekotlin.DispatchFunction diff --git a/settings.gradle.kts b/settings.gradle.kts index 028fd3f613..b7dc831ec1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ include(":domain") include(":common") // region Core modules +include(":core:analytics") include(":core:datasource") include(":core:res") include(":core:utils") From 905792c77e655fa595c2c63a6d5cc241cb7351a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 00:35:28 +0300 Subject: [PATCH 40/42] Updated on 2026-08-14 --- .../tap/domain/scanCard/ScanCardProcessor.kt | 15 ++-- .../tangem/tap/domain/termsOfUse/CardTou.kt | 47 ---------- .../features/details/redux/DetailsAction.kt | 3 - .../details/redux/DetailsMiddleware.kt | 6 -- .../features/details/redux/DetailsReducer.kt | 1 - .../features/details/redux/DetailsState.kt | 2 - .../details/ui/details/DetailsScreenState.kt | 1 - .../details/ui/details/DetailsViewModel.kt | 6 +- .../tap/features/disclaimer/Disclaimer.kt | 80 +++++++++++++++++ .../disclaimer/DisclaimerDataProvider.kt | 12 +++ .../tap/features/disclaimer/DisclaimerType.kt | 44 +++++++++ .../disclaimer/redux/DisclaimerAction.kt | 21 ++--- .../disclaimer/redux/DisclaimerMiddleware.kt | 35 +------- .../disclaimer/redux/DisclaimerReducer.kt | 14 +-- .../disclaimer/redux/DisclaimerState.kt | 39 +++----- .../disclaimer/ui/DisclaimerFragment.kt | 90 +++++++++++++++---- .../disclaimer/ui/DisclaimerWebViewClient.kt | 68 ++++++++++++++ .../tap/features/wallet/ui/WalletFragment.kt | 2 - .../tap/persistence/DisclaimerPrefStorage.kt | 20 ++--- .../main/res/layout/fragment_disclaimer.xml | 75 +++++++++++++--- 20 files changed, 384 insertions(+), 197 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt create mode 100644 app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt create mode 100644 app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index e9b7d1a7e3..7b33c2f9fc 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -21,10 +21,10 @@ import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.disclaimer.DisclaimerType +import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback -import com.tangem.tap.features.disclaimer.redux.DisclaimerType -import com.tangem.tap.features.disclaimer.redux.isAccepted import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -137,16 +137,17 @@ object ScanCardProcessor { crossinline nextHandler: suspend (ScanResponse) -> Unit, crossinline onFailure: suspend (error: TangemError) -> Unit, ) { - val disclaimerType = DisclaimerType.get(scanResponse) - store.dispatchOnMain(DisclaimerAction.SetDisclaimerType(disclaimerType)) + val disclaimer = DisclaimerType.get(scanResponse.card).createDisclaimer(scanResponse.card) + store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) - if (disclaimerType.isAccepted()) { + if (disclaimer.isAccepted()) { nextHandler((scanResponse)) - } else scope.launch(Dispatchers.Main) { + } else scope.launch { delay(DELAY_SDK_DIALOG_CLOSE) disclaimerWillShow() - store.dispatchOnMain( + dispatchOnMain( DisclaimerAction.Show( + fromScreen = AppScreen.Home, callback = DisclaimerCallback( onAccept = { scope.launch(Dispatchers.Main) { diff --git a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt deleted file mode 100644 index 6644362bf5..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.tangem.tap.domain.termsOfUse - -import android.content.res.Resources -import android.net.Uri -import androidx.core.os.ConfigurationCompat -import com.tangem.domain.common.CardDTO -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -class CardTou { - private val locale: Locale = - ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)!! - - fun getUrl(card: CardDTO): Uri? { - val issuerName = card.issuer.name - if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null - - val baseUrl = "https://app.tangem.com/tou/" - val regionCode = regionCode(card.cardId) ?: "fr" - val filename = filename(locale.language, regionCode) - return Uri.parse(baseUrl + filename) - } - - private fun filename(languageCode: String, regionCode: String): String { - return when { - languageCode == "fr" && regionCode == "ch" -> "Start2Coin-fr-ch-tangem.pdf" - languageCode == "de" && regionCode == "ch" -> "Start2Coin-de-ch-tangem.pdf" - languageCode == "en" && regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf" - languageCode == "it" && regionCode == "ch" -> "Start2Coin-it-ch-tangem.pdf" - languageCode == "fr" && regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf" - languageCode == "de" && regionCode == "at" -> "Start2Coin-de-at-tangem.pdf" - regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf" - regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf" - regionCode == "at" -> "Start2Coin-de-at-tangem.pdf" - else -> "Start2Coin-fr-fr-atangem.pdf" - } - } - - private fun regionCode(cardId: String): String? = when (cardId[1]) { - '0' -> "fr" - '1' -> "ch" - '2' -> "at" - else -> null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index d08fd627f5..5447faf5b0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Wallet import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.domain.termsOfUse.CardTou import org.rekotlin.Action sealed class DetailsAction : Action { @@ -12,10 +11,8 @@ sealed class DetailsAction : Action { data class PrepareScreen( val scanResponse: ScanResponse, val wallets: List, - val cardTou: CardTou, ) : DetailsAction() - object ShowDisclaimer : DetailsAction() object ReCreateTwinsWallet : DetailsAction() sealed class ResetToFactory : DetailsAction() { 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 fda0a60ea4..f6e2c4b7c4 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 @@ -61,12 +61,6 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action) - is DetailsAction.ShowDisclaimer -> { - val uri = state.cardTermsOfUseUrl - if (uri != null) { - store.dispatch(NavigationAction.OpenDocument(uri)) - } - } is DetailsAction.ReCreateTwinsWallet -> { store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 11b737097a..142d59fbf3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -52,7 +52,6 @@ private fun handlePrepareScreen( return DetailsState( scanResponse = action.scanResponse, wallets = action.wallets, - cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, isBiometricsAvailable = tangemSdkManager.canUseBiometry, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 3cb211b7c5..a22b810f7c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.details.redux -import android.net.Uri import com.tangem.blockchain.common.Wallet import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse @@ -16,7 +15,6 @@ data class DetailsState( val scanResponse: ScanResponse? = null, val wallets: List = emptyList(), val cardSettingsState: CardSettingsState? = null, - val cardTermsOfUseUrl: Uri? = null, val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, val appCurrency: FiatCurrency = FiatCurrency.Default, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index e55525ae1d..ac69fe0ad8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -29,7 +29,6 @@ enum class SettingsElement( AppSettings(R.drawable.ic_settings, R.string.app_settings_title), LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup), TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App - TermsOfUse(R.drawable.ic_text, R.string.details_row_title_card_tou), // Terms of Use for S2C cards only PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy); } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 42fbc5b252..6165dc5c39 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -38,7 +38,6 @@ class DetailsViewModel(private val store: Store) { } SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null - SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null // SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isTangemWallet == true) it else null else -> it } @@ -88,10 +87,7 @@ class DetailsViewModel(private val store: Store) { store.dispatch(DetailsAction.CreateBackup) } SettingsElement.TermsOfService -> { - store.dispatch(DisclaimerAction.Show()) - } - SettingsElement.TermsOfUse -> { - store.dispatch(DetailsAction.ShowDisclaimer) + store.dispatch(DisclaimerAction.Show(AppScreen.Details)) } SettingsElement.PrivacyPolicy -> { // TODO: To be available later diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt new file mode 100644 index 0000000000..a3f28bfb73 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -0,0 +1,80 @@ +package com.tangem.tap.features.disclaimer + +import android.net.Uri + +/** +[REDACTED_AUTHOR] + */ +interface Disclaimer { + fun type(): DisclaimerType + fun getUri(): Uri + fun accept() + fun isAccepted(): Boolean +} + +abstract class BaseDisclaimer( + protected val dataProvider: DisclaimerDataProvider, +) : Disclaimer { + + val baseUrl = "https://tangem.com" + + override fun accept() { + dataProvider.storage().accept(getPreferenceKey()) + } + + override fun isAccepted(): Boolean = dataProvider.storage().isAccepted(getPreferenceKey()) + + protected open fun getPreferenceKey(): String = type().name +} + +class DummyDisclaimer : Disclaimer { + override fun type(): DisclaimerType = DisclaimerType.Tangem + override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html") + override fun accept() {} + override fun isAccepted(): Boolean = false +} + +class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { + override fun type(): DisclaimerType = DisclaimerType.Tangem + override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html") + override fun getPreferenceKey(): String = "tangem_tos_accepted" +} + +class SaltPayDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { + override fun type(): DisclaimerType = DisclaimerType.SaltPay + override fun getUri(): Uri = Uri.parse("$baseUrl/soltpay_tos.html") + override fun getPreferenceKey(): String = "saltPay_tos_accepted" +} + +class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { + override fun type(): DisclaimerType = DisclaimerType.Start2Coin + override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion())) + override fun getPreferenceKey(): String = "start2Coin_tos_accepted_${getRegion()}" + + private fun filename(languageCode: String, regionCode: String?): String { + return when { + languageCode == "fr" && regionCode == "ch" -> "Start2Coin-fr-ch-tangem.html" + languageCode == "de" && regionCode == "ch" -> "Start2Coin-de-ch-tangem.html" + languageCode == "en" && regionCode == "ch" -> "Start2Coin-en-ch-tangem.html" + languageCode == "it" && regionCode == "ch" -> "Start2Coin-it-ch-tangem.html" + languageCode == "fr" && regionCode == "fr" -> "Start2Coin-fr-fr-atangem.html" + languageCode == "de" && regionCode == "at" -> "Start2Coin-de-at-tangem.html" + regionCode == "fr" -> "Start2Coin-fr-fr-atangem.html" + regionCode == "ch" -> "Start2Coin-en-ch-tangem.html" + regionCode == "at" -> "Start2Coin-de-at-tangem.html" + else -> "Start2Coin-fr-fr-atangem.html" + } + } + + private fun getRegion(): String? { + val cardId = dataProvider.getCardId() + if (cardId.isEmpty()) return null + + return when (cardId[1]) { + '0' -> "fr" + '1' -> "ch" + '2' -> "at" + else -> null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt new file mode 100644 index 0000000000..0097f34142 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.disclaimer + +import com.tangem.tap.persistence.DisclaimerPrefStorage + +/** +[REDACTED_AUTHOR] + */ +interface DisclaimerDataProvider { + fun getLanguage(): String + fun getCardId(): String + fun storage(): DisclaimerPrefStorage +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt new file mode 100644 index 0000000000..d6af0e0596 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.disclaimer + +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.tap.persistence.DisclaimerPrefStorage +import com.tangem.tap.preferencesStorage +import java.util.* + +/** +[REDACTED_AUTHOR] + */ +enum class DisclaimerType { + Tangem, + SaltPay, + Start2Coin; + + companion object { + fun get(cardDTO: CardDTO): DisclaimerType { + return when { + cardDTO.isSaltPay -> SaltPay + cardDTO.isStart2Coin -> Start2Coin + else -> Tangem + } + } + } +} + +fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer { + val dataProvider = provideDisclaimerDataProvider(cardDTO.cardId) + return when (this) { + DisclaimerType.Tangem -> TangemDisclaimer(dataProvider) + DisclaimerType.SaltPay -> SaltPayDisclaimer(dataProvider) + DisclaimerType.Start2Coin -> Start2CoinDisclaimer(dataProvider) + } +} + +private fun provideDisclaimerDataProvider(cardId: String): DisclaimerDataProvider { + return object : DisclaimerDataProvider { + override fun getLanguage(): String = Locale.getDefault().language + override fun getCardId(): String = cardId + override fun storage(): DisclaimerPrefStorage = preferencesStorage.disclaimerPrefStorage + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index 395137228c..1a712e1c49 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -1,26 +1,21 @@ package com.tangem.tap.features.disclaimer.redux -import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.features.disclaimer.Disclaimer +import com.tangem.tap.features.wallet.redux.ProgressState import org.rekotlin.Action sealed class DisclaimerAction : Action { - data class SetDisclaimerType( - val type: DisclaimerType, - ) : DisclaimerAction() + data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction() data class Show( + val fromScreen: AppScreen, val callback: DisclaimerCallback? = null, ) : DisclaimerAction() - data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() - - internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() - + object AcceptDisclaimer : DisclaimerAction() object OnBackPressed : DisclaimerAction() -} -data class DisclaimerCallback( - val onAccept: VoidCallback? = null, - val onDismiss: VoidCallback? = null, -) \ No newline at end of file + data class OnProgressStateChanged(val state: ProgressState?) : DisclaimerAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index a604d3275f..ca71193f2a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.disclaimer.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.preferencesStorage import com.tangem.tap.store import org.rekotlin.Action import org.rekotlin.Middleware @@ -23,16 +22,11 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { val state = appState.disclaimerState when (action) { - is DisclaimerAction.SetDisclaimerType -> { - handleUpdateState = action.type.createUpdateState() - } is DisclaimerAction.Show -> { - handleUpdateState = state.type.createUpdateState() store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } is DisclaimerAction.AcceptDisclaimer -> { - action.type.accept() - handleUpdateState = action.type.createUpdateState() + state.disclaimer.accept() store.dispatch(NavigationAction.PopBackTo()) state.callback?.onAccept?.invoke() } @@ -41,29 +35,4 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { state.callback?.onDismiss?.invoke() } } -} - -private fun DisclaimerType.accept() = when (this) { - DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted = true - DisclaimerType.Start2Coin -> preferencesStorage.disclaimerPrefStorage.hasStart2CoinTosAccepted = true - DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted = true -} - -fun DisclaimerType.isAccepted(): Boolean = when (this) { - DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted - DisclaimerType.Start2Coin -> preferencesStorage.disclaimerPrefStorage.hasStart2CoinTosAccepted - DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted -} - -private fun DisclaimerType.createUpdateState(): DisclaimerAction.UpdateState { - return DisclaimerAction.UpdateState(this, this.isAccepted()) -} - -private var handleUpdateState: DisclaimerAction.UpdateState = DisclaimerAction.UpdateState( - type = DisclaimerType.Tangem, - accepted = false, -) - set(value) { - field = value - store.dispatch(value) - } \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index d44d02e993..456818fe70 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -15,15 +15,19 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { val disclaimerState = state.disclaimerState return when (action) { - is DisclaimerAction.Show -> disclaimerState.copy( - callback = action.callback, + is DisclaimerAction.SetDisclaimer -> disclaimerState.copy( + disclaimer = action.disclaimer, ) - is DisclaimerAction.UpdateState -> disclaimerState.copy( - type = action.type, - accepted = action.accepted, + is DisclaimerAction.Show -> disclaimerState.copy( + showedFromScreen = action.fromScreen, + callback = action.callback, ) is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy( callback = null, + progressState = null, + ) + is DisclaimerAction.OnProgressStateChanged -> disclaimerState.copy( + progressState = action.state, ) else -> disclaimerState } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index d8dcbddd2a..99db90b3d8 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -1,32 +1,21 @@ package com.tangem.tap.features.disclaimer.redux -import android.net.Uri -import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.features.disclaimer.Disclaimer +import com.tangem.tap.features.disclaimer.DummyDisclaimer +import com.tangem.tap.features.wallet.redux.ProgressState import org.rekotlin.StateType data class DisclaimerState( - val accepted: Boolean = false, - val type: DisclaimerType = DisclaimerType.Tangem, + val disclaimer: Disclaimer = DummyDisclaimer(), + val showedFromScreen: AppScreen = AppScreen.Home, val callback: DisclaimerCallback? = null, -) : StateType + val progressState: ProgressState? = null, +) : StateType { +} -sealed class DisclaimerType( - val uri: Uri, -) { - object Tangem : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html")) - object Start2Coin : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html")) - object SaltPay : DisclaimerType(Uri.parse("https://tangem.com/soltpay_tos.html")) - - companion object { - fun get(scanResponse: ScanResponse): DisclaimerType = get(scanResponse.card) - - fun get(card: CardDTO): DisclaimerType = when { - card.isSaltPay -> SaltPay - card.isStart2Coin -> Start2Coin - else -> Tangem - } - } -} \ No newline at end of file +data class DisclaimerCallback( + val onAccept: VoidCallback? = null, + val onDismiss: VoidCallback? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 7761de53b6..3c177a29e7 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -5,13 +5,17 @@ import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.core.ui.fragments.setStatusBarColor -import com.tangem.tap.common.extensions.configureSettings +import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.features.BaseFragment import com.tangem.tap.features.addBackPressHandler +import com.tangem.tap.features.disclaimer.Disclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerState +import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentDisclaimerBinding @@ -20,17 +24,52 @@ import org.rekotlin.StoreSubscriber class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubscriber { private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind) + private val webViewClient = DisclaimerWebViewClient() + + override fun configureTransitions() { + val inflater = TransitionInflater.from(requireContext()) + when (store.state.disclaimerState.showedFromScreen) { + AppScreen.Home -> { + enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) + exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + } + AppScreen.Details -> { + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(handler = this) - binding.toolbar.setNavigationOnClickListener { handleOnBackPressed() } - binding.webView.configureSettings() + + binding.apply { + toolbar.setNavigationOnClickListener { handleOnBackPressed() } + webView.apply { + settings.allowFileAccess = false + settings.javaScriptEnabled = false + webViewClient = this@DisclaimerFragment.webViewClient + } + webView.hide() + groupError.hide() + groupAccept.hide() + groupLoading.hide() + + btnAccept.setOnClickListener { + store.dispatch(DisclaimerAction.AcceptDisclaimer) + } + btnRepeat.setOnClickListener { + webViewClient.reset() + } + } } override fun onStart() { super.onStart() setStatusBarColor(R.color.backgroundLightGray) + + webViewClient.onProgressStateChanged = { store.dispatch(DisclaimerAction.OnProgressStateChanged(it)) } store.subscribe(subscriber = this) { state -> state .skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState } @@ -39,30 +78,43 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs } override fun onStop() { - super.onStop() + webViewClient.onProgressStateChanged = null store.unsubscribe(this) - } - - override fun configureTransitions() { - super.configureTransitions() - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(android.R.transition.fade) - exitTransition = inflater.inflateTransition(android.R.transition.fade) + super.onStop() } override fun newState(state: DisclaimerState) = with(binding) { if (activity == null || view == null) return - if (state.accepted) { - halfTransparentOverlay.hide() - btnAccept.hide() - } + updateUiVisibility(state.disclaimer, state.progressState) + binding.webView.loadUrl(state.disclaimer.getUri().toString()) + } - btnAccept.setOnClickListener { - store.dispatch(DisclaimerAction.AcceptDisclaimer(state.type)) + private fun updateUiVisibility(disclaimer: Disclaimer, progressState: ProgressState?) = with(binding) { + when (progressState) { + ProgressState.Loading -> { + root.beginDelayedTransition() + webView.hide() + groupError.hide() + groupAccept.hide() + groupLoading.show() + } + ProgressState.Done -> { + root.beginDelayedTransition() + groupError.hide() + groupLoading.hide() + webView.show() + groupAccept.show(!disclaimer.isAccepted()) + } + ProgressState.Error -> { + root.beginDelayedTransition() + webView.hide() + groupAccept.hide() + groupLoading.hide() + groupError.show() + } + else -> {} } - - webView.loadUrl(state.type.uri.toString()) } override fun handleOnBackPressed() { diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt new file mode 100644 index 0000000000..c1a024fc0c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt @@ -0,0 +1,68 @@ +package com.tangem.tap.features.disclaimer.ui + +import android.graphics.Bitmap +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient +import com.tangem.common.extensions.ifNotNull +import com.tangem.tap.features.wallet.redux.ProgressState + +class DisclaimerWebViewClient : WebViewClient() { + + var onProgressStateChanged: ((ProgressState) -> Unit)? = null + + private var loadingUrl: String? = null + private var loadedUrl: String? = null + + private var progressState: ProgressState = ProgressState.Loading + set(value) { + field = value + onProgressStateChanged?.invoke(value) + } + + fun reset() { + loadingUrl = null + loadedUrl = null + progressState = ProgressState.Loading + } + + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + + if (loadingUrl != url) progressState = ProgressState.Loading + loadingUrl = url + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + + if (loadedUrl != url) progressState = ProgressState.Done + loadedUrl = url + } + + override fun onReceivedError( + view: WebView?, + resourceRequest: WebResourceRequest?, + error: WebResourceError?, + ) { + super.onReceivedError(view, resourceRequest, error) + error?.let { progressState = ProgressState.Error } + } + + override fun onReceivedHttpError( + view: WebView?, + resourceRequest: WebResourceRequest?, + errorResponse: WebResourceResponse?, + ) { + super.onReceivedHttpError(view, resourceRequest, errorResponse) + + ifNotNull(resourceRequest, errorResponse) { request, response -> + if (request.url?.toString() != loadingUrl || response.statusCode < 400) return + if (progressState != ProgressState.Done) return + + progressState = ProgressState.Error + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 860939d53c..7c622df177 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -34,7 +34,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.statePrinter.printScanResponseState import com.tangem.tap.domain.statePrinter.printWalletState -import com.tangem.tap.domain.termsOfUse.CardTou import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState @@ -245,7 +244,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber - - + + + + +