From 15f12717f6664fe77cb79a1a1ea31afe275ec51b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 May 2023 16:06:36 +0300 Subject: [PATCH 01/14] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 17 +- .../java/com/tangem/tap/di/ActivityModule.kt | 42 +++ .../domain/scanCard/LegacyScanProcessor.kt | 272 +++++++++++++++++ .../tap/domain/scanCard/ScanCardProcessor.kt | 285 +++--------------- .../domain/scanCard/UseCaseScanProcessor.kt | 117 +++++++ .../repository/DefaultScanCardRepository.kt | 38 +++ .../utils/ScanCardExceptionConverter.kt | 47 +++ .../tap/domain/tokens/UserTokensRepository.kt | 2 + .../CustomTokenFeatureToggles.kt | 2 + .../DefaultCustomTokenFeatureToggles.kt | 3 + .../details/redux/DetailsMiddleware.kt | 13 +- .../tap/features/home/redux/HomeMiddleware.kt | 44 ++- .../middlewares/MultiWalletMiddleware.kt | 45 ++- .../redux/WalletSelectorMiddleware.kt | 2 +- .../welcome/redux/WelcomeMiddleware.kt | 7 +- .../tap/proxy/redux/DaggerGraphAction.kt | 6 +- .../tap/proxy/redux/DaggerGraphReducer.kt | 1 + .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../configs/feature_toggles_config.json | 8 +- .../tangem/domain/card/ScanCardException.kt | 6 +- .../com/tangem/domain/card/ScanCardUseCase.kt | 4 +- .../domain/core/chain/ChainProcessor.kt | 2 +- 22 files changed, 648 insertions(+), 317 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/ActivityModule.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 7ad61d6a45..ff361d442b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,10 +10,10 @@ 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.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.operations.backup.BackupService import com.tangem.sdk.extensions.init -import com.tangem.sdk.extensions.initWithBiometrics import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.OnActivityResultCallback @@ -77,6 +77,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var testerRouter: TesterRouter + @Inject + lateinit var injectedTangemSdk: TangemSdk + + @Inject + lateinit var injectedTangemSdkManager: TangemSdkManager + + @Inject + lateinit var scanCardUseCase: ScanCardUseCase + private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) @@ -89,8 +98,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac systemActions() store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config) - tangemSdkManager = TangemSdkManager(tangemSdk, this) + tangemSdk = injectedTangemSdk + tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) @@ -104,7 +113,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ), ) - store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter)) + store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter, scanCardUseCase)) } private fun initUserWalletsListManager() { diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt new file mode 100644 index 0000000000..96639f64e4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.di + +import android.content.Context +import androidx.fragment.app.FragmentActivity +import com.tangem.TangemSdk +import com.tangem.domain.card.ScanCardUseCase +import com.tangem.sdk.extensions.initWithBiometrics +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository +import com.tangem.tap.userTokensRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +internal object ActivityModule { + + @Provides + @ActivityScoped + fun provideTangemSdk(@ActivityContext context: Context): TangemSdk { + return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config) + } + + @Provides + @ActivityScoped + fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager { + return TangemSdkManager(tangemSdk, context) + } + + @Provides + @ActivityScoped + fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase { + return ScanCardUseCase( + tangemSdk = tangemSdk, + scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt new file mode 100644 index 0000000000..e5a3579e07 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -0,0 +1,272 @@ +package com.tangem.tap.domain.scanCard + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.backup.BackupService +import com.tangem.tap.* +import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor +import com.tangem.tap.common.extensions.addContext +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa +import com.tangem.tap.common.extensions.setContext +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.createDisclaimer +import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback +import com.tangem.tap.features.onboarding.OnboardingHelper +import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.rekotlin.Action + +internal object LegacyScanProcessor { + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return tangemSdkManager.scanProduct( + userTokensRepository, + cardId, + allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = withMainContext { + onProgressStateChange(true) + onScanStateChange(true) + + tangemSdkManager.changeDisplayedCardIdNumbersCount(null) + + val result = tangemSdkManager.scanProduct( + userTokensRepository = userTokensRepository, + cardId = cardId, + ) + + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + + result + .doOnFailure { error -> + onScanStateChange(false) + onFailure(error) + } + .doOnSuccess { scanResponse -> + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + + onScanStateChange(false) + sendAnalytics(analyticsEvent, scanResponse) + + checkForUnfinishedBackupForSaltPay( + backupService = backupService, + scanResponse = scanResponse, + onFailure = onFailure, + nextHandler = { scanResponse1 -> + showDisclaimerIfNeed( + scanResponse = scanResponse1, + disclaimerWillShow = disclaimerWillShow, + onFailure = onFailure, + nextHandler = { scanResponse2 -> + onScanSuccess( + scanResponse = scanResponse2, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, + onFailure = onFailure, + ) + }, + ) + }, + ) + } + } + + private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { + analyticsEvent?.let { + // this workaround needed to send CardWasScannedEvent without adding a context + val interceptor = CardContextInterceptor(scanResponse) + val params = it.params.toMutableMap() + interceptor.intercept(params) + it.params = params.toMap() + + Analytics.send(it) + } + } + + /** + * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards + * see BackupAction.CheckForUnfinishedBackup + * If user touches card other than Visa SaltPay - show dialog and block next processing + */ + private suspend inline fun checkForUnfinishedBackupForSaltPay( + backupService: BackupService, + scanResponse: ScanResponse, + nextHandler: (ScanResponse) -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { + nextHandler(scanResponse) + return + } + + val isTheSamePrimaryCard = backupService.primaryCardId + ?.let { it == scanResponse.card.cardId } + ?: false + + if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) + } else { + nextHandler(scanResponse) + } + } + + private suspend inline fun showDisclaimerIfNeed( + scanResponse: ScanResponse, + crossinline disclaimerWillShow: () -> Unit = {}, + crossinline nextHandler: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + val disclaimer = scanResponse.card.createDisclaimer() + store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) + + if (disclaimer.isAccepted()) { + nextHandler(scanResponse) + } else { + scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + arrayOf( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), + ), + ) + } + } + } + + @Suppress("LongMethod", "MagicNumber") + private suspend inline fun onScanSuccess( + scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + crossinline onWalletNotCreated: suspend () -> Unit, + crossinline onSuccess: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + val globalState = store.state.globalState + val tapWalletManager = globalState.tapWalletManager + tapWalletManager.updateConfigManager(scanResponse) + + store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + + val cardTypesResolver = scanResponse.cardTypesResolver + if (cardTypesResolver.isSaltPay()) { + if (cardTypesResolver.isSaltPayVisa()) { + val manager = SaltPayActivationManagerFactory( + blockchain = scanResponse.cardTypesResolver.getBlockchain(), + card = scanResponse.card, + ).create() + val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) + delay(500) + withMainContext { + when (result) { + is Result.Success -> { + Analytics.addContext(scanResponse) + val isOnboardingCase = result.data + if (isOnboardingCase) { + onWalletNotCreated() + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) + store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) + store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) + navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + } + } + is Result.Failure -> { + delay(DELAY_SDK_DIALOG_CLOSE) + SaltPayExceptionHandler.handle(result.error) + onFailure(TangemSdkError.ExceptionError(result.error)) + } + } + } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + if (scanResponse.card.backupStatus?.isActive == false) { + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) + } else { + Analytics.setContext(scanResponse) + onSuccess(scanResponse) + } + } + } else { + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) + onWalletNotCreated() + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + Analytics.setContext(scanResponse) + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + onWalletNotCreated() + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + } + } + } + } + + private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + onProgressStateChange(false) + } +} \ No newline at end of file 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 86f790ee67..0546a79db5 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 @@ -1,49 +1,31 @@ package com.tangem.tap.domain.scanCard +import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError -import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess -import com.tangem.common.services.Result -import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -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.paramsInterceptor.CardContextInterceptor -import com.tangem.tap.common.extensions.addContext -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa -import com.tangem.tap.common.extensions.setContext -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.createDisclaimer -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback -import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -// TODO: Create repository for that -object ScanCardProcessor { +// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead +internal object ScanCardProcessor { + private val isNewCardScanningEnabled: Boolean + get() = store.state.daggerGraphState + .get(DaggerGraphState::customTokenFeatureToggles) + .isNewCardScanningEnabled + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } else { + LegacyScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } + } + + @Suppress("LongParameterList") suspend fun scan( analyticsEvent: AnalyticsEvent? = null, cardId: String? = null, @@ -53,212 +35,29 @@ object ScanCardProcessor { disclaimerWillShow: () -> Unit = {}, onFailure: suspend (error: TangemError) -> Unit = {}, onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, - ) = withMainContext { - onProgressStateChange(true) - onScanStateChange(true) - - tangemSdkManager.changeDisplayedCardIdNumbersCount(null) - - val result = tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - cardId = cardId, - ) - - store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - - result - .doOnFailure { error -> - onScanStateChange(false) - onFailure(error) - } - .doOnSuccess { scanResponse -> - tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - - onScanStateChange(false) - sendAnalytics(analyticsEvent, scanResponse) - - checkForUnfinishedBackupForSaltPay( - backupService = backupService, - scanResponse = scanResponse, - onFailure = onFailure, - nextHandler = { scanResponse1 -> - showDisclaimerIfNeed( - scanResponse = scanResponse1, - disclaimerWillShow = disclaimerWillShow, - onFailure = onFailure, - nextHandler = { scanResponse2 -> - onScanSuccess( - scanResponse = scanResponse2, - onProgressStateChange = onProgressStateChange, - onSuccess = onSuccess, - onWalletNotCreated = onWalletNotCreated, - onFailure = onFailure, - ) - }, - ) - }, - ) - } - } - - private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { - analyticsEvent?.let { - // this workaround needed to send CardWasScannedEvent without adding a context - val interceptor = CardContextInterceptor(scanResponse) - val params = it.params.toMutableMap() - interceptor.intercept(params) - it.params = params.toMap() - - Analytics.send(it) - } - } - - /** - * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards - * see BackupAction.CheckForUnfinishedBackup - * If user touches card other than Visa SaltPay - show dialog and block next processing - */ - private suspend inline fun checkForUnfinishedBackupForSaltPay( - backupService: BackupService, - scanResponse: ScanResponse, - nextHandler: (ScanResponse) -> Unit, - onFailure: suspend (error: TangemError) -> Unit, ) { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - nextHandler(scanResponse) - return - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == scanResponse.card.cardId } - ?: false - - if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) + if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } else { - nextHandler(scanResponse) + LegacyScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } } - - private suspend inline fun showDisclaimerIfNeed( - scanResponse: ScanResponse, - crossinline disclaimerWillShow: () -> Unit = {}, - crossinline nextHandler: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, - ) { - val disclaimer = scanResponse.card.createDisclaimer() - store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) - - if (disclaimer.isAccepted()) { - nextHandler(scanResponse) - } else { - scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onFailure(TangemSdkError.UserCancelled()) - } - }, - ), - ), - ) - } - } - } - - @Suppress("LongMethod", "MagicNumber") - private suspend inline fun onScanSuccess( - scanResponse: ScanResponse, - crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, - crossinline onWalletNotCreated: suspend () -> Unit, - crossinline onSuccess: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, - ) { - val globalState = store.state.globalState - val tapWalletManager = globalState.tapWalletManager - tapWalletManager.updateConfigManager(scanResponse) - - store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - - val cardTypesResolver = scanResponse.cardTypesResolver - if (cardTypesResolver.isSaltPay()) { - if (cardTypesResolver.isSaltPayVisa()) { - val manager = SaltPayActivationManagerFactory( - blockchain = scanResponse.cardTypesResolver.getBlockchain(), - card = scanResponse.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) - delay(500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(scanResponse) - val isOnboardingCase = result.data - if (isOnboardingCase) { - onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - onFailure(TangemSdkError.ExceptionError(result.error)) - } - } - } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - if (scanResponse.card.backupStatus?.isActive == false) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - Analytics.setContext(scanResponse) - onSuccess(scanResponse) - } - } - } else { - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - Analytics.addContext(scanResponse) - onWalletNotCreated() - store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) { onProgressStateChange(it) } - } else { - Analytics.setContext(scanResponse) - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - onWalletNotCreated() - store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) - navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - } - } - - private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { - delay(DELAY_SDK_DIALOG_CLOSE) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - onProgressStateChange(false) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt new file mode 100644 index 0000000000..52d6cf4339 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -0,0 +1,117 @@ +package com.tangem.tap.domain.scanCard + +import arrow.fx.coroutines.resourceScope +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.card.ScanCardException +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.backupService +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.scanCard.chains.* +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.preferencesStorage +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.store +import kotlinx.coroutines.delay + +internal object UseCaseScanProcessor { + private val scanCardExceptionConverter = ScanCardExceptionConverter() + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + return scanCardUseCase(cardId, allowsRequestAccessCodeFromRepository) + .fold( + ifLeft = { CompletionResult.Failure(scanCardExceptionConverter.convertBack(it)) }, + ifRight = { CompletionResult.Success(it) }, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = progressScope(onProgressStateChange) { + onScanStateChange(true) + + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + val chains = buildList { + add(ScanningFinishedChain { onScanStateChange(false) }) + if (analyticsEvent != null) { + add(AnalyticsChain(analyticsEvent)) + } + add(CheckForUnfinishedSaltPayBackupChain(backupService)) + add(DisclaimerChain(store, disclaimerWillShow)) + add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage)) + } + + scanCardUseCase(cardId, afterScanChains = chains) + .map { onSuccess(it) } + .mapLeft { proceedWithException(it, onWalletNotCreated, onFailure) } + } + + private suspend fun proceedWithException( + exception: ScanCardException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanCardException.ChainException -> proceedWithScanChainException( + exception, + onWalletNotCreated, + onFailure, + ) + is ScanCardException.UnknownException, + is ScanCardException.UserCancelled, + is ScanCardException.WrongAccessCode, + is ScanCardException.WrongCardId, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun proceedWithScanChainException( + exception: ScanCardException.ChainException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanChainException.OnboardingNeeded -> { + navigateTo(exception.onboardingRoute) + onWalletNotCreated() + } + is ScanChainException.PutSaltPayVisaCard, + is ScanChainException.DisclaimerWasCanceled, + is ScanChainException.CheckForSaltPayOnboardingCaseException, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun progressScope( + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + action: suspend () -> Unit, + ) = resourceScope { + install( + acquire = { onProgressStateChange(true) }, + release = { _, _ -> onProgressStateChange(false) }, + ) + + action() + } + + private suspend inline fun navigateTo(screen: AppScreen) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt new file mode 100644 index 0000000000..2de676ba6c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.domain.scanCard.repository + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.common.CompletionResult +import com.tangem.domain.card.ScanCardException +import com.tangem.domain.card.repository.ScanCardRepository +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.domain.tokens.UserTokensRepository + +internal class DefaultScanCardRepository( + // FIXME: The repository should not depend on another repository. + // But now we need to provide loadBlockchainsToDerive() to ScanProductTask and it's hard to move this method from + // UserTokensRepository. + private val userTokensRepository: UserTokensRepository, + private val tangemSdkManager: TangemSdkManager, +) : ScanCardRepository { + + private val exceptionConverter = ScanCardExceptionConverter() + + override suspend fun scanCard( + cardId: String?, + allowRequestAccessCodeFromStorage: Boolean, + ): Either = either { + when ( + val result = tangemSdkManager.scanProduct( + cardId = cardId, + userTokensRepository = userTokensRepository, + allowsRequestAccessCodeFromRepository = allowRequestAccessCodeFromStorage, + ) + ) { + is CompletionResult.Success -> result.data + is CompletionResult.Failure -> raise(exceptionConverter.convert(result.error)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt new file mode 100644 index 0000000000..f04df13017 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt @@ -0,0 +1,47 @@ +package com.tangem.tap.domain.scanCard.utils + +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.domain.card.ScanCardException +import com.tangem.tap.domain.scanCard.chains.ScanChainException +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.utils.converter.TwoWayConverter + +internal class ScanCardExceptionConverter : TwoWayConverter { + override fun convert(value: TangemError): ScanCardException = when (value) { + is TangemSdkError -> convertTangemSdkError(value) + else -> ScanCardException.UnknownException(value) + } + + override fun convertBack(value: ScanCardException): TangemError { + return when (value) { + is ScanCardException.UnknownException -> TangemSdkError.ExceptionError(value) + is ScanCardException.UserCancelled -> TangemSdkError.UserCancelled() + is ScanCardException.WrongAccessCode -> TangemSdkError.WrongAccessCode() + is ScanCardException.WrongCardId -> TangemSdkError.WrongCardNumber(value.cardId) + is ScanCardException.ChainException -> concertScanChainException(value) + } + } + + private fun convertTangemSdkError(value: TangemSdkError): ScanCardException = when (value) { + is TangemSdkError.UserCancelled -> ScanCardException.UserCancelled + is TangemSdkError.WrongAccessCode, + is TangemSdkError.WrongPasscode, + -> ScanCardException.WrongAccessCode + is TangemSdkError.WrongCardNumber -> ScanCardException.WrongCardId(value.cardId) + else -> ScanCardException.UnknownException(value) + } + + private fun concertScanChainException(value: ScanCardException.ChainException): TangemError { + return when (val e = value as? ScanChainException) { + is ScanChainException.DisclaimerWasCanceled -> TangemSdkError.UserCancelled() + is ScanChainException.PutSaltPayVisaCard -> TangemSdkError.ExceptionError( + SaltPayActivationError.PutVisaCard, + ) + is ScanChainException.CheckForSaltPayOnboardingCaseException, + is ScanChainException.OnboardingNeeded, + null, + -> TangemSdkError.ExceptionError(e?.cause) + } + } +} \ 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 255d8611c5..bb1c27c80e 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 @@ -47,6 +47,7 @@ class UserTokensRepository( storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens) } + // FIXME: Move to data layer suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { val userWalletId = getUserWalletId(card) ?: return@withContext emptyList() val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks() @@ -62,6 +63,7 @@ class UserTokensRepository( return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList() } + // FIXME: Move to user wallet config private fun loadDemoCurrencies(): List { return DemoHelper.config.demoBlockchains .map { blockchain -> diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt index 6de26f4bd2..b64f9efd14 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt @@ -9,4 +9,6 @@ interface CustomTokenFeatureToggles { /** Availability of redesigned screen (internal feature) */ val isRedesignedScreenEnabled: Boolean + + val isNewCardScanningEnabled: Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt index fbdc41b2c0..a19dde5403 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt @@ -16,4 +16,7 @@ internal class DefaultCustomTokenFeatureToggles( override val isRedesignedScreenEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED") + + override val isNewCardScanningEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED") } \ No newline at end of file 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 b10ad9de2a..acc0130ea9 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 @@ -24,6 +24,7 @@ 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.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation @@ -32,6 +33,13 @@ 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 import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver +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 com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -72,10 +80,7 @@ class DetailsMiddleware { is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - allowsRequestAccessCodeFromRepository = true, - ) + ScanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) .doOnSuccess { scanResponse -> // if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) 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 f534813ecb..9d7d775e1d 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 @@ -3,9 +3,11 @@ package com.tangem.tap.features.home.redux import com.tangem.common.doOnFailure import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -25,11 +27,6 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction -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 import org.rekotlin.Action @@ -80,6 +77,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) + ScanCardProcessor.scan( analyticsEvent = analyticsEvent, onProgressStateChange = { showProgress -> @@ -93,6 +91,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, onFailure = { + Timber.e(it, "Unable to scan card") changeButtonState(ButtonState.ENABLED) }, onSuccess = { scanResponse -> @@ -101,26 +100,23 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { ) } -fun proceedWithScanResponse(scanResponse: ScanResponse) { - scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - if (userWallet == null) { - Timber.e("User wallet not created") - return@launch - } - - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet = userWallet) } - } - .doOnResult { - store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) - navigateTo(AppScreen.Wallet) - } +private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build().guard { + Timber.e("User wallet not created") + return@launch } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet = userWallet) } + } + .doOnResult { + store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) + navigateTo(AppScreen.Wallet) + } } private suspend fun navigateTo(appScreen: AppScreen) { 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 d7c7bd6d85..607d8c63c4 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 @@ -15,13 +15,12 @@ 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.model.UserWallet +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog 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.walletCurrenciesManager import kotlinx.coroutines.Dispatchers @@ -96,33 +95,27 @@ class MultiWalletMiddleware { return } store.state.globalState.topUpController?.scanToGetDerivations() - scanAndUpdateCard(selectedUserWallet, walletState) + scanAndUpdateCard(selectedUserWallet) } else -> {} } } - private fun scanAndUpdateCard(selectedUserWallet: UserWallet, state: WalletState?) = - scope.launch(Dispatchers.Default) { - tangemSdkManager.scanProduct( - cardId = selectedUserWallet.cardId, - userTokensRepository = userTokensRepository, - additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, - allowsRequestAccessCodeFromRepository = true, - ) - .flatMap { scanResponse -> - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { userWallet -> - userWallet.copy( - scanResponse = scanResponse, - ) - }, - ) - } - .doOnSuccess { updatedUserWallet -> - store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) - store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) - } - } + private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) { + ScanCardProcessor.scan(selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true) + .flatMap { scanResponse -> + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + } + .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) + } + } } \ No newline at end of file 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 59336d8989..077c90ca2c 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 @@ -217,7 +217,7 @@ internal class WalletSelectorMiddleware { private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) - return tangemSdkManager.scanProduct(userTokensRepository) + return ScanCardProcessor.scan() .map { scanResponse -> val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() if (scannedUserWalletId == userWallet.walletId) { 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 6bd65c13eb..a7dc158068 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 @@ -8,6 +8,7 @@ import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.extensions.dispatchOnMain @@ -21,12 +22,6 @@ import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.signin.redux.SignInAction -import com.tangem.tap.intentHandler -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.launch import org.rekotlin.Middleware import timber.log.Timber diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 2952ed0257..3c6621663b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,9 +1,13 @@ package com.tangem.tap.proxy.redux +import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import org.rekotlin.Action sealed interface DaggerGraphAction : Action { - data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction + data class SetActivityDependencies( + val testerRouter: TesterRouter, + val scanCardUseCase: ScanCardUseCase, + ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 43cb94af5c..267665cce3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -14,6 +14,7 @@ object DaggerGraphReducer { return when (action) { is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( testerRouter = action.testerRouter, + scanCardUseCase = action.scanCardUseCase, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 73fc659cf8..3ffa120662 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -11,6 +12,7 @@ data class DaggerGraphState( val testerRouter: TesterRouter? = null, val networkConnectionManager: NetworkConnectionManager? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, + val scanCardUseCase: ScanCardUseCase? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index c44daa2e41..d2cc7402fd 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -5,6 +5,10 @@ }, { "name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED", - "version": "4.6.0" + "version": "4.7.0" + }, + { + "name": "NEW_CARD_SCANNING_ENABLED", + "version": "4.7.0" } -] \ No newline at end of file +] diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt index b9d3e287ac..3f153ee689 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt @@ -2,13 +2,13 @@ package com.tangem.domain.card // TODO: May be add new error types sealed class ScanCardException : Exception() { - object WrongCardId : ScanCardException() - object UserCancelled : ScanCardException() object WrongAccessCode : ScanCardException() open class ChainException : ScanCardException() - class UnknownException(override val cause: Exception) : ScanCardException() + data class UnknownException(override val cause: Exception) : ScanCardException() + + data class WrongCardId(val cardId: String) : ScanCardException() } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt index 0365ac58ae..b6bab97249 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt @@ -35,14 +35,14 @@ class ScanCardUseCase( * Defaults to null. * @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed. * Defaults to false. - * @param afterScanChains An array of chains that should be executed after a successful card scan operation. + * @param afterScanChains A list of chains that should be executed after a successful card scan operation. * Defaults to an empty array. * @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse]. */ suspend operator fun invoke( cardId: String? = null, allowRequestAccessCodeFromStorage: Boolean = false, - afterScanChains: Array> = emptyArray(), + afterScanChains: List> = emptyList(), ): Either { resetCardIdDisplayFormat() scanChainProcessor.addChains(afterScanChains) diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt index 13101be7fd..c1cac09eb3 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt @@ -21,7 +21,7 @@ class ChainProcessor { * Adds chains to the existing list of chains to be executed. * @param chains the chains to be added to the list */ - fun addChains(chains: Array>) { + fun addChains(chains: List>) { this.chains.addAll(chains) } From 2782d8a8463f50dafccf1edbc806eb69c22b545a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Jun 2023 17:31:20 +0800 Subject: [PATCH 02/14] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../java/com/tangem/tap/TapApplication.kt | 5 + .../tap/common/extensions/Navigation.kt | 18 +- .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../configs/feature_toggles_config.json | 6 +- .../src/main/res/drawable/ic_tap_card_24.xml | 0 .../featuretoggles/WalletFeatureToggles.kt | 12 ++ features/wallet/impl/build.gradle.kts | 2 +- .../wallet/di/WalletFeatureTogglesModule.kt | 21 ++ .../DefaultWalletFeatureToggles.kt | 19 ++ .../wallet/presentation/WalletFragment.kt | 41 ++++ .../wallet/presentation/state/TokenUIState.kt | 9 + .../{ui/config => state}/TokenV2Config.kt | 6 +- .../presentation/state/WalletCardState.kt | 92 +++++++++ .../presentation/state/WalletStateHolder.kt | 28 +++ .../feature/wallet/presentation/ui/Tokens.kt | 33 ++- .../wallet/presentation/ui/WalletHeader.kt | 193 ------------------ .../presentation/ui/WalletPreviewData.kt | 41 ++++ .../wallet/presentation/ui/WalletScreen.kt | 25 +++ .../presentation/ui/components/WalletCard.kt | 192 +++++++++++++++++ .../ui/components/WalletHeader.kt | 107 ++++++++++ .../ui/config/WalletHeaderConfig.kt | 13 -- .../presentation/ui/state/BalanceUIState.kt | 5 - .../presentation/ui/state/TokenUIState.kt | 9 - .../res/drawable/img_tangem_logo_90_24.xml | 28 +++ gradle/dependencies.toml | 4 +- 26 files changed, 662 insertions(+), 251 deletions(-) rename {app => core/ui}/src/main/res/drawable/ic_tap_card_24.xml (100%) create mode 100644 features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ui/config => state}/TokenV2Config.kt (80%) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt create mode 100644 features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d3445cf24..b536124d5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,8 @@ dependencies { implementation(project(":features:swap:data")) implementation(project(":features:tester:api")) implementation(project(":features:tester:impl")) + implementation(project(":features:wallet:api")) + implementation(project(":features:wallet:impl")) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b224a7f81a..65d7a0c72c 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -21,6 +21,7 @@ import com.tangem.datasource.config.models.Config import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -133,6 +134,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var preferencesDataSource: PreferencesDataSource + @Inject + lateinit var walletFeatureToggles: WalletFeatureToggles + override fun onCreate() { super.onCreate() @@ -146,6 +150,7 @@ class TapApplication : Application(), ImageLoaderFactory { assetReader = assetReader, networkConnectionManager = networkConnectionManager, customTokenFeatureToggles = customTokenFeatureToggles, + walletFeatureToggles = walletFeatureToggles, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 806a46dd1d..1d0af74a36 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,11 +1,7 @@ package com.tangem.tap.common.extensions import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.* import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen @@ -37,6 +33,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.wallet.R import timber.log.Timber +import com.tangem.feature.wallet.presentation.WalletFragment as RedesignedWalletFragment import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment fun FragmentActivity.openFragment( @@ -138,7 +135,16 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.OnboardingWallet -> OnboardingWalletFragment() AppScreen.OnboardingTwins -> TwinsCardsFragment() AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() - AppScreen.Wallet -> WalletFragment() + AppScreen.Wallet -> { + val featureToggles = store.state.daggerGraphState.get( + getDependency = DaggerGraphState::walletFeatureToggles, + ) + if (featureToggles.isRedesignedScreenEnabled) { + RedesignedWalletFragment() + } else { + WalletFragment() + } + } AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 3ffa120662..5e823c02da 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -13,6 +14,7 @@ data class DaggerGraphState( val networkConnectionManager: NetworkConnectionManager? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, val scanCardUseCase: ScanCardUseCase? = null, + val walletFeatureToggles: WalletFeatureToggles? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index d2cc7402fd..174175e4e6 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -10,5 +10,9 @@ { "name": "NEW_CARD_SCANNING_ENABLED", "version": "4.7.0" + }, + { + "name": "REDESIGNED_WALLET_SCREEN_ENABLED", + "version": "undefined" } -] +] \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_tap_card_24.xml b/core/ui/src/main/res/drawable/ic_tap_card_24.xml similarity index 100% rename from app/src/main/res/drawable/ic_tap_card_24.xml rename to core/ui/src/main/res/drawable/ic_tap_card_24.xml diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt new file mode 100644 index 0000000000..5af5379a9e --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.features.wallet.featuretoggles + +/** + * Wallet feature toggles + * +[REDACTED_AUTHOR] + */ +interface WalletFeatureToggles { + + /** Availability of redesigned screen */ + val isRedesignedScreenEnabled: Boolean +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 873d96fbe0..c0a25c65df 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(deps.compose.constraintLayout) implementation(deps.compose.material) implementation(deps.compose.foundation) + implementation(deps.compose.material3) implementation(deps.compose.navigation) implementation(deps.compose.navigation.hilt) implementation(deps.compose.ui) @@ -32,5 +33,4 @@ dependencies { /** Feature Apis */ implementation(project(":features:wallet:api")) - } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt new file mode 100644 index 0000000000..ed23c5d4c3 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.wallet.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object WalletFeatureTogglesModule { + + @Provides + @Singleton + fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles { + return DefaultWalletFeatureToggles(featureTogglesManager = featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt new file mode 100644 index 0000000000..71696ceba1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.wallet.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles + +/** + * Default implementation of CustomToken feature toggles + * + * @property featureTogglesManager manager for getting information about the availability of feature toggles + * +[REDACTED_AUTHOR] + */ +internal class DefaultWalletFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : WalletFeatureToggles { + + override val isRedesignedScreenEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_WALLET_SCREEN_ENABLED") +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt new file mode 100644 index 0000000000..3c6f1114ad --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.wallet.presentation + +import android.os.Bundle +import android.transition.TransitionInflater +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.core.view.WindowCompat +import androidx.fragment.app.Fragment +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import dagger.hilt.android.AndroidEntryPoint + +/** + * Wallet fragment + * +[REDACTED_AUTHOR] + */ +@AndroidEntryPoint +class WalletFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + + with(TransitionInflater.from(requireContext())) { + enterTransition = inflateTransition(R.transition.slide_right) + exitTransition = inflateTransition(R.transition.fade) + } + + return ComposeView(inflater.context).apply { + setContent { + isTransitionGroup = true + + TangemTheme { + // TODO: [REDACTED_TASK_KEY] Call WalletScreen + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt new file mode 100644 index 0000000000..ace321b30a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt @@ -0,0 +1,9 @@ +package com.tangem.feature.wallet.presentation.state + +internal enum class TokenUIState { + LOADED, LOADING +} + +internal enum class TokenOptionsUIState { + VISIBLE, HIDDEN, DRAG, UNREACHABLE +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt similarity index 80% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt index bfa34bae3f..dca7440c39 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt @@ -1,8 +1,6 @@ -package com.tangem.feature.wallet.presentation.ui.config +package com.tangem.feature.wallet.presentation.state import androidx.annotation.DrawableRes -import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState -import com.tangem.feature.wallet.presentation.ui.state.TokenUIState /** * Token config @@ -18,7 +16,7 @@ import com.tangem.feature.wallet.presentation.ui.state.TokenUIState * @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc * @property hasPending pending tx in blockchain */ -data class TokenV2Config( +internal data class TokenV2Config( val name: String, val amount: String, val fiatAmount: String, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt new file mode 100644 index 0000000000..0837a3ef7d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt @@ -0,0 +1,92 @@ +package com.tangem.feature.wallet.presentation.state + +import androidx.annotation.DrawableRes + +/** Wallet card state */ +internal sealed interface WalletCardState { + /** Id */ + val id: String + + /** Title */ + val title: String + + /** Additional wallet information */ + val additionalInfo: String + + /** Wallet image resource id */ + @get:DrawableRes + val imageResId: Int + + /** Lambda be invoked when card is clicked */ + val onClick: (() -> Unit)? + + /** + * Wallet card content state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + * @property balance wallet balance + */ + data class Content( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)? = null, + val balance: String, + ) : WalletCardState + + /** + * Wallet card loading state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class Loading( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState + + /** + * Wallet card hidden content state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class HiddenContent( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState + + /** + * Wallet card error state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class Error( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt new file mode 100644 index 0000000000..c69ab425ae --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.wallet.presentation.state + +/** + * Wallet state holder + * + * @property onBackClick lambda be invoked when back button is clicked + * @property headerConfig header config + * +[REDACTED_AUTHOR] + */ +internal data class WalletStateHolder( + val onBackClick: () -> Unit, + val headerConfig: HeaderConfig, +) { + + /** + * Header config + * + * @property wallets list of wallets states + * @property onScanCardClick lambda be invoked when scan card button is clicked + * @property onMoreClick lambda be invoked when more button is clicked + */ + data class HeaderConfig( + val wallets: List, + val onScanCardClick: () -> Unit, + val onMoreClick: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt index 6d178bdab6..45b3eb843d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt @@ -5,10 +5,9 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,20 +20,16 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest -import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTypography -import com.tangem.feature.wallet.presentation.ui.config.PriceChange -import com.tangem.feature.wallet.presentation.ui.config.PriceChangeType -import com.tangem.feature.wallet.presentation.ui.config.TokenV2Config -import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState -import com.tangem.feature.wallet.presentation.ui.state.TokenUIState +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.* private const val DOTS = "•••" @Composable -fun TokenItemV2(config: TokenV2Config) { +internal fun TokenItemV2(config: TokenV2Config) { when (config.tokenUIState) { TokenUIState.LOADED -> LoadedTokenState(config) TokenUIState.LOADING -> LoadingTokenState() @@ -167,7 +162,6 @@ private fun LoadingTokenState() { } } -@OptIn(ExperimentalMaterialApi::class) @Composable private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { Surface( @@ -224,12 +218,15 @@ private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChang ) { val iconChangeArrow: Int val changeTextColor: Color - if (priceChange.type == PriceChangeType.UP) { - iconChangeArrow = R.drawable.img_arrow_up_8 - changeTextColor = TangemTheme.colors.text.accent - } else { - iconChangeArrow = R.drawable.img_arrow_down_8 - changeTextColor = TangemTheme.colors.text.warning + when (priceChange.type) { + PriceChangeType.UP -> { + iconChangeArrow = R.drawable.img_arrow_up_8 + changeTextColor = TangemTheme.colors.text.accent + } + PriceChangeType.DOWN -> { + iconChangeArrow = R.drawable.img_arrow_down_8 + changeTextColor = TangemTheme.colors.text.warning + } } Image( modifier = Modifier.align(Alignment.CenterVertically), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt deleted file mode 100644 index 13eb2f12f3..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import com.tangem.core.ui.R -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.ui.config.WalletHeaderConfig -import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState - -private const val DOTS = "•••" - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun WalletHeaderCard(config: WalletHeaderConfig, modifier: Modifier = Modifier) { - Surface( - modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108), - shape = TangemTheme.shapes.roundedCornersXMedium, - color = TangemTheme.colors.background.primary, - onClick = config.onClick ?: {}, - enabled = config.onClick != null, - ) { - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing12), - ) { - val (balanceBlock, imageItem) = createRefs() - Column( - modifier = Modifier.constrainAs(balanceBlock) { - centerVerticallyTo(parent) - start.linkTo(parent.start) - end.linkTo(imageItem.start) - width = Dimension.fillToConstraints - }, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - HeaderWalletName( - walletName = config.walletName, - balanceState = config.balanceState, - ) - HeaderBalanceTitle( - balance = config.balance, - balanceState = config.balanceState, - ) - Text( - text = config.additionalInfo, - color = TangemTheme.colors.text.disabled, - style = TangemTheme.typography.caption, - ) - } - val imageWidth = TangemTheme.dimens.size120 - Image( - modifier = Modifier.constrainAs(imageItem) { - centerVerticallyTo(parent) - top.linkTo(parent.top) - end.linkTo(parent.end) - height = Dimension.fillToConstraints - width = Dimension.value(imageWidth) - }, - painter = config.cardImage, - contentDescription = null, - contentScale = ContentScale.FillWidth, - ) - } - } -} - -@Composable -private fun HeaderWalletName(walletName: String, balanceState: BalanceUIState) { - if (balanceState == BalanceUIState.HIDDEN) { - Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - Text( - text = walletName, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - maxLines = 1, - ) - Icon( - modifier = Modifier.size(size = TangemTheme.dimens.size20), - painter = painterResource(id = R.drawable.ic_eye_off_24), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - ) - } - } else { - Text( - text = walletName, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - maxLines = 1, - ) - } -} - -@Composable -private fun HeaderBalanceTitle(balance: String, balanceState: BalanceUIState) { - when (balanceState) { - BalanceUIState.VISIBLE -> { - ResizableText( - text = balance, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), - modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32), - ) - } - BalanceUIState.LOADING -> { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size102, - height = TangemTheme.dimens.size24, - ), - ) - } - BalanceUIState.HIDDEN -> { - Text( - text = DOTS, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - ) - } - BalanceUIState.NONE -> { - Text( - text = "—", - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - ) - } - } -} - -// region Preview - -@Composable -private fun WarningsPreview() { - val walletHeaderConfig = WalletHeaderConfig( - walletName = "Wallet 1", - balance = "8923,05 $", - additionalInfo = "3 cards • Seed enabled", - balanceState = BalanceUIState.VISIBLE, - cardImage = painterResource(id = R.drawable.ill_businessman_3d), - onClick = {}, - ) - Column(modifier = Modifier.fillMaxWidth()) { - WalletHeaderCard( - config = walletHeaderConfig, - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.HIDDEN), - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.LOADING), - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.NONE), - ) - SpacerH32() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InLightTheme() { - TangemTheme(isDark = false) { - WarningsPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InDarkTheme() { - TangemTheme(isDark = true) { - WarningsPreview() - } -} - -// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt new file mode 100644 index 0000000000..535b09f1b4 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.wallet.presentation.ui + +import com.tangem.core.ui.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import java.util.UUID + +internal object WalletPreviewData { + + val walletCardContent = WalletCardState.Content( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + balance = "8923,05 $", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardLoading = WalletCardState.Loading( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardHiddenContent = WalletCardState.HiddenContent( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardError = WalletCardState.Error( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt new file mode 100644 index 0000000000..dc166a77a2 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.wallet.presentation.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import com.tangem.feature.wallet.presentation.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.ui.components.WalletHeader + +/** + * Wallet screen + * + * @param state screen state + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun WalletScreen(state: WalletStateHolder) { + BackHandler(onBack = state.onBackClick) + + Scaffold( + topBar = { WalletHeader(config = state.headerConfig) }, + ) { + // TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt new file mode 100644 index 0000000000..25a427408a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt @@ -0,0 +1,192 @@ +package com.tangem.feature.wallet.presentation.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import com.tangem.core.ui.components.FontSizeRange +import com.tangem.core.ui.components.ResizableText +import com.tangem.core.ui.components.ShimmerRectangle +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import com.tangem.feature.wallet.presentation.ui.WalletPreviewData + +private const val DOTS = "•••" + +/** + * Wallet card + * + * @param state state + * @param modifier modifier + */ +@Composable +internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108), + shape = TangemTheme.shapes.roundedCornersXMedium, + color = TangemTheme.colors.background.primary, + onClick = state.onClick ?: {}, + enabled = state.onClick != null, + ) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12), + ) { + val (balanceBlock, imageItem) = createRefs() + Column( + modifier = Modifier.constrainAs(balanceBlock) { + centerVerticallyTo(parent) + start.linkTo(parent.start) + end.linkTo(imageItem.start) + width = Dimension.fillToConstraints + }, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Title(state) + Balance(state) + AdditionalInfo(description = state.additionalInfo) + } + + val imageWidth = TangemTheme.dimens.size120 + WalletImage( + id = state.imageResId, + modifier = Modifier.constrainAs(imageItem) { + centerVerticallyTo(parent) + top.linkTo(parent.top) + end.linkTo(parent.end) + height = Dimension.fillToConstraints + width = Dimension.value(imageWidth) + }, + ) + } + } +} + +@Composable +private fun Title(state: WalletCardState) { + when (state) { + is WalletCardState.HiddenContent -> { + Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { + Text( + text = state.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + Icon( + modifier = Modifier.size(size = TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_eye_off_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + } + else -> { + Text( + text = state.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + } + } +} + +@Composable +private fun Balance(state: WalletCardState) { + when (state) { + is WalletCardState.Content -> { + ResizableText( + text = state.balance, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), + modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32), + ) + } + is WalletCardState.Loading -> { + ShimmerRectangle( + modifier = Modifier.size( + width = TangemTheme.dimens.size102, + height = TangemTheme.dimens.size24, + ), + ) + } + is WalletCardState.HiddenContent -> { + Text( + text = DOTS, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + } + is WalletCardState.Error -> { + Text( + text = "—", + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + } + } +} + +@Composable +private fun AdditionalInfo(description: String) { + Text( + text = description, + color = TangemTheme.colors.text.disabled, + style = TangemTheme.typography.caption, + ) +} + +@Composable +private fun WalletImage(@DrawableRes id: Int, modifier: Modifier = Modifier) { + Image( + painter = painterResource(id), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.FillWidth, + ) +} + +// region Preview + +@Preview +@Composable +private fun Preview_WalletCard_LightTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) { + TangemTheme(isDark = false) { + WalletCard(state) + } +} + +@Preview +@Composable +private fun Preview_WalletCard_DarkTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) { + TangemTheme(isDark = true) { + WalletCard(state) + } +} + +private class WalletCardStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.walletCardContent, + WalletPreviewData.walletCardLoading, + WalletPreviewData.walletCardHiddenContent, + WalletPreviewData.walletCardError, + ), +) + +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt new file mode 100644 index 0000000000..0c2169bb16 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt @@ -0,0 +1,107 @@ +package com.tangem.feature.wallet.presentation.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import com.tangem.feature.wallet.presentation.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.ui.WalletPreviewData + +/** + * Wallet screen header + * + * @param config config + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.secondary) + .padding(bottom = TangemTheme.dimens.spacing14), + ) { + TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick) + LazyRow( + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + items(items = config.wallets, key = WalletCardState::id) { state -> + WalletCard( + state = state, + modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32), + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onScanCardClick: () -> Unit, onMoreClick: () -> Unit) { + TopAppBar( + title = { + Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null) + }, + actions = { + IconButton(onClick = onScanCardClick) { + Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card") + } + IconButton(onClick = onMoreClick) { + Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More") + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = TangemTheme.colors.background.secondary, + titleContentColor = TangemTheme.colors.icon.primary1, + actionIconContentColor = TangemTheme.colors.icon.primary1, + ), + ) +} + +@Preview +@Composable +private fun Preview_WalletHeader_LightTheme( + @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, +) { + TangemTheme(isDark = false) { + WalletHeader(state) + } +} + +@Preview +@Composable +private fun Preview_WalletHeader_DarkTheme( + @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, +) { + TangemTheme(isDark = true) { + WalletHeader(state) + } +} + +private class WalletHeaderProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletStateHolder.HeaderConfig( + wallets = listOf( + WalletPreviewData.walletCardContent, + WalletPreviewData.walletCardLoading, + WalletPreviewData.walletCardHiddenContent, + WalletPreviewData.walletCardError, + ), + onScanCardClick = {}, + onMoreClick = {}, + ), + ), +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt deleted file mode 100644 index 4a621be4e5..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.config - -import androidx.compose.ui.graphics.painter.Painter -import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState - -data class WalletHeaderConfig( - val walletName: String, - val balance: String, - val additionalInfo: String, - val balanceState: BalanceUIState, - val cardImage: Painter, - val onClick: (() -> Unit)?, -) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt deleted file mode 100644 index c4a22f046c..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.state - -enum class BalanceUIState { - VISIBLE, LOADING, HIDDEN, NONE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt deleted file mode 100644 index 0ef9163e07..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.state - -enum class TokenUIState { - LOADED, LOADING -} - -enum class TokenOptionsUIState { - VISIBLE, HIDDEN, DRAG, UNREACHABLE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml b/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml new file mode 100644 index 0000000000..2fa7574ea9 --- /dev/null +++ b/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 1ad8e1ccca..f7f6cb31df 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -25,6 +25,7 @@ compose-compiler = "1.4.3" compose-runtime = "1.3.3" compose-foundation = "1.3.1" compose-material = "1.4.2" +compose-material3 = "1.1.0" compose-constraint = "1.0.1" compose-navigation = "2.5.3" compose-accompanist = "0.28.0" @@ -132,6 +133,7 @@ compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose-runtime" } compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-foundation" } compose-material = { module = "androidx.compose.material:material", version.ref = "compose-material" } +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } compose-constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "compose-constraint" } compose-shimmer = { module = "com.valentinilk.shimmer:compose-shimmer", version.ref = "compose-shimmer" } compose-coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" } @@ -211,5 +213,5 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } -reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } +reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } # endregion Other From 8262c72f40ade35f8fa41c2ab806f83496f661fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Jun 2023 20:10:08 +0800 Subject: [PATCH 03/14] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 12 ++++- .../tap/common/extensions/Navigation.kt | 5 +- .../tap/proxy/redux/DaggerGraphAction.kt | 2 + .../tap/proxy/redux/DaggerGraphReducer.kt | 1 + .../tap/proxy/redux/DaggerGraphState.kt | 2 + features/wallet/api/build.gradle.kts | 5 ++ .../wallet/navigation/WalletRouter.kt | 14 +++++ .../feature/wallet/di/WalletRouterModule.kt | 18 +++++++ .../wallet/presentation/WalletFragment.kt | 26 +++++++--- .../router/DefaultWalletRouter.kt | 51 +++++++++++++++++++ .../presentation/router/InnerWalletRouter.kt | 23 +++++++++ .../presentation/router/WalletScreens.kt | 10 ++++ .../presentation/ui/WalletPreviewData.kt | 10 ++++ .../ui/components/WalletHeader.kt | 33 ++++++++---- .../viewmodels/WalletViewModel.kt | 36 +++++++++++++ 15 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletScreens.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index ff361d442b..0dae1204d4 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -12,6 +12,7 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.TangemSdk import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter +import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.operations.backup.BackupService import com.tangem.sdk.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder @@ -86,6 +87,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var scanCardUseCase: ScanCardUseCase + @Inject + lateinit var walletRouter: WalletRouter + private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) @@ -113,7 +117,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ), ) - store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter, scanCardUseCase)) + store.dispatch( + DaggerGraphAction.SetActivityDependencies( + testerRouter = testerRouter, + scanCardUseCase = scanCardUseCase, + walletRouter = walletRouter, + ), + ) } private fun initUserWalletsListManager() { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 1d0af74a36..f45d14c864 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -33,7 +33,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.wallet.R import timber.log.Timber -import com.tangem.feature.wallet.presentation.WalletFragment as RedesignedWalletFragment import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment fun FragmentActivity.openFragment( @@ -140,7 +139,9 @@ private fun fragmentFactory(screen: AppScreen): Fragment { getDependency = DaggerGraphState::walletFeatureToggles, ) if (featureToggles.isRedesignedScreenEnabled) { - RedesignedWalletFragment() + store.state.daggerGraphState + .get(getDependency = DaggerGraphState::walletRouter) + .getEntryFragment() } else { WalletFragment() } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 3c6621663b..99734202b0 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter +import com.tangem.features.wallet.navigation.WalletRouter import org.rekotlin.Action sealed interface DaggerGraphAction : Action { @@ -9,5 +10,6 @@ sealed interface DaggerGraphAction : Action { data class SetActivityDependencies( val testerRouter: TesterRouter, val scanCardUseCase: ScanCardUseCase, + val walletRouter: WalletRouter, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 267665cce3..a2c7994ce1 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -15,6 +15,7 @@ object DaggerGraphReducer { is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( testerRouter = action.testerRouter, scanCardUseCase = action.scanCardUseCase, + walletRouter = action.walletRouter, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 5e823c02da..b59edc1928 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -5,6 +5,7 @@ import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -15,6 +16,7 @@ data class DaggerGraphState( val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, val scanCardUseCase: ScanCardUseCase? = null, val walletFeatureToggles: WalletFeatureToggles? = null, + val walletRouter: WalletRouter? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/features/wallet/api/build.gradle.kts b/features/wallet/api/build.gradle.kts index 1fbece85d5..3b5deb5bae 100644 --- a/features/wallet/api/build.gradle.kts +++ b/features/wallet/api/build.gradle.kts @@ -2,4 +2,9 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) id("configuration") +} + +dependencies { + /** AndroidX */ + implementation(deps.androidx.fragment.ktx) } \ No newline at end of file diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt new file mode 100644 index 0000000000..168bb9eca5 --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt @@ -0,0 +1,14 @@ +package com.tangem.features.wallet.navigation + +import androidx.fragment.app.Fragment + +/** + * Wallet feature router + * +[REDACTED_AUTHOR] + */ +interface WalletRouter { + + /** Get feature entry point [Fragment] */ + fun getEntryFragment(): Fragment +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt new file mode 100644 index 0000000000..d981abb47b --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.wallet.di + +import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter +import com.tangem.features.wallet.navigation.WalletRouter +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +internal object WalletRouterModule { + + @Provides + @ActivityScoped + fun provideWalletRouter(): WalletRouter = DefaultWalletRouter() +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 3c6f1114ad..1bc2cd04fd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -8,9 +8,11 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment -import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import com.tangem.features.wallet.navigation.WalletRouter import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Wallet fragment @@ -18,7 +20,16 @@ import dagger.hilt.android.AndroidEntryPoint [REDACTED_AUTHOR] */ @AndroidEntryPoint -class WalletFragment : Fragment() { +internal class WalletFragment : Fragment() { + + /** Feature router */ + @Inject + internal lateinit var walletRouter: WalletRouter + + private val _walletRouter: InnerWalletRouter + get() = requireNotNull(walletRouter as? InnerWalletRouter) { + "_walletRouter should be instance of InnerWalletRouter" + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } @@ -31,11 +42,14 @@ class WalletFragment : Fragment() { return ComposeView(inflater.context).apply { setContent { isTransitionGroup = true - - TangemTheme { - // TODO: [REDACTED_TASK_KEY] Call WalletScreen - } + _walletRouter.Initialize() } } } + + companion object { + + /** Create wallet fragment instance */ + fun create(): WalletFragment = WalletFragment() + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt new file mode 100644 index 0000000000..90a84bef1c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -0,0 +1,51 @@ +package com.tangem.feature.wallet.presentation.router + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.fragment.app.Fragment +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.presentation.WalletFragment +import com.tangem.feature.wallet.presentation.ui.WalletScreen +import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel +import kotlin.properties.Delegates + +/** Default implementation of wallet feature router */ +internal class DefaultWalletRouter : InnerWalletRouter { + + private var navController: NavHostController by Delegates.notNull() + + override fun getEntryFragment(): Fragment = WalletFragment.create() + + @Composable + override fun Initialize() { + TangemTheme { + NavHost( + navController = rememberNavController().apply { navController = this }, + startDestination = WalletScreens.WALLET.name, + ) { + composable(WalletScreens.WALLET.name) { + val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + WalletScreen(state = viewModel.uiState) + } + + composable(WalletScreens.ORGANIZE_TOKENS.name) { + BackHandler(onBack = ::popBackStack) + // TODO: Add OrganizeTokensScreen + } + } + } + } + + override fun popBackStack() { + navController.popBackStack() + } + + override fun openOrganizeTokensScreen() { + navController.navigate(WalletScreens.ORGANIZE_TOKENS.name) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt new file mode 100644 index 0000000000..00782aa81d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.wallet.presentation.router + +import androidx.compose.runtime.Composable +import com.tangem.features.wallet.navigation.WalletRouter + +/** + * Interface of inner wallet feature router + * +[REDACTED_AUTHOR] + */ +internal interface InnerWalletRouter : WalletRouter { + + /** Initialize router */ + @Suppress("TopLevelComposableFunctions") + @Composable + fun Initialize() + + /** Pop back stack */ + fun popBackStack() + + /** Open organize tokens screen */ + fun openOrganizeTokensScreen() +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletScreens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletScreens.kt new file mode 100644 index 0000000000..0a74b766aa --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletScreens.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.wallet.presentation.router + +/** + * Wallet feature screens + * +[REDACTED_AUTHOR] + */ +enum class WalletScreens { + WALLET, ORGANIZE_TOKENS +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt index 535b09f1b4..b716022f9b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.ui import com.tangem.core.ui.R import com.tangem.feature.wallet.presentation.state.WalletCardState +import com.tangem.feature.wallet.presentation.state.WalletStateHolder import java.util.UUID internal object WalletPreviewData { @@ -38,4 +39,13 @@ internal object WalletPreviewData { imageResId = R.drawable.ill_businessman_3d, onClick = {}, ) + + val walletScreenState = WalletStateHolder( + onBackClick = {}, + headerConfig = WalletStateHolder.HeaderConfig( + wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), + onScanCardClick = {}, + onMoreClick = {}, + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt index 0c2169bb16..acfa7b9a49 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt @@ -1,11 +1,17 @@ package com.tangem.feature.wallet.presentation.ui.components +import androidx.compose.animation.core.* +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource @@ -26,6 +32,7 @@ import com.tangem.feature.wallet.presentation.ui.WalletPreviewData * [REDACTED_AUTHOR] */ +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { Column( @@ -34,15 +41,23 @@ internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { .padding(bottom = TangemTheme.dimens.spacing14), ) { TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick) - LazyRow( - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - items(items = config.wallets, key = WalletCardState::id) { state -> - WalletCard( - state = state, - modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32), - ) + + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + val lazyListState = rememberLazyListState() + LazyRow( + state = lazyListState, + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), + ) { + items(items = config.wallets, key = WalletCardState::id) { state -> + WalletCard( + state = state, + modifier = Modifier.width( + LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32, + ), + ) + } } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt new file mode 100644 index 0000000000..c69e4f439b --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt @@ -0,0 +1,36 @@ +package com.tangem.feature.wallet.presentation.viewmodels + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import com.tangem.feature.wallet.presentation.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlin.properties.Delegates + +/** + * Wallet screen view model + * +[REDACTED_AUTHOR] + */ +@HiltViewModel +internal class WalletViewModel @Inject constructor() : ViewModel() { + + /** Feature router */ + var router: InnerWalletRouter by Delegates.notNull() + + /** Screen state */ + var uiState by mutableStateOf(getInitialState()) + private set + + // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData + private fun getInitialState(): WalletStateHolder = WalletPreviewData.walletScreenState.copy( + onBackClick = { router.popBackStack() }, + headerConfig = WalletPreviewData.walletScreenState.headerConfig.copy( + onScanCardClick = { router.openOrganizeTokensScreen() }, + ), + ) +} \ No newline at end of file From 99b4c180cc9dec1ddb579d5b736e1ab707c2af66 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Jun 2023 14:26:31 +0300 Subject: [PATCH 04/14] Updated on 2026-08-14 --- build.gradle.kts | 25 +++++++++++++++++++ gradle/dependencies.toml | 12 ++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- .../configuration/ConfigurationPlugin.kt | 8 +++--- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 91904f4d34..8ceb8869c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,4 +73,29 @@ val assembleQA by tasks.registering { dependsOn(assembleInternalQA) dependsOn(assembleExternalQA) +} + +val generateComposeMetrics by tasks.registering { + group = "other" + description = "Build external APK and generates compose metrics to 'build/compose-metrics' directory" + + subprojects { + tasks.withType { + compilerOptions { + val outputDirectory = "${project.buildDir.absolutePath}/compose_metrics" + // Metrics + freeCompilerArgs.addAll( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$outputDirectory", + ) + // Reports + freeCompilerArgs.addAll( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$outputDirectory", + ) + } + } + } + + finalizedBy(assembleExternalQA) } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index f7f6cb31df..5ad6f0a810 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -6,7 +6,7 @@ androidGradlePlugin = "7.4.2" firebaseCrashlytics = "2.9.4" googleServices = "4.3.10" -kotlin = "1.8.10" +kotlin = "1.8.21" # endregion Classpath # region AndroidX @@ -21,14 +21,14 @@ androidx-paging = "3.1.1" # endregion AndroidX # region Compose -compose-compiler = "1.4.3" -compose-runtime = "1.3.3" -compose-foundation = "1.3.1" -compose-material = "1.4.2" +compose-compiler = "1.4.7" +compose-runtime = "1.4.3" +compose-foundation = "1.4.3" +compose-material = "1.4.3" compose-material3 = "1.1.0" compose-constraint = "1.0.1" compose-navigation = "2.5.3" -compose-accompanist = "0.28.0" +compose-accompanist = "0.30.1" compose-paging = "1.0.0-alpha18" # endregion Compose diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ddde06c17d..135c32a8de 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Mar 07 14:45:01 MSK 2023 distributionBase = GRADLE_USER_HOME -distributionUrl = https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl = https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip distributionPath = wrapper/dists zipStorePath = wrapper/dists zipStoreBase = GRADLE_USER_HOME diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/ConfigurationPlugin.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/ConfigurationPlugin.kt index 53b9d53e1c..26359f8f2d 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/ConfigurationPlugin.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/ConfigurationPlugin.kt @@ -12,12 +12,12 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.configure class ConfigurationPlugin : Plugin { - override fun apply(target: Project) { - target.plugins.all(startConfigurationAction(project = target)) + override fun apply(project: Project) { + project.configure() + project.plugins.all(startPluginConfigurationAction(project)) } - private fun startConfigurationAction(project: Project) = Action> { - project.configure() + private fun startPluginConfigurationAction(project: Project) = Action> { when (this) { is AppPlugin -> { project.configure { configure(project) } From 1a4e8d8f20452926dc2c91263066906ee46b0fa1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Jun 2023 08:53:24 +0300 Subject: [PATCH 05/14] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/TapErrors.kt | 9 +- .../tap/domain/model/WalletDataModel.kt | 1 + .../utils/WalletDataOperations.kt | 5 +- .../features/send/redux/SendScreenAction.kt | 2 +- .../send/redux/reducers/SendScreenReducer.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 2 +- .../middlewares/TradeCryptoMiddleware.kt | 10 +- .../middlewares/WalletDialogsMiddleware.kt | 2 + .../redux/middlewares/WalletMiddleware.kt | 201 ++++++++++-------- .../wallet/redux/models/WalletDialog.kt | 2 +- .../dialogs/AmountToSendBottomSheetDialog.kt | 8 +- 11 files changed, 142 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index b0ee231286..fc7bdedd39 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -22,6 +22,10 @@ sealed class TapError( object UnknownError : TapError(R.string.send_error_unknown) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) + data class UnsupportedState( + val stateError: String, + ) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError")) + object ScanCardError : TapError(R.string.scan_card_error) object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle) object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) @@ -38,11 +42,6 @@ sealed class TapError( data class DustAmount(override val args: List) : TapError(R.string.send_error_dust_amount_format) object DustChange : TapError(R.string.send_error_dust_change) - data class UnsupportedState( - val stateError: String, - val customMessage: String = "Unsupported state:", - ) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError")) - sealed class WalletManager { object CreationError : CustomError("Can't create wallet manager") class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) 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 fe751acee5..e6782972eb 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 @@ -79,6 +79,7 @@ data class WalletDataModel( data class Unreachable( override val errorMessage: String?, + override val amount: BigDecimal = BigDecimal.ZERO, ) : Status() { override val isErrorStatus: Boolean = true } 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 e342ff4e70..396329ad4f 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 @@ -170,7 +170,10 @@ internal fun List.updateWithMissedDerivation(): List.updateWithUnreachable(): List { return this.map { walletData -> walletData.copy( - status = WalletDataModel.Unreachable(errorMessage = null), + status = WalletDataModel.Unreachable( + errorMessage = null, + amount = walletData.status.amount, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 719cd930c3..5dee274734 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -26,9 +26,9 @@ interface SendScreenActionUi : SendScreenAction object ReleaseSendState : Action data class PrepareSendScreen( + val walletManager: WalletManager, val coinAmount: Amount?, val coinRate: BigDecimal?, - val walletManager: WalletManager?, val tokenAmount: Amount? = null, val tokenRate: BigDecimal? = null, ) : SendScreenAction diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 48eafac40f..4134e8c148 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -85,7 +85,7 @@ private class EmptyReducer : SendInternalReducer { private class PrepareSendScreenStatesReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { val prepareAction = action as PrepareSendScreen - val walletManager = action.walletManager!! + val walletManager = action.walletManager val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!! val decimals = amountToExtract.decimals val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain) 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 db13376244..8db98a4ee0 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 @@ -98,7 +98,7 @@ sealed class WalletAction : Action { val swapAllowed: Boolean, ) : DialogAction() - data class ChooseCurrency(val amounts: List?) : DialogAction() + data class ChooseCurrency(val amounts: List) : DialogAction() data class RussianCardholdersWarningDialog( val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() 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 6020f4c151..0c56d30f51 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import androidx.core.os.bundleOf +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType import com.tangem.common.extensions.guard @@ -129,13 +130,16 @@ class TradeCryptoMiddleware { val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) + val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard { + FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null")) + return + } - val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency) store.dispatchOnMain( PrepareSendScreen( - coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin), - coinRate = selectedWalletData.fiatRate, walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = selectedWalletData.fiatRate, ), ) store.dispatchOnMain( 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 c21a15ce98..b381ec0365 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 @@ -37,6 +37,8 @@ class WalletDialogsMiddleware { ) } is WalletAction.DialogAction.ChooseCurrency -> { + if (action.amounts.isEmpty()) return + store.dispatchDialogShow( WalletDialog.SelectAmountToSendDialog( amounts = action.amounts, 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 b71a2eccc9..c7bde94141 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 @@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.common.CompletionResult import com.tangem.common.doOnSuccess @@ -32,7 +33,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.ifActive -import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -147,26 +147,76 @@ class WalletMiddleware { store.dispatchOpenUrl(action.exploreUrl) } is WalletAction.Send -> { + val walletStore = walletState.getWalletStore(walletState.selectedCurrency) + val selectedWalletData = walletState.selectedWalletData + val walletManager = walletStore?.walletManager + + if (walletStore == null || walletManager == null || selectedWalletData == null) { + val error = TapError.UnsupportedState( + "WalletAction.Send: walletStore or selectedWalletData or walletManager is null", + ) + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return + } if (!networkConnectionManager.isOnline) { store.dispatchErrorNotification(TapError.NoInternetConnection) return } - val newAction = prepareSendAction(action.amount, store.state.walletState) - if (newAction is PrepareSendScreen && newAction.walletManager == null) { - store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) - FirebaseCrashlytics.getInstance().recordException( - IllegalStateException("PrepareSendScreen: walletManager is null"), - ) - store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) - } else { - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.state.walletState.selectedWalletData?.currency?.let { currency -> - Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency))) - } - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + + val currency = selectedWalletData.currency + val initSendStateAction = if (action.amount == null) { + val sendableAmounts = walletManager.wallet.getSendableAmounts() + if (sendableAmounts.isEmpty()) { + val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send") + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return } + + if (walletState.isMultiwalletAllowed) { + val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol } + if (amountToSend == null) { + val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null") + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return + } + + makeInitSendStateActionByCurrency( + currency = currency, + amount = amountToSend, + walletStore = walletStore, + walletManager = walletManager, + selectedWalletData = selectedWalletData, + ) + } else { + val isSingleAmount = sendableAmounts.size == 1 + if (isSingleAmount) { + makeInitSendStateActionByAmount( + amount = sendableAmounts.first(), + walletStore = walletStore, + walletManager = walletManager, + selectedWalletData = selectedWalletData, + ) + } else { + store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts)) + return + } + } + } else { + // action.amount received from the ChooseCurrency dialog + makeInitSendStateActionByAmount( + amount = action.amount, + walletManager = walletManager, + walletStore = walletStore, + selectedWalletData = selectedWalletData, + ) } + + Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency))) + store.dispatch(initSendStateAction) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) } is WalletAction.ShowSaveWalletIfNeeded -> { showSaveWalletIfNeeded() @@ -222,6 +272,59 @@ class WalletMiddleware { } } + private fun makeInitSendStateActionByAmount( + amount: Amount, + walletStore: WalletStoreModel, + walletManager: WalletManager, + selectedWalletData: WalletDataModel, + ): PrepareSendScreen = when (amount.type) { + AmountType.Coin -> + PrepareSendScreen( + walletManager = walletManager, + coinAmount = amount, + coinRate = selectedWalletData.fiatRate, + ) + is AmountType.Token -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = walletStore.blockchainWalletData.fiatRate, + tokenAmount = amount, + tokenRate = selectedWalletData.fiatRate, + ) + } + AmountType.Reserve -> { + val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent") + FirebaseCrashlytics.getInstance().recordException(exception) + throw exception + } + } + + private fun makeInitSendStateActionByCurrency( + currency: Currency, + amount: Amount, + walletStore: WalletStoreModel, + walletManager: WalletManager, + selectedWalletData: WalletDataModel, + ): PrepareSendScreen = when (currency) { + is Currency.Blockchain -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = amount, + coinRate = selectedWalletData.fiatRate, + ) + } + is Currency.Token -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = walletStore.blockchainWalletData.fiatRate, + tokenAmount = amount, + tokenRate = selectedWalletData.fiatRate, + ) + } + } + private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) { val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard { Timber.e("Unable to change selected wallet address, no user wallet selected") @@ -284,72 +387,4 @@ class WalletMiddleware { } } } - - private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { - val selectedWalletData = state?.selectedWalletData - val currency = selectedWalletData?.currency - val walletStore = state?.getWalletStore(currency) - - return if (amount != null) { - if (amount.type is AmountType.Token) { - prepareSendActionForToken(amount, selectedWalletData, walletStore) - } else { - PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager) - } - } else { - val amounts = walletStore?.walletManager?.wallet?.getSendableAmounts() - if (currency != null && state.isMultiwalletAllowed) { - when (currency) { - is Currency.Blockchain -> { - val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) - PrepareSendScreen( - coinAmount = amountToSend, - coinRate = selectedWalletData.fiatRate, - walletManager = walletStore.walletManager, - ) - } - is Currency.Token -> { - val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) - prepareSendActionForToken( - amount = amountToSend, - selectedWalletData = selectedWalletData, - walletStore = walletStore, - ) - } - } - } else { - val amountsSize = amounts?.size ?: 0 - if (amountsSize > 1) { - WalletAction.DialogAction.ChooseCurrency(amounts) - } else { - val amountToSend = amounts?.first() - PrepareSendScreen( - coinAmount = amountToSend, - coinRate = selectedWalletData?.fiatRate, - walletManager = walletStore?.walletManager, - ) - } - } - } - } - - private fun prepareSendActionForToken( - amount: Amount, - selectedWalletData: WalletDataModel?, - walletStore: WalletStoreModel?, - ): PrepareSendScreen { - val coinRate = walletStore?.blockchainWalletData?.fiatRate - val tokenRate = selectedWalletData?.fiatRate - val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin) - - return PrepareSendScreen( - coinAmount = coinAmount, - coinRate = coinRate, - walletManager = walletStore?.walletManager, - tokenAmount = amount, - tokenRate = tokenRate, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index c772684c48..88fbb86932 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -6,7 +6,7 @@ import com.tangem.tap.common.redux.StateDialog import com.tangem.wallet.R sealed interface WalletDialog : StateDialog { - data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog + data class SelectAmountToSendDialog(val amounts: List) : WalletDialog object SignedHashesMultiWalletDialog : WalletDialog data class ChooseTradeActionDialog( val buyAllowed: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt index 160d3fc40e..75cbe3010a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt @@ -5,11 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.appcompat.view.ContextThemeWrapper -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.* import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.blockchain.common.Amount import com.tangem.tap.common.extensions.toFormattedString @@ -55,7 +51,7 @@ class AmountToSendBottomSheetDialog( } } -class ChooseAmountAdapter : ListAdapter(DiffUtilCallback) { +private class ChooseAmountAdapter : ListAdapter(DiffUtilCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder { val binding = ItemWalletAmountToSendBinding.inflate( From 17f89eebd5cd6b54ff19b1c847665a563341ab38 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Jun 2023 14:09:21 +0300 Subject: [PATCH 06/14] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 1 + .../com/tangem/core/ui/components/Buttons.kt | 186 ++++++-- .../com/tangem/core/ui/components/Shimmers.kt | 4 +- .../main/res/drawable/ic_group_drop_24.xml | 10 + features/wallet/impl/build.gradle.kts | 1 + .../presentation/common/WalletPreviewData.kt | 161 +++++++ .../common/component/NetworkGroupItem.kt | 79 ++++ .../common/component/TokenItem.kt | 412 ++++++++++++++++++ .../common/state/TokenItemState.kt | 111 +++++ .../common/state/TokenListState.kt | 35 ++ .../router/DefaultWalletRouter.kt | 4 +- .../presentation/router/InnerWalletRouter.kt | 5 + .../wallet/presentation/state/TokenUIState.kt | 9 - .../presentation/state/TokenV2Config.kt | 39 -- .../feature/wallet/presentation/ui/Tokens.kt | 352 --------------- .../presentation/ui/WalletPreviewData.kt | 51 --- .../{ => wallet}/state/WalletCardState.kt | 2 +- .../{ => wallet}/state/WalletStateHolder.kt | 6 +- .../{ => wallet}/ui/WalletScreen.kt | 6 +- .../{ => wallet}/ui/components/WalletCard.kt | 10 +- .../ui/components/WalletHeader.kt | 11 +- .../viewmodels/WalletViewModel.kt | 6 +- 23 files changed, 1001 insertions(+), 501 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_group_drop_24.xml create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/state/WalletCardState.kt (97%) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/state/WalletStateHolder.kt (79%) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/ui/WalletScreen.kt (69%) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/ui/components/WalletCard.kt (95%) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/ui/components/WalletHeader.kt (91%) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/{ => wallet}/viewmodels/WalletViewModel.kt (83%) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index af94ad33ae..245601c136 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -471,6 +471,7 @@ Аккаунт не создан Эта карта не поддерживается Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение. + Сеть %s Адрес скопирован в буфер обмена Нет соединения с интернетом Получение diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 038f2a7fe9..3874bb14cb 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -463,6 +463,7 @@ Account is not created This card is not supported Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app + %s network Address was copied to clipboard No internet connection Receiving diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 50651d2e35..8e63ac74d1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,29 +1,9 @@ package com.tangem.core.ui.components import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.Button -import androidx.compose.material.ButtonColors -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.ButtonElevation -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.State -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -244,6 +224,78 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif } // endregion Other +// region Action + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4) + * */ +@Composable +fun RoundedActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.secondaryButtonColors, + size = TangemButtonSize.RoundedAction, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4) + * */ +@Composable +fun ActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.secondaryButtonColors, + size = TangemButtonSize.Action, + ) +} + +/** + * Same as [RoundedActionButton] but colored in primary background color + * */ +@Composable +fun BackgroundActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.backgroundButtonColors, + size = TangemButtonSize.RoundedAction, + ) +} +// endregion Action + // region Defaults @Suppress("LongParameterList") @Composable @@ -335,13 +387,12 @@ private fun ButtonContent( } } -sealed interface TangemButtonIcon { +@Immutable +private sealed interface TangemButtonIcon { val painter: Painter? - @Immutable data class Left(override val painter: Painter) : TangemButtonIcon - @Immutable data class Right(override val painter: Painter) : TangemButtonIcon object None : TangemButtonIcon { @@ -349,10 +400,12 @@ sealed interface TangemButtonIcon { } } -enum class TangemButtonSize { +private enum class TangemButtonSize { Default, Text, Selector, + Action, + RoundedAction, } @Composable @@ -360,6 +413,9 @@ private fun TangemButtonSize.toHeightDp(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.size48 TangemButtonSize.Text -> TangemTheme.dimens.size40 TangemButtonSize.Selector -> TangemTheme.dimens.size24 + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> TangemTheme.dimens.size36 } @Composable @@ -367,6 +423,8 @@ private fun TangemButtonSize.toShape(): Shape = when (this) { TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall + TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge } @Composable @@ -374,6 +432,9 @@ private fun TangemButtonSize.toIconPadding(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.spacing8 TangemButtonSize.Text -> TangemTheme.dimens.spacing8 TangemButtonSize.Selector -> 0.dp + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> TangemTheme.dimens.spacing8 } object TangemButtonsDefaults { @@ -423,6 +484,14 @@ object TangemButtonsDefaults { disabledBackgroundColor = Color.Transparent, disabledContentColor = TangemTheme.colors.text.disabled, ) + + val backgroundButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.background.primary, + contentColor = TangemTheme.colors.text.primary1, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) } @Immutable @@ -650,4 +719,67 @@ private fun TextButtonPreview_Dark() { TextButtonSample() } } + +@Composable +private fun ActionButtonSample(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + RoundedActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + ActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + BackgroundActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + RoundedActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + ActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + BackgroundActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ActionButtonPreview_Light() { + TangemTheme { + ActionButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ActionButtonPreview_Dark() { + TangemTheme(isDark = true) { + ActionButtonSample() + } +} // endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt index 3b6227abbc..e793e21eaf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt @@ -14,7 +14,7 @@ import com.valentinilk.shimmer.shimmer * Rectangle shimmer item with rounded shape from DS */ @Composable -fun ShimmerRectangle(modifier: Modifier = Modifier) { +fun RectangleShimmer(modifier: Modifier = Modifier) { Box( modifier = modifier .shimmer() @@ -51,7 +51,7 @@ private fun ShimmersPreview() { modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), ) { - ShimmerRectangle( + RectangleShimmer( modifier = Modifier.size( width = TangemTheme.dimens.size72, height = TangemTheme.dimens.size12, diff --git a/core/ui/src/main/res/drawable/ic_group_drop_24.xml b/core/ui/src/main/res/drawable/ic_group_drop_24.xml new file mode 100644 index 0000000000..6e8c84c533 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_group_drop_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c0a25c65df..183e43c174 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.shimmer) implementation(deps.compose.accompanist.systemUiController) + implementation(deps.kotlin.immutable.collections) /** DI */ implementation(deps.hilt.android) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt new file mode 100644 index 0000000000..619f57d8bb --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -0,0 +1,161 @@ +package com.tangem.feature.wallet.presentation.common + +import com.tangem.core.ui.R +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import kotlinx.collections.immutable.persistentListOf +import java.util.UUID + +internal object WalletPreviewData { + + val walletCardContent = WalletCardState.Content( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + balance = "8923,05 $", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardLoading = WalletCardState.Loading( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardHiddenContent = WalletCardState.HiddenContent( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardError = WalletCardState.Error( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletScreenState = WalletStateHolder( + onBackClick = {}, + headerConfig = WalletStateHolder.HeaderConfig( + wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), + onScanCardClick = {}, + onMoreClick = {}, + ), + ) + + val tokenItemVisibleState = TokenItemState.Content( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + amount = "5,412 MATIC", + hasPending = true, + tokenOptions = TokenOptionsState.Visible( + fiatAmount = "321 $", + priceChange = TokenOptionsState.PriceChange( + valuePercent = "2%", + type = TokenOptionsState.PriceChange.Type.UP, + ), + ), + ) + + val tokenItemHiddenState = TokenItemState.Content( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + amount = "5,412 MATIC", + hasPending = true, + tokenOptions = TokenOptionsState.Hidden( + priceChange = TokenOptionsState.PriceChange( + valuePercent = "2%", + type = TokenOptionsState.PriceChange.Type.UP, + ), + ), + ) + + val tokenItemDragState = TokenItemState.Draggable( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + fiatAmount = "3 172,14 $", + ) + + val tokenItemUnreachableState = TokenItemState.Unreachable( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + ) + + val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) + + private val draggableTokenList = persistentListOf( + tokenItemDragState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + fiatAmount = "3 172,14 $", + ), + tokenItemDragState.copy( + id = "token_2", + networkIconResId = R.drawable.img_eth_22, + fiatAmount = "803,65 $", + ), + tokenItemDragState.copy( + id = "token_3", + name = "USDT", + tokenIconResId = R.drawable.img_arbitrum_22, + networkIconResId = R.drawable.img_eth_22, + fiatAmount = "88,01 $", + ), + ) + + val networkGroup = NetworkGroupState.Content( + id = UUID.randomUUID().toString(), + networkName = "Ethereum", + tokens = persistentListOf( + tokenItemVisibleState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + tokenItemVisibleState.copy( + id = "token_2", + networkIconResId = R.drawable.img_eth_22, + amount = "733,71097 MATIC", + ), + tokenItemVisibleState.copy( + id = "token_3", + name = "USDT", + tokenIconResId = R.drawable.img_arbitrum_22, + networkIconResId = R.drawable.img_eth_22, + amount = "0,25404523 ARB", + ), + ), + ) + + val draggableNetworkGroup = NetworkGroupState.Draggable( + id = UUID.randomUUID().toString(), + networkName = "Ethereum", + tokens = draggableTokenList, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt new file mode 100644 index 0000000000..ee2d924e85 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.wallet.presentation.common.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState + +@Composable +internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Row( + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .padding(horizontal = TangemTheme.dimens.spacing12) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size48), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = R.string.wallet_network_group_title, state.networkName), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + if (state is NetworkGroupState.Draggable) { + Icon( + painter = painterResource(id = R.drawable.ic_group_drop_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + } + Column { + state.tokens.forEach { token -> + key(token.id) { + TokenItem(state = token) + } + } + } + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { + TangemTheme { + NetworkGroupItem(group) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { + TangemTheme(isDark = true) { + NetworkGroupItem(group) + } +} + +private class NetworkGroupProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.networkGroup, + WalletPreviewData.draggableNetworkGroup, + ), +) +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt new file mode 100644 index 0000000000..78c5b5f322 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -0,0 +1,412 @@ +package com.tangem.feature.wallet.presentation.common.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstrainedLayoutReference +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.ConstraintLayoutScope +import androidx.constraintlayout.compose.Dimension +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemTypography +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState + +private const val DOTS = "•••" + +@Composable +internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) { + when (state) { + is TokenItemState.Content -> ContentTokenItem(state, modifier) + is TokenItemState.Loading -> LoadingTokenItem(modifier) + is TokenItemState.Draggable -> DraggableTokenItem(state, modifier) + is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier) + } +} + +@Composable +private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = content.name, + tokenIconUrl = content.tokenIconUrl, + tokenIconResId = content.tokenIconResId, + networkIconResId = content.networkIconResId, + amount = if (content.tokenOptions is TokenOptionsState.Hidden) DOTS else content.amount, + hasPending = content.hasPending, + options = { ref -> + TokenOptionsBlock( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + state = content.tokenOptions, + ) + }, + ) +} + +@Composable +internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = state.name, + tokenIconUrl = state.tokenIconUrl, + tokenIconResId = state.tokenIconResId, + networkIconResId = state.networkIconResId, + amount = state.fiatAmount, + hasPending = false, + options = { ref -> + Icon( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + painter = painterResource(id = R.drawable.ic_drag_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + }, + ) +} + +@Composable +internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = state.name, + tokenIconUrl = state.tokenIconUrl, + tokenIconResId = state.tokenIconResId, + networkIconResId = state.networkIconResId, + amount = null, + hasPending = false, + options = { ref -> + Text( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + text = "Unreachable", // TODO (conform this text) + style = TangemTypography.body2, + color = TangemTheme.colors.text.tertiary, + ) + }, + ) +} + +@Composable +private fun LoadingTokenItem(modifier: Modifier = Modifier) { + BaseSurface(modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing4, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size72, + height = TangemTheme.dimens.size12, + ), + ) + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size50, + height = TangemTheme.dimens.size12, + ), + ) + } + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size40, + height = TangemTheme.dimens.size12, + ), + ) + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size40, + height = TangemTheme.dimens.size12, + ), + ) + } + } + } + } +} + +/** + * Block for end part of token item + * shows status is reachable, is drag, hidden or show balance + */ +@Composable +private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) { + when (state) { + is TokenOptionsState.Visible -> { + TokenFiatPercentageBlock( + modifier = modifier, + fiatAmount = state.fiatAmount, + priceChange = state.priceChange, + ) + } + is TokenOptionsState.Hidden -> { + TokenFiatPercentageBlock( + modifier = modifier, + fiatAmount = DOTS, + priceChange = state.priceChange, + ) + } + } +} + +@Suppress("LongParameterList") +@Composable +private fun InternalTokenItem( + name: String, + tokenIconUrl: String?, + @DrawableRes tokenIconResId: Int, + @DrawableRes networkIconResId: Int?, + amount: String?, + hasPending: Boolean, + options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit, + modifier: Modifier = Modifier, +) { + BaseSurface(modifier) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing4, + ), + ) { + val (iconItem, tokenNameItem, optionsItem) = createRefs() + + TokenIcon( + modifier = Modifier.constrainAs(iconItem) { + centerVerticallyTo(parent) + start.linkTo(parent.start) + }, + tokenIconUrl = tokenIconUrl, + tokenIconResId = tokenIconResId, + networkIconRes = networkIconResId, + ) + + TokenTitleAmountBlock( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing12) + .constrainAs(tokenNameItem) { + centerVerticallyTo(parent) + start.linkTo(iconItem.end) + end.linkTo(optionsItem.start) + width = Dimension.fillToConstraints + }, + title = name, + amount = amount, + hasPending = hasPending, + ) + + options(optionsItem) + } + } +} + +@Stable +private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: ConstrainedLayoutReference): Modifier { + return with(scope) { + this@constrainAsOptionsItem.constrainAs(ref) { + centerVerticallyTo(parent) + end.linkTo(parent.end) + } + } +} + +@Composable +private fun BaseSurface( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Surface( + modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), + color = TangemTheme.colors.background.primary, + onClick = onClick ?: {}, + enabled = onClick != null, + ) { + content() + } +} + +@Composable +private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Boolean, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + Text( + text = title, + style = TangemTypography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + if (hasPending) { + Image( + modifier = Modifier.align(Alignment.CenterVertically), + painter = painterResource(id = R.drawable.img_loader_15), + contentDescription = null, + ) + } + } + if (!amount.isNullOrBlank()) { + Text( + text = amount, + style = TangemTypography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } + } +} + +@Composable +private fun TokenFiatPercentageBlock( + fiatAmount: String, + priceChange: TokenOptionsState.PriceChange, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) { + Text( + modifier = Modifier.align(Alignment.End), + text = fiatAmount, + style = TangemTypography.body2, + color = TangemTheme.colors.text.primary1, + ) + SpacerH2() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + val iconChangeArrow: Int + val changeTextColor: Color + when (priceChange.type) { + TokenOptionsState.PriceChange.Type.UP -> { + iconChangeArrow = R.drawable.img_arrow_up_8 + changeTextColor = TangemTheme.colors.text.accent + } + TokenOptionsState.PriceChange.Type.DOWN -> { + iconChangeArrow = R.drawable.img_arrow_down_8 + changeTextColor = TangemTheme.colors.text.warning + } + } + Image( + modifier = Modifier.align(Alignment.CenterVertically), + painter = painterResource(id = iconChangeArrow), + contentDescription = null, + ) + SpacerW4() + Text( + modifier = Modifier.align(Alignment.CenterVertically), + text = priceChange.valuePercent, + style = TangemTypography.body2, + color = changeTextColor, + ) + } + } +} + +@Composable +private fun TokenIcon( + tokenIconUrl: String?, + modifier: Modifier = Modifier, + @DrawableRes tokenIconResId: Int? = null, + @DrawableRes networkIconRes: Int? = null, +) { + Box( + modifier = modifier + .padding(end = TangemTheme.dimens.spacing16) + .size(TangemTheme.dimens.size42), + ) { + val tokenImageModifier = Modifier + .align(Alignment.BottomStart) + .size(TangemTheme.dimens.size36) + + val data = if (tokenIconUrl.isNullOrEmpty()) tokenIconResId else tokenIconUrl + SubcomposeAsyncImage( + modifier = tokenImageModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(data) + .crossfade(true) + .build(), + loading = { CircleShimmer(modifier = tokenImageModifier) }, + contentDescription = null, + ) + + if (networkIconRes != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background(color = Color.White, shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + Image( + modifier = Modifier.padding(all = 0.5.dp), + painter = painterResource(id = networkIconRes), + contentDescription = null, + ) + } + } + } +} + +// region preview + +@Preview +@Composable +private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { + TangemTheme(isDark = false) { + TokenItem(state) + } +} + +@Preview +@Composable +private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { + TangemTheme(isDark = true) { + TokenItem(state) + } +} + +private class TokenConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.tokenItemVisibleState, + WalletPreviewData.tokenItemUnreachableState, + WalletPreviewData.tokenItemDragState, + WalletPreviewData.tokenItemHiddenState, + WalletPreviewData.loadingTokenItemState, + ), +) + +// endregion preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt new file mode 100644 index 0000000000..706387e9d6 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -0,0 +1,111 @@ +package com.tangem.feature.wallet.presentation.common.state + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState.PriceChange.Type + +/** Token item state */ +@Immutable +internal sealed interface TokenItemState { + + /** Token id */ + val id: String + + /** + * Loading token state + * + * @property id token id + */ + data class Loading(override val id: String) : TokenItemState + + /** + * Content token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + * @property amount amount of token + * @property hasPending pending tx in blockchain + * @property tokenOptions state for token options + */ + data class Content( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + val amount: String, + val hasPending: Boolean, + val tokenOptions: TokenOptionsState, + ) : TokenItemState + + /** + * Draggable token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + */ + data class Draggable( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + val fiatAmount: String, + ) : TokenItemState + + /** + * Unreachable token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + */ + data class Unreachable( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + ) : TokenItemState + + /** Token options state */ + sealed interface TokenOptionsState { + + /** + * Visible token options state + * + * @property fiatAmount fiat amount of token + * @property priceChange value of price changing + */ + data class Visible(val fiatAmount: String, val priceChange: PriceChange) : TokenOptionsState + + /** + * Hidden token options state + * + * @property priceChange value of price changing + */ + data class Hidden(val priceChange: PriceChange) : TokenOptionsState + + /** + * Price changing option state + * + * @property valuePercent value in percent + * @property type type [Type] + */ + data class PriceChange(val valuePercent: String, val type: Type) { + + /** Price changing type */ + enum class Type { + UP, DOWN + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt new file mode 100644 index 0000000000..c504d1c81e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.wallet.presentation.common.state + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal sealed interface TokenListState { + + data class GroupedByNetwork( + val groups: ImmutableList, + ) : TokenListState + + data class Ungrouped( + val tokens: ImmutableList, + ) : TokenListState +} + +@Immutable +internal sealed interface NetworkGroupState { + val id: String + val networkName: String + val tokens: ImmutableList + + data class Draggable( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState + + data class Content( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 90a84bef1c..74751d0c6b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -10,8 +10,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.WalletFragment -import com.tangem.feature.wallet.presentation.ui.WalletScreen -import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel +import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel import kotlin.properties.Delegates /** Default implementation of wallet feature router */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 00782aa81d..7558ac61c7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -1,13 +1,18 @@ package com.tangem.feature.wallet.presentation.router import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import com.tangem.features.wallet.navigation.WalletRouter /** * Interface of inner wallet feature router * + * Annotated as [Stable] because implementation of this interface passed as argument to [Initialize] method by compose + * compiler + * [REDACTED_AUTHOR] */ +@Stable internal interface InnerWalletRouter : WalletRouter { /** Initialize router */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt deleted file mode 100644 index ace321b30a..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.wallet.presentation.state - -internal enum class TokenUIState { - LOADED, LOADING -} - -internal enum class TokenOptionsUIState { - VISIBLE, HIDDEN, DRAG, UNREACHABLE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt deleted file mode 100644 index dca7440c39..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.feature.wallet.presentation.state - -import androidx.annotation.DrawableRes - -/** - * Token config - * - * @property name of token/coin - * @property amount of token - * @property fiatAmount amount in fiat - * @property priceChange value of price changing - * @property iconUrl - * @property icon token/coin icon - * @property networkIcon - * @property tokenUIState token state [TokenUIState] loading etc. - * @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc - * @property hasPending pending tx in blockchain - */ -internal data class TokenV2Config( - val name: String, - val amount: String, - val fiatAmount: String, - val priceChange: PriceChange, - val iconUrl: String?, - @DrawableRes val icon: Int?, - @DrawableRes val networkIcon: Int, - val tokenUIState: TokenUIState, - val tokenOptionsUIState: TokenOptionsUIState, - val hasPending: Boolean, -) - -data class PriceChange( - val valuePercent: String, - val type: PriceChangeType, -) - -enum class PriceChangeType { - UP, DOWN -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt deleted file mode 100644 index 45b3eb843d..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt +++ /dev/null @@ -1,352 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import coil.compose.SubcomposeAsyncImage -import coil.request.ImageRequest -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemTypography -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.* - -private const val DOTS = "•••" - -@Composable -internal fun TokenItemV2(config: TokenV2Config) { - when (config.tokenUIState) { - TokenUIState.LOADED -> LoadedTokenState(config) - TokenUIState.LOADING -> LoadingTokenState() - } -} - -@Composable -private fun LoadedTokenState(config: TokenV2Config) { - BaseSurface { - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = TangemTheme.dimens.spacing12, - vertical = TangemTheme.dimens.spacing4, - ), - ) { - val (iconItem, tokenNameItem, fiatItem) = createRefs() - TokenIcon( - modifier = Modifier.constrainAs(iconItem) { - centerVerticallyTo(parent) - start.linkTo(parent.start) - }, - tokenIconUrl = config.iconUrl, - icon = config.icon, - networkIconRes = config.networkIcon, - ) - TokenTitleAmountBlock( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing12) - .constrainAs(tokenNameItem) { - centerVerticallyTo(parent) - start.linkTo(iconItem.end) - end.linkTo(fiatItem.start) - width = Dimension.fillToConstraints - }, - title = config.name, - amount = config.amount, - hasPending = config.hasPending, - ) - TokenOptionsBlock( - config = config, - modifier = Modifier.constrainAs(fiatItem) { - centerVerticallyTo(parent) - end.linkTo(parent.end) - }, - ) - } - } -} - -/** - * Block for end part of token item - * shows status is reachable, is drag, hidden or show balance - */ -@Composable -private fun TokenOptionsBlock(config: TokenV2Config, modifier: Modifier = Modifier) { - when (config.tokenOptionsUIState) { - TokenOptionsUIState.VISIBLE -> TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = config.fiatAmount, - priceChange = config.priceChange, - ) - TokenOptionsUIState.UNREACHABLE -> Text( - modifier = modifier, - text = "Unreachable", // TODO (conform this text) - style = TangemTypography.body2, - color = TangemTheme.colors.text.tertiary, - ) - TokenOptionsUIState.HIDDEN -> TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = DOTS, - priceChange = config.priceChange, - ) - TokenOptionsUIState.DRAG -> Icon( - modifier = modifier, - painter = painterResource(id = R.drawable.ic_drag_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } -} - -@Composable -private fun LoadingTokenState() { - BaseSurface { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = TangemTheme.dimens.spacing12, - vertical = TangemTheme.dimens.spacing4, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) - SpacerW12() - Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size72, - height = TangemTheme.dimens.size12, - ), - ) - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size50, - height = TangemTheme.dimens.size12, - ), - ) - } - } - Column( - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size40, - height = TangemTheme.dimens.size12, - ), - ) - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size40, - height = TangemTheme.dimens.size12, - ), - ) - } - } -} - -@Composable -private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { - Surface( - modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), - color = TangemTheme.colors.background.primary, - onClick = onClick ?: {}, - enabled = onClick != null, - ) { - content() - } -} - -@Composable -private fun TokenTitleAmountBlock(title: String, amount: String, hasPending: Boolean, modifier: Modifier = Modifier) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), - ) { - Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { - Text( - text = title, - style = TangemTypography.subtitle2, - color = TangemTheme.colors.text.primary1, - ) - if (hasPending) { - Image( - modifier = Modifier.align(Alignment.CenterVertically), - painter = painterResource(id = R.drawable.img_loader_15), - contentDescription = null, - ) - } - } - Text( - text = amount, - style = TangemTypography.body2, - color = TangemTheme.colors.text.tertiary, - ) - } -} - -@Composable -private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChange, modifier: Modifier = Modifier) { - Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) { - Text( - modifier = Modifier.align(Alignment.End), - text = fiatAmount, - style = TangemTypography.body2, - color = TangemTheme.colors.text.primary1, - ) - SpacerH2() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - val iconChangeArrow: Int - val changeTextColor: Color - when (priceChange.type) { - PriceChangeType.UP -> { - iconChangeArrow = R.drawable.img_arrow_up_8 - changeTextColor = TangemTheme.colors.text.accent - } - PriceChangeType.DOWN -> { - iconChangeArrow = R.drawable.img_arrow_down_8 - changeTextColor = TangemTheme.colors.text.warning - } - } - Image( - modifier = Modifier.align(Alignment.CenterVertically), - painter = painterResource(id = iconChangeArrow), - contentDescription = null, - ) - SpacerW4() - Text( - modifier = Modifier.align(Alignment.CenterVertically), - text = priceChange.valuePercent, - style = TangemTypography.body2, - color = changeTextColor, - ) - } - } -} - -@Composable -private fun TokenIcon( - tokenIconUrl: String?, - modifier: Modifier = Modifier, - @DrawableRes icon: Int? = null, - @DrawableRes networkIconRes: Int? = null, -) { - Box( - modifier = modifier - .padding(end = TangemTheme.dimens.spacing16) - .size(TangemTheme.dimens.size42), - ) { - val tokenImageModifier = Modifier - .align(Alignment.BottomStart) - .size(TangemTheme.dimens.size36) - - val data = if (tokenIconUrl.isNullOrEmpty()) { - icon - } else { - tokenIconUrl - } - SubcomposeAsyncImage( - modifier = tokenImageModifier, - model = ImageRequest.Builder(LocalContext.current) - .data(data) - .crossfade(true) - .build(), - loading = { CircleShimmer(modifier = tokenImageModifier) }, - contentDescription = null, - ) - - if (networkIconRes != null) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background(color = Color.White, shape = CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - modifier = Modifier.padding(all = 0.5.dp), - painter = painterResource(id = networkIconRes), - contentDescription = null, - ) - } - } - } -} - -// region preview - -@Composable -private fun TokensPreview() { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), - ) { - val config = TokenV2Config( - name = "Polygon", - amount = "5,412 MATIC", - fiatAmount = "321 $", - priceChange = PriceChange( - valuePercent = "2%", - type = PriceChangeType.UP, - ), - iconUrl = null, - icon = R.drawable.img_polygon_22, - networkIcon = R.drawable.img_polygon_22, - tokenUIState = TokenUIState.LOADED, - tokenOptionsUIState = TokenOptionsUIState.VISIBLE, - hasPending = true, - ) - // Loaded item - TokenItemV2(config) - // Unreachable item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE)) - // Drag item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.DRAG)) - // Hidden item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE)) - // Loading item - TokenItemV2( - config.copy( - tokenUIState = TokenUIState.LOADING, - ), - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Tokens_InLightTheme() { - TangemTheme(isDark = false) { - TokensPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Tokens_InDarkTheme() { - TangemTheme(isDark = true) { - TokensPreview() - } -} - -// endregion preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt deleted file mode 100644 index b716022f9b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import com.tangem.core.ui.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import java.util.UUID - -internal object WalletPreviewData { - - val walletCardContent = WalletCardState.Content( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - balance = "8923,05 $", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardLoading = WalletCardState.Loading( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardHiddenContent = WalletCardState.HiddenContent( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardError = WalletCardState.Error( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletScreenState = WalletStateHolder( - onBackClick = {}, - headerConfig = WalletStateHolder.HeaderConfig( - wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), - onScanCardClick = {}, - onMoreClick = {}, - ), - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt similarity index 97% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt index 0837a3ef7d..7685a267da 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.state +package com.tangem.feature.wallet.presentation.wallet.state import androidx.annotation.DrawableRes diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index c69ab425ae..8142667ad3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -1,4 +1,6 @@ -package com.tangem.feature.wallet.presentation.state +package com.tangem.feature.wallet.presentation.wallet.state + +import kotlinx.collections.immutable.ImmutableList /** * Wallet state holder @@ -21,7 +23,7 @@ internal data class WalletStateHolder( * @property onMoreClick lambda be invoked when more button is clicked */ data class HeaderConfig( - val wallets: List, + val wallets: ImmutableList, val onScanCardClick: () -> Unit, val onMoreClick: () -> Unit, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt similarity index 69% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index dc166a77a2..ba17e6e692 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -1,10 +1,10 @@ -package com.tangem.feature.wallet.presentation.ui +package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.components.WalletHeader +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader /** * Wallet screen diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt similarity index 95% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt index 25a427408a..562c954023 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.ui.components +package com.tangem.feature.wallet.presentation.wallet.ui.components import androidx.annotation.DrawableRes import androidx.compose.foundation.Image @@ -17,12 +17,12 @@ import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import com.tangem.core.ui.components.FontSizeRange +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.ResizableText -import com.tangem.core.ui.components.ShimmerRectangle import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState private const val DOTS = "•••" @@ -119,7 +119,7 @@ private fun Balance(state: WalletCardState) { ) } is WalletCardState.Loading -> { - ShimmerRectangle( + RectangleShimmer( modifier = Modifier.size( width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt similarity index 91% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt index acfa7b9a49..cb01b48377 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.ui.components +package com.tangem.feature.wallet.presentation.wallet.ui.components import androidx.compose.animation.core.* import androidx.compose.foundation.ExperimentalFoundationApi @@ -21,9 +21,10 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import kotlinx.collections.immutable.persistentListOf /** * Wallet screen header @@ -109,7 +110,7 @@ private fun Preview_WalletHeader_DarkTheme( private class WalletHeaderProvider : CollectionPreviewParameterProvider( collection = listOf( WalletStateHolder.HeaderConfig( - wallets = listOf( + wallets = persistentListOf( WalletPreviewData.walletCardContent, WalletPreviewData.walletCardLoading, WalletPreviewData.walletCardHiddenContent, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt similarity index 83% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index c69e4f439b..a565bb8c91 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -1,12 +1,12 @@ -package com.tangem.feature.wallet.presentation.viewmodels +package com.tangem.feature.wallet.presentation.wallet.viewmodels import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlin.properties.Delegates From 57e00dae8c3aebf8d246186b0030c8d7233e7b67 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Jun 2023 17:16:59 +0300 Subject: [PATCH 07/14] Updated on 2026-08-14 --- .../AddCustomTokenFloatingButton.kt | 7 +- .../legacy/compose/AddCustomTokenScreen.kt | 41 +--- .../ui/common/DetailsComposeElements.kt | 24 +- .../WallectSelectorScreenContent.kt | 15 +- .../ui/components/WelcomeScreenContent.kt | 17 +- .../com/tangem/core/ui/components/Buttons.kt | 205 ++++++++++++------ .../core/ui/components/SuccessScreen.kt | 14 +- .../wallet2/ui/CheckSeedPhraseScreen.kt | 7 +- .../wallet2/ui/ImportSeedPhraseScreen.kt | 7 +- .../presentation/wallet2/ui/IntroScreen.kt | 6 +- .../ui/SwapPermissionBottomSheetContent.kt | 4 +- .../feature/swap/ui/SwapScreenContent.kt | 4 +- 12 files changed, 178 insertions(+), 173 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt index 618a46fd70..5011ca010c 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt @@ -5,12 +5,11 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton import com.tangem.wallet.R @@ -25,13 +24,13 @@ import com.tangem.wallet.R */ @Composable internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = modifier .imePadding() .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_add), - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, enabled = model.isEnabled, onClick = model.onClick, ) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt index 0a4ad3b4a6..baa487cec7 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt @@ -1,48 +1,21 @@ package com.tangem.tap.features.customtoken.legacy.compose -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.BottomSheetScaffoldState -import androidx.compose.material.BottomSheetState -import androidx.compose.material.BottomSheetValue -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FabPosition -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.material.Surface -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.material.rememberScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.FieldId -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.features.addCustomToken.redux.ScreenState @@ -189,12 +162,12 @@ fun Warnings(warnings: List) { @Composable private fun AddButton(state: MutableState) { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_add), - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, enabled = state.value.screenState.addButton.isEnabled, onClick = { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 38fdbe45f6..c92047d0bc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -1,23 +1,9 @@ package com.tangem.tap.features.details.ui.common import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.selection.selectable -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.RadioButton -import androidx.compose.material.RadioButtonDefaults -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -25,7 +11,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -110,13 +96,13 @@ fun EmptyTopBarWithNavigation( @Composable fun DetailsMainButton(title: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) { - PrimaryButtonIconRight( + PrimaryButtonIconEnd( text = title, enabled = enabled, onClick = onClick, modifier = modifier .fillMaxWidth(), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, ) } 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 71d7f6a253..f0dd6b139b 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 @@ -3,14 +3,7 @@ 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.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.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.Icon @@ -28,7 +21,7 @@ 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.SecondaryButtonIconEnd import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme @@ -191,11 +184,11 @@ private fun Footer( onClick = onUnlockClick, ) } - SecondaryButtonIconRight( + SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.user_wallet_list_add_button), showProgress = showAddCardProgress, - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = onAddCardClick, ) } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index 373d370eda..cc83e0d893 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -1,11 +1,7 @@ package com.tangem.tap.features.welcome.ui.components import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -15,12 +11,7 @@ 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.PrimaryButtonIconRight -import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH32 -import com.tangem.core.ui.components.SpacerHMax +import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -76,13 +67,13 @@ internal fun WelcomeScreenContent( onClick = onUnlockClick, ) SpacerH12() - PrimaryButtonIconRight( + PrimaryButtonIconEnd( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(R.string.welcome_unlock_card), showProgress = showScanCardProgress, - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = onScanCardClick, ) SpacerH16() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 8e63ac74d1..00a6b2ac03 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -8,7 +9,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview @@ -39,9 +39,9 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier, * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A62&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun TextButtonIconLeft( +fun TextButtonIconStart( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -49,7 +49,7 @@ fun TextButtonIconLeft( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, enabled = enabled, showProgress = false, @@ -97,9 +97,9 @@ fun PrimaryButton( * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun PrimaryButtonIconRight( +fun PrimaryButtonIconEnd( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, showProgress: Boolean = false, @@ -108,7 +108,7 @@ fun PrimaryButtonIconRight( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Right(icon), + icon = TangemButtonIcon.End(iconResId), onClick = onClick, colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, @@ -120,9 +120,9 @@ fun PrimaryButtonIconRight( * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun PrimaryButtonIconLeft( +fun PrimaryButtonIconStart( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, showProgress: Boolean = false, @@ -131,7 +131,7 @@ fun PrimaryButtonIconLeft( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, @@ -164,9 +164,9 @@ fun SecondaryButton( * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=99%3A50&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun SecondaryButtonIconRight( +fun SecondaryButtonIconEnd( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, showProgress: Boolean = false, @@ -175,7 +175,7 @@ fun SecondaryButtonIconRight( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Right(icon), + icon = TangemButtonIcon.End(iconResId), onClick = onClick, colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, @@ -187,9 +187,9 @@ fun SecondaryButtonIconRight( * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A262&t=TmfD6UBHPg9uYfev-4) * */ @Composable -fun SecondaryButtonIconLeft( +fun SecondaryButtonIconStart( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, showProgress: Boolean = false, @@ -198,7 +198,7 @@ fun SecondaryButtonIconLeft( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, @@ -214,7 +214,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif modifier = modifier, text = text, textStyle = TangemTheme.typography.subtitle2, - icon = TangemButtonIcon.Right(painterResource(id = R.drawable.ic_chevron_24)), + icon = TangemButtonIcon.End(iconResId = R.drawable.ic_chevron_24), onClick = onClick, colors = TangemButtonsDefaults.selectorButtonColors, showProgress = false, @@ -232,7 +232,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif @Composable fun RoundedActionButton( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -240,7 +240,7 @@ fun RoundedActionButton( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, enabled = enabled, showProgress = false, @@ -255,7 +255,7 @@ fun RoundedActionButton( @Composable fun ActionButton( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -263,7 +263,7 @@ fun ActionButton( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, enabled = enabled, showProgress = false, @@ -278,7 +278,7 @@ fun ActionButton( @Composable fun BackgroundActionButton( text: String, - icon: Painter, + @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -286,7 +286,7 @@ fun BackgroundActionButton( TangemButton( modifier = modifier, text = text, - icon = TangemButtonIcon.Left(icon), + icon = TangemButtonIcon.Start(iconResId), onClick = onClick, enabled = enabled, showProgress = false, @@ -324,6 +324,7 @@ private fun TangemButton( elevation = elevation, shape = size.toShape(), colors = colors, + contentPadding = size.toContentPadding(icon = icon), ) { ButtonContent( text = text, @@ -348,10 +349,10 @@ private fun ButtonContent( enabled: Boolean, showProgress: Boolean, ) { - val icon = @Composable { painter: Painter -> + val icon = @Composable { iconResId: Int -> Icon( modifier = Modifier.size(TangemTheme.dimens.size20), - painter = painter, + painter = painterResource(id = iconResId), tint = colors.contentColor(enabled = enabled).value, contentDescription = null, ) @@ -372,16 +373,17 @@ private fun ButtonContent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()), ) { - if (buttonIcon is TangemButtonIcon.Left) { - icon(buttonIcon.painter) + if (buttonIcon is TangemButtonIcon.Start) { + icon(buttonIcon.iconResId) } Text( text = text, style = textStyle, color = colors.contentColor(enabled = enabled).value, + maxLines = 1, ) - if (buttonIcon is TangemButtonIcon.Right) { - icon(buttonIcon.painter) + if (buttonIcon is TangemButtonIcon.End) { + icon(buttonIcon.iconResId) } } } @@ -389,14 +391,14 @@ private fun ButtonContent( @Immutable private sealed interface TangemButtonIcon { - val painter: Painter? + val iconResId: Int? - data class Left(override val painter: Painter) : TangemButtonIcon + data class Start(override val iconResId: Int) : TangemButtonIcon - data class Right(override val painter: Painter) : TangemButtonIcon + data class End(override val iconResId: Int) : TangemButtonIcon object None : TangemButtonIcon { - override val painter: Painter? = null + override val iconResId: Int? = null } } @@ -409,6 +411,7 @@ private enum class TangemButtonSize { } @Composable +@ReadOnlyComposable private fun TangemButtonSize.toHeightDp(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.size48 TangemButtonSize.Text -> TangemTheme.dimens.size40 @@ -419,6 +422,7 @@ private fun TangemButtonSize.toHeightDp(): Dp = when (this) { } @Composable +@ReadOnlyComposable private fun TangemButtonSize.toShape(): Shape = when (this) { TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall @@ -428,6 +432,7 @@ private fun TangemButtonSize.toShape(): Shape = when (this) { } @Composable +@ReadOnlyComposable private fun TangemButtonSize.toIconPadding(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.spacing8 TangemButtonSize.Text -> TangemTheme.dimens.spacing8 @@ -437,7 +442,63 @@ private fun TangemButtonSize.toIconPadding(): Dp = when (this) { -> TangemTheme.dimens.spacing8 } -object TangemButtonsDefaults { +@Composable +@ReadOnlyComposable +private fun TangemButtonSize.toContentPadding(icon: TangemButtonIcon): PaddingValues { + val horizontalPadding = this.toHorizontalContentPadding(icon = icon) + + return when (this) { + TangemButtonSize.Default -> PaddingValues( + top = TangemTheme.dimens.spacing14, + bottom = TangemTheme.dimens.spacing14, + start = horizontalPadding.first, + end = horizontalPadding.second, + ) + TangemButtonSize.Text -> PaddingValues( + top = TangemTheme.dimens.spacing10, + bottom = TangemTheme.dimens.spacing10, + start = horizontalPadding.first, + end = horizontalPadding.second, + ) + TangemButtonSize.Selector -> PaddingValues( + top = TangemTheme.dimens.spacing0_5, + bottom = TangemTheme.dimens.spacing0_5, + start = horizontalPadding.first, + end = horizontalPadding.second, + ) + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> PaddingValues( + top = TangemTheme.dimens.spacing8, + bottom = TangemTheme.dimens.spacing8, + start = horizontalPadding.first, + end = horizontalPadding.second, + ) + } +} + +@Composable +@ReadOnlyComposable +private fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIcon): Pair { + return when (this) { + TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32 + TangemButtonSize.Text -> when (icon) { + is TangemButtonIcon.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16 + is TangemButtonIcon.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16 + is TangemButtonIcon.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14 + } + TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5 + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> when (icon) { + is TangemButtonIcon.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24 + is TangemButtonIcon.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24 + is TangemButtonIcon.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16 + } + } +} + +private object TangemButtonsDefaults { val elevation: ButtonElevation @Composable get() = ButtonDefaults .elevation( @@ -446,7 +507,9 @@ object TangemButtonsDefaults { ) val primaryButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = TangemTheme.colors.button.primary, contentColor = TangemTheme.colors.text.primary2, disabledBackgroundColor = TangemTheme.colors.button.disabled, @@ -454,7 +517,9 @@ object TangemButtonsDefaults { ) val secondaryButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = TangemTheme.colors.button.secondary, contentColor = TangemTheme.colors.text.primary1, disabledBackgroundColor = TangemTheme.colors.button.disabled, @@ -462,7 +527,9 @@ object TangemButtonsDefaults { ) val defaultTextButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = Color.Transparent, contentColor = TangemTheme.colors.text.secondary, disabledBackgroundColor = Color.Transparent, @@ -470,7 +537,9 @@ object TangemButtonsDefaults { ) val warningTextButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = Color.Transparent, contentColor = TangemTheme.colors.text.warning, disabledBackgroundColor = Color.Transparent, @@ -478,7 +547,9 @@ object TangemButtonsDefaults { ) val selectorButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = Color.Transparent, contentColor = TangemTheme.colors.text.tertiary, disabledBackgroundColor = Color.Transparent, @@ -486,7 +557,9 @@ object TangemButtonsDefaults { ) val backgroundButtonColors: ButtonColors - @Composable get() = TangemButtonColors( + @Composable + @ReadOnlyComposable + get() = TangemButtonColors( backgroundColor = TangemTheme.colors.background.primary, contentColor = TangemTheme.colors.text.primary1, disabledBackgroundColor = TangemTheme.colors.button.disabled, @@ -533,17 +606,17 @@ private fun PrimaryButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - PrimaryButtonIconRight( + PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -554,18 +627,18 @@ private fun PrimaryButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - PrimaryButtonIconRight( + PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = false, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = false, onClick = { /* no-op */ }, ) @@ -607,17 +680,17 @@ private fun SecondaryButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SecondaryButtonIconRight( + SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SecondaryButtonIconLeft( + SecondaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -628,18 +701,18 @@ private fun SecondaryButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SecondaryButtonIconRight( + SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = false, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SecondaryButtonIconLeft( + SecondaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = false, onClick = { /* no-op */ }, ) @@ -673,9 +746,9 @@ private fun TextButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - TextButtonIconLeft( + TextButtonIconStart( text = "Enabled", - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -685,9 +758,9 @@ private fun TextButtonSample(modifier: Modifier = Modifier) { onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - TextButtonIconLeft( + TextButtonIconStart( text = "Enabled", - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, enabled = false, onClick = { /* no-op */ }, ) @@ -728,39 +801,39 @@ private fun ActionButtonSample(modifier: Modifier = Modifier) { ) { RoundedActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) ActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) BackgroundActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) RoundedActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) ActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) BackgroundActionButton( text = "Send", - icon = painterResource(id = R.drawable.ic_arrow_up_24), + iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { /* [REDACTED_TODO_COMMENT]*/ }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt index ebab2d5145..c5c3feabc1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt @@ -3,15 +3,7 @@ package com.tangem.core.ui.components import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -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.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll @@ -143,9 +135,9 @@ private fun SecondaryButtonForResultScreen( @DrawableRes secondaryButtonIcon: Int? = null, ) { if (secondaryButtonIcon != null) { - SecondaryButtonIconLeft( + SecondaryButtonIconStart( text = stringResource(id = secondaryButtonText), - icon = painterResource(id = secondaryButtonIcon), + iconResId = secondaryButtonIcon, onClick = onSecondaryButtonClick, modifier = Modifier.fillMaxWidth(), ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt index b67af3906f..2edb3e416c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -4,11 +4,10 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.tangem.core.ui.R import com.tangem.core.ui.components.OutlineTextField -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description @@ -57,11 +56,11 @@ fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modi } OnboardingActionBlock( firstActionContent = { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, onClick = state.buttonCreateWallet.onClick, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index 805aee6175..d29fd51838 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -9,12 +9,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.unit.IntOffset import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.TangemTextFieldsDefault import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R @@ -56,11 +55,11 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo Box { OnboardingActionBlock( firstActionContent = { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, onClick = state.buttonCreateWallet.onClick, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt index c99a233f33..cba03f9df9 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R @@ -46,11 +46,11 @@ fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) { Box { OnboardingActionBlock( firstActionContent = { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, onClick = state.buttonCreateWallet.onClick, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt index 1f87fc061f..6f8bee6fe7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt @@ -74,9 +74,9 @@ fun SwapPermissionBottomSheetContent(data: SwapPermissionState.ReadyForRequest, SpacerH28() - PrimaryButtonIconRight( + PrimaryButtonIconEnd( text = stringResource(id = R.string.swapping_permission_buttons_approve), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, modifier = Modifier.fillMaxWidth(), onClick = data.approveButton.onClick, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index b7a127af0b..27e4b3c750 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -345,10 +345,10 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U ) } else -> { - PrimaryButtonIconRight( + PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = stringResource(id = R.string.swapping_swap), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, enabled = state.swapButton.enabled, showProgress = state.swapButton.loading, onClick = state.swapButton.onClick, From 65a2c8e9dba2eda1b9b9797b452a9273837fbf89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Jun 2023 19:48:11 +0400 Subject: [PATCH 08/14] Updated on 2026-08-14 --- .../models/AddCustomTokenViewsModels.kt | 32 ++++ .../ui/AddCustomTokenPreviewData.kt | 10 +- .../ui/components/AddCustomTokenForm.kt | 38 ++-- .../viewmodels/AddCustomTokenViewModel.kt | 170 ++++++++++++++---- core/res/src/main/res/values-ru/strings.xml | 2 + core/res/src/main/res/values/strings.xml | 2 + .../domain/common/extensions/Blockchain.kt | 1 + 7 files changed, 180 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt index a43c5a84c4..71c369bcad 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt @@ -67,6 +67,8 @@ internal data class AddCustomTokenChooseTokenBottomSheet( * @property tokenSymbolInputField input field to enter the token symbol * @property decimalsInputField input field to enter the token decimals * @property derivationPathSelectorField selector field to select the derivation path + * @property derivationPathInputField input field for a custom derivation path + * @property showTokenFields if token fields should be shown */ internal data class AddCustomTokenForm( val contractAddressInputField: AddCustomTokenInputField.ContactAddress, @@ -75,6 +77,8 @@ internal data class AddCustomTokenForm( val tokenSymbolInputField: AddCustomTokenInputField.TokenSymbol, val decimalsInputField: AddCustomTokenInputField.Decimals, val derivationPathSelectorField: AddCustomTokenSelectorField.DerivationPath?, + val derivationPathInputField: AddCustomTokenInputField.DerivationPath?, + val showTokenFields: Boolean = false, ) /** Base input field model of add custom token screen */ @@ -174,6 +178,25 @@ internal sealed interface AddCustomTokenInputField { override val placeholder: TextReference, val isEnabled: Boolean, ) : AddCustomTokenInputField + + /** + * Input field model to enter a custom derivation path + * + * @property value current value + * @property onValueChange lambda be invoked when value is been changed + * @property keyboardOptions keyboard options + * @property label label + * @property placeholder placeholder (hint) + * @property showField whether the field should be shown + */ + data class DerivationPath( + override val value: String, + override val onValueChange: (String) -> Unit, + override val keyboardOptions: KeyboardOptions, + override val label: TextReference, + override val placeholder: TextReference, + val showField: Boolean = false, + ) : AddCustomTokenInputField } /** Base selector field model of add custom token screen */ @@ -251,10 +274,15 @@ internal sealed interface AddCustomTokenSelectorField { override val title: TextReference, override val blockchain: Blockchain, val subtitle: TextReference, + val type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN, ) : SelectorItem } } +enum class DerivationPathSelectorType { + DEFAULT, CUSTOM, BLOCKCHAIN +} + /** * Warning model of add custom token screen * @@ -276,6 +304,10 @@ internal sealed class AddCustomTokenWarning(val description: TextReference) { object UnsupportedSolanaToken : AddCustomTokenWarning( description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message), ) + + object WrongDerivationPath : AddCustomTokenWarning( + description = TextReference.Res(R.string.custom_token_invalid_derivation_path), + ) } /** diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt index e463c90ae9..d69f1c08fa 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt @@ -4,14 +4,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar +import com.tangem.tap.features.customtoken.impl.presentation.models.* import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R @@ -85,6 +78,7 @@ internal object AddCustomTokenPreviewData { onMenuItemClick = {}, isEnabled = true, ), + derivationPathInputField = null, ) } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt index 6b2e38d732..0051e2e6c4 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt @@ -1,31 +1,11 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.* import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ExposedDropdownMenuBox -import androidx.compose.material.ExposedDropdownMenuDefaults -import androidx.compose.material.LinearProgressIndicator -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -62,12 +42,13 @@ internal fun AddCustomTokenForm(model: AddCustomTokenForm) { modifier = Modifier.padding(TangemTheme.dimens.spacing16), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - InputField(model = model.contractAddressInputField) + if (model.showTokenFields) InputField(model = model.contractAddressInputField) SelectorField(model = model.networkSelectorField) - InputField(model = model.tokenNameInputField) - InputField(model = model.tokenSymbolInputField) - InputField(model = model.decimalsInputField) + if (model.showTokenFields) InputField(model = model.tokenNameInputField) + if (model.showTokenFields) InputField(model = model.tokenSymbolInputField) + if (model.showTokenFields) InputField(model = model.decimalsInputField) model.derivationPathSelectorField?.let { SelectorField(model = it) } + if (model.derivationPathInputField?.showField == true) InputField(model = model.derivationPathInputField) } } } @@ -103,6 +84,7 @@ private fun TextField(model: AddCustomTokenInputField, isError: Boolean) { is AddCustomTokenInputField.Decimals -> model.isEnabled is AddCustomTokenInputField.TokenName -> model.isEnabled is AddCustomTokenInputField.TokenSymbol -> model.isEnabled + is AddCustomTokenInputField.DerivationPath -> true } OutlinedTextField( diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 74084af835..1d886914c7 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -15,6 +15,7 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.Token import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.HDWalletError import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.* @@ -135,18 +136,28 @@ internal class AddCustomTokenViewModel @Inject constructor( tokenSymbolInputField = createTokenSymbolInputField(), decimalsInputField = createDecimalsInputField(), derivationPathSelectorField = createDerivationPathsSelectorField(), + derivationPathInputField = createDerivationPathInputField(), ) } - fun createDerivationPathSelectorItem(blockchain: Blockchain): SelectorItem.TitleWithSubtitle { - return if (blockchain == Blockchain.Unknown) { - SelectorItem.TitleWithSubtitle( + fun createDerivationPathSelectorAdditionalItem( + blockchain: Blockchain, + type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN, + ): SelectorItem.TitleWithSubtitle { + return when (type) { + DerivationPathSelectorType.DEFAULT -> SelectorItem.TitleWithSubtitle( title = TextReference.Res(R.string.custom_token_derivation_path_default), subtitle = TextReference.Res(R.string.custom_token_derivation_path_default), blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, ) - } else { - SelectorItem.TitleWithSubtitle( + DerivationPathSelectorType.CUSTOM -> SelectorItem.TitleWithSubtitle( + title = TextReference.Res(R.string.custom_token_custom_derivation), + subtitle = TextReference.Res(R.string.custom_token_custom_derivation), + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.CUSTOM, + ) + DerivationPathSelectorType.BLOCKCHAIN -> SelectorItem.TitleWithSubtitle( title = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath?.let(TextReference::Str) ?: TextReference.Res(R.string.custom_token_derivation_path_default), subtitle = TextReference.Str(blockchain.fullName), @@ -175,7 +186,7 @@ internal class AddCustomTokenViewModel @Inject constructor( onValueChange = actionsHandler::onContactAddressValueChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), label = TextReference.Res(R.string.custom_token_contract_address_input_title), - placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"), + placeholder = TextReference.Str(value = CONTRACT_ADDRESS_PLACEHOLDER), isLoading = false, isError = false, error = null, @@ -196,8 +207,7 @@ internal class AddCustomTokenViewModel @Inject constructor( val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown) return listOf(defaultNetwork) + Blockchain.values() .filter { blockchain -> - (blockchain.isEvm() || blockchain.canHandleTokens()) && - reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true + reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true } .sortedBy(Blockchain::fullName) .map(::createNetworkSelectorItem) @@ -231,7 +241,7 @@ internal class AddCustomTokenViewModel @Inject constructor( onValueChange = actionsHandler::onDecimalsValueChange, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next), label = TextReference.Res(R.string.custom_token_decimals_input_title), - placeholder = TextReference.Str(value = "8"), + placeholder = TextReference.Str(value = DECIMALS_PLACEHOLDER), isEnabled = false, ) } @@ -250,11 +260,31 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun getDerivationPathsSelectorItems(): List { - val defaultDerivationPath = createDerivationPathSelectorItem(Blockchain.Unknown) - return listOf(defaultDerivationPath) + Blockchain.values() - .filter { blockchain -> blockchain.isEvm() && blockchain.isSupportedInApp() && !blockchain.isTestnet() } + return listOf( + createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, + ), + createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.CUSTOM, + ), + ) + Blockchain.values() + .filter { blockchain -> blockchain.isSupportedInApp() && !blockchain.isTestnet() } .sortedBy(Blockchain::fullName) - .map(::createDerivationPathSelectorItem) + .map(::createDerivationPathSelectorAdditionalItem) + } + + private fun createDerivationPathInputField(): AddCustomTokenInputField.DerivationPath? { + if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null + + return AddCustomTokenInputField.DerivationPath( + value = "", + onValueChange = actionsHandler::onDerivationPathValueChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + label = TextReference.Res(R.string.custom_token_custom_derivation), + placeholder = TextReference.Str(value = DERIVATION_PATH_PLACEHOLDER), + ) } } @@ -306,25 +336,16 @@ internal class AddCustomTokenViewModel @Inject constructor( Timber.e(it) } - updateDerivationPathSelector() updateWarnings() updateFloatingButton() } } - private fun updateDerivationPathSelector() { - uiState = uiState.copySealed( - form = uiState.form.copy( - derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy( - isEnabled = uiState.form.networkSelectorField.selectedItem.blockchain.isEvm(), - ), - ), - ) - } - private fun isDerivationPathSelected(): Boolean { return with(uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) { - this != null && this != Blockchain.Unknown + this != null && this != Blockchain.Unknown || + uiState.form.derivationPathSelectorField?.selectedItem?.type == DerivationPathSelectorType.CUSTOM && + !uiState.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) } } @@ -397,6 +418,7 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun updateFloatingButton() { + uiState = updateStateWithDerivationError(uiState) if (isCustomTokenAlreadyAdded()) { uiState = uiState.copySealed( warnings = uiState.warnings + AddCustomTokenWarning.TokenAlreadyAdded, @@ -405,6 +427,7 @@ internal class AddCustomTokenViewModel @Inject constructor( return } + val isCorrectDerivationInput = !uiState.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) val state = when { isAllTokenFieldsFilled() && isNetworkSelected() -> { val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain @@ -418,7 +441,8 @@ internal class AddCustomTokenViewModel @Inject constructor( uiState.copySealed( floatingButton = uiState.floatingButton.copy( - isEnabled = error is ContractAddressValidatorResult.Success && isSupportedToken, + isEnabled = error is ContractAddressValidatorResult.Success && + isSupportedToken && isCorrectDerivationInput, ), ) } @@ -430,17 +454,43 @@ internal class AddCustomTokenViewModel @Inject constructor( else -> { uiState.copySealed( floatingButton = uiState.floatingButton.copy( - isEnabled = if (isNetworkSelected()) !isBlockchainAlreadyAdded() else false, + isEnabled = if (isNetworkSelected()) { + !isBlockchainAlreadyAdded() && isCorrectDerivationInput + } else { + false + }, ), ) } } uiState = state.copySealed( - warnings = uiState.warnings - AddCustomTokenWarning.TokenAlreadyAdded, + warnings = state.warnings - AddCustomTokenWarning.TokenAlreadyAdded, ) } + private fun updateStateWithDerivationError(uiState: AddCustomTokenStateHolder): AddCustomTokenStateHolder { + val updatedWarnings = if (isWrongDerivationPathEntered( + derivationPathSelectorType = uiState.form.derivationPathSelectorField?.selectedItem?.type, + derivationPath = getDerivationPath(), + ) + ) { + uiState.warnings + AddCustomTokenWarning.WrongDerivationPath + } else { + uiState.warnings - AddCustomTokenWarning.WrongDerivationPath + } + return uiState.copySealed( + warnings = updatedWarnings, + ) + } + + private fun isWrongDerivationPathEntered( + derivationPathSelectorType: DerivationPathSelectorType?, + derivationPath: DerivationPath?, + ): Boolean { + return derivationPathSelectorType == DerivationPathSelectorType.CUSTOM && derivationPath == null + } + private fun isCustomTokenAlreadyAdded(): Boolean { return when (getCustomTokenType()) { CustomTokenType.TOKEN -> isTokenAlreadyAdded() @@ -520,16 +570,36 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun getDerivationPath(): DerivationPath? { + return when (uiState.form.derivationPathSelectorField?.selectedItem?.type) { + DerivationPathSelectorType.CUSTOM -> + createDerivationPathOrNull(uiState.form.derivationPathInputField?.value ?: "") + else -> + getDerivationPathForBlockchain(uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) + } + } + + private fun createDerivationPathOrNull(rawPath: String): DerivationPath? { + return try { + DerivationPath(rawPath) + } catch (error: HDWalletError) { + null + } + } + + private fun getDerivationPathForBlockchain(blockchain: Blockchain?): DerivationPath? { + if (blockchain == null) return null + val derivationStyle = if (!isDerivationPathSelected()) { reduxStateHolder.scanResponse?.card?.derivationStyle } else { DerivationStyle.LEGACY } - - return when (val derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) { - Blockchain.Unknown -> uiState.form.networkSelectorField.selectedItem.blockchain - else -> derivationNetwork - }?.derivationPath(derivationStyle) + val derivationNetwork = if (blockchain == Blockchain.Unknown) { + uiState.form.networkSelectorField.selectedItem.blockchain + } else { + blockchain + } + return derivationNetwork.derivationPath(derivationStyle) } private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) { @@ -566,7 +636,6 @@ internal class AddCustomTokenViewModel @Inject constructor( is ContractAddressValidatorResult.Error -> { handleContractAddressErrorValidation(type = validatorResult.type) - updateDerivationPathSelector() updateWarnings() updateFloatingButton() } @@ -574,11 +643,13 @@ internal class AddCustomTokenViewModel @Inject constructor( } fun onNetworkSelectorItemClick(index: Int) { + val selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)) uiState = uiState.copySealed( form = uiState.form.copy( networkSelectorField = uiState.form.networkSelectorField.copy( - selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)), + selectedItem = selectedItem, ), + showTokenFields = selectedItem.blockchain.canHandleTokens(), ), ) onContactAddressValueChange(uiState.form.contractAddressInputField.value) @@ -612,12 +683,26 @@ internal class AddCustomTokenViewModel @Inject constructor( } fun onDerivationPathSelectorItemClick(index: Int) { - val field = requireNotNull(uiState.form.derivationPathSelectorField) + val derivationSelector = requireNotNull(uiState.form.derivationPathSelectorField) + val selected = requireNotNull(derivationSelector.items.getOrNull(index)) + val derivationInputField = requireNotNull(uiState.form.derivationPathInputField) uiState = uiState.copySealed( form = uiState.form.copy( - derivationPathSelectorField = field.copy( - selectedItem = requireNotNull(field.items.getOrNull(index)), + derivationPathSelectorField = derivationSelector.copy( + selectedItem = selected, ), + derivationPathInputField = derivationInputField.copy( + showField = selected.type == DerivationPathSelectorType.CUSTOM, + ), + ), + ) + updateFloatingButton() + } + + fun onDerivationPathValueChange(enteredValue: String) { + uiState = uiState.copySealed( + form = uiState.form.copy( + derivationPathInputField = uiState.form.derivationPathInputField?.copy(value = enteredValue), ), ) updateFloatingButton() @@ -691,7 +776,11 @@ internal class AddCustomTokenViewModel @Inject constructor( decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false), derivationPathSelectorField = derivationPathSelectorField?.copy( isEnabled = true, - selectedItem = formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown), + selectedItem = formStateBuilder + .createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, + ), ), ), ) @@ -722,5 +811,8 @@ internal class AddCustomTokenViewModel @Inject constructor( TestTokenItem(name = "USDT (invalid - 1/3 of address)", address = "Es9vMFrzaCERmJ"), TestTokenItem(name = "ETH (full)", address = "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk"), ) + const val DERIVATION_PATH_PLACEHOLDER = "m/44'/0'/0'/0/0" + const val DECIMALS_PLACEHOLDER = "8" + const val CONTRACT_ADDRESS_PLACEHOLDER = "0x0000000000000000000000000000000000000000" } } \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index af94ad33ae..70e4deefde 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -84,9 +84,11 @@ Пожалуйста, выберите сеть Обязательное поле Количество знаков после запятой должно быть корректным числом не больше %d + Своя деривация Знаков после запятой По умолчанию Деривация по BIP44 + Введенный путь деривации некорректен Например, USD Coin Название токена Не выбрано diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 038f2a7fe9..cc6b198c0b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -82,9 +82,11 @@ Please select the network Required field Decimal number must be a valid integer, no higher than %d + Custom derivation Decimals Default BIP44 coin type + The derivation path you\'ve entered is not valid E.g. USD Coin Name Not selected diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index aca74ef3ec..5dfdf7c1d3 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -177,4 +177,5 @@ fun Blockchain.isSupportedInApp(): Boolean { private val excludedBlockchains = listOf( Blockchain.SaltPay, + Blockchain.Unknown, ) \ No newline at end of file From e8b81f400dc60ba54d3e992ab62e0c07150cadc8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Jun 2023 14:06:14 +0300 Subject: [PATCH 09/14] Updated on 2026-08-14 --- .../presentation/common/WalletPreviewData.kt | 16 ++++ .../common/state/NetworkGroupState.kt | 23 +++++ .../common/state/TokenListState.kt | 19 ---- .../organizetokens/OrganizeTokensScreen.kt | 88 +++++++++++++++++++ .../OrganizeTokensStateHolder.kt | 20 +++++ .../organizetokens/OrganizeTokensViewModel.kt | 22 +++++ .../router/DefaultWalletRouter.kt | 8 +- 7 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index 619f57d8bb..40817e750e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -4,6 +4,8 @@ import com.tangem.core.ui.R import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState +import com.tangem.feature.wallet.presentation.common.state.TokenListState +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import kotlinx.collections.immutable.persistentListOf @@ -105,6 +107,20 @@ internal object WalletPreviewData { val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) + val organizeTokensState = OrganizeTokensStateHolder( + tokens = TokenListState.GroupedByNetwork( + groups = persistentListOf(), + ), + header = OrganizeTokensStateHolder.HeaderConfig( + onSortByBalanceClick = {}, + onGroupByNetworkClick = {}, + ), + actions = OrganizeTokensStateHolder.ActionsConfig( + onApplyClick = {}, + onCancelClick = {}, + ), + ) + private val draggableTokenList = persistentListOf( tokenItemDragState.copy( id = "token_1", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt new file mode 100644 index 0000000000..c34fdd126d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.wallet.presentation.common.state + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal sealed interface NetworkGroupState { + val id: String + val networkName: String + val tokens: ImmutableList + + data class Draggable( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState + + data class Content( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt index c504d1c81e..fe3b7fba46 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt @@ -13,23 +13,4 @@ internal sealed interface TokenListState { data class Ungrouped( val tokens: ImmutableList, ) : TokenListState -} - -@Immutable -internal sealed interface NetworkGroupState { - val id: String - val networkName: String - val tokens: ImmutableList - - data class Draggable( - override val id: String, - override val networkName: String, - override val tokens: ImmutableList, - ) : NetworkGroupState - - data class Content( - override val id: String, - override val networkName: String, - override val tokens: ImmutableList, - ) : NetworkGroupState } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt new file mode 100644 index 0000000000..e626eb0029 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -0,0 +1,88 @@ +// TODO: Remove after components implementation +@file:Suppress("UNUSED_PARAMETER") + +package com.tangem.feature.wallet.presentation.organizetokens + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.state.TokenListState + +@Composable +internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) { + Scaffold( + topBar = { + TopBar(state.header) + }, + content = { paddingValues -> + TokenList( + state = state.tokens, + modifier = Modifier.padding(paddingValues), + ) + }, + floatingActionButtonPosition = FabPosition.Center, + floatingActionButton = { + Actions(state.actions) + }, + contentColor = TangemTheme.colors.background.primary, + ) +} + +@Composable +private fun TokenList(state: TokenListState, modifier: Modifier = Modifier) { + when (state) { + is TokenListState.GroupedByNetwork -> { + /* [REDACTED_TODO_COMMENT] */ + } + is TokenListState.Ungrouped -> { + /* [REDACTED_TODO_COMMENT] */ + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(config: OrganizeTokensStateHolder.HeaderConfig, modifier: Modifier = Modifier) { + TopAppBar( + title = { + Text( + text = "Organize tokens", // TODO: Move to resources + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = TangemTheme.colors.background.secondary, + titleContentColor = TangemTheme.colors.text.primary1, + ), + ) +} + +@Composable +private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) { + // TODO: Implement list apply and cancel actions +} + +// region Preview + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OrganizeTokensScreenPreview_Light() { + TangemTheme { + OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OrganizeTokensScreenPreview_Dark() { + TangemTheme(isDark = true) { + OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + } +} +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt new file mode 100644 index 0000000000..b9a8e797cb --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.wallet.presentation.organizetokens + +import com.tangem.feature.wallet.presentation.common.state.TokenListState + +internal data class OrganizeTokensStateHolder( + val tokens: TokenListState, + val header: HeaderConfig, + val actions: ActionsConfig, +) { + + data class HeaderConfig( + val onSortByBalanceClick: () -> Unit, + val onGroupByNetworkClick: () -> Unit, + ) + + data class ActionsConfig( + val onApplyClick: () -> Unit, + val onCancelClick: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt new file mode 100644 index 0000000000..16f726eab1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -0,0 +1,22 @@ +package com.tangem.feature.wallet.presentation.organizetokens + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlin.properties.Delegates + +@HiltViewModel +internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() { + + var router: InnerWalletRouter by Delegates.notNull() + + var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState()) + private set + + private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 74751d0c6b..f2768ed116 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -10,6 +10,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.WalletFragment +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel import kotlin.properties.Delegates @@ -35,7 +37,11 @@ internal class DefaultWalletRouter : InnerWalletRouter { composable(WalletScreens.ORGANIZE_TOKENS.name) { BackHandler(onBack = ::popBackStack) - // TODO: Add OrganizeTokensScreen + + val viewModel: OrganizeTokensViewModel = hiltViewModel() + .apply { router = this@DefaultWalletRouter } + + OrganizeTokensScreen(state = viewModel.uiState) } } } From bd217bf4a51ee675f8ec873c35feac44f90d1c2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Jun 2023 19:28:01 +0300 Subject: [PATCH 10/14] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/com/tangem/tap/MainActivity.kt | 3 +++ app/src/main/res/drawable/inset_splash.xml | 10 ++++++++++ app/src/main/res/drawable/splash_logo.xml | 15 +++++++++++++++ app/src/main/res/values/colors.xml | 3 ++- app/src/main/res/values/styles.xml | 6 ++++++ gradle/dependencies.toml | 6 ++++-- 8 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/inset_splash.xml create mode 100644 app/src/main/res/drawable/splash_logo.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b536124d5f..4f61d629c7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { /** AndroidX libraries */ implementation(deps.androidx.core.ktx) + implementation(deps.androidx.core.splashScreen) implementation(deps.androidx.appCompat) implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.constraintLayout) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e632399d0..48ca967d94 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,7 @@ () override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) systemActions() diff --git a/app/src/main/res/drawable/inset_splash.xml b/app/src/main/res/drawable/inset_splash.xml new file mode 100644 index 0000000000..1d66dc1301 --- /dev/null +++ b/app/src/main/res/drawable/inset_splash.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_logo.xml b/app/src/main/res/drawable/splash_logo.xml new file mode 100644 index 0000000000..29cb6365cf --- /dev/null +++ b/app/src/main/res/drawable/splash_logo.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ce094f06b8..3509ec1ee5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -33,7 +33,6 @@ #F2F2F2 #090E13 - #14212121 @color/colorSecondary @@ -74,5 +73,7 @@ #656565 #919191 + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9356f81daf..ae38fdcf4a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -12,6 +12,12 @@ + diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5ad6f0a810..47104a5cb0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -14,7 +14,8 @@ androidxActivityCompose = "1.5.0" androidxAppCompat = "1.5.1" androidxBrowser = "1.4.0" androidxConstraintLayout = "2.1.4" -androidxCore = "1.9.0" +androidxKtx = "1.9.0" +androidxSplashScreen = "1.0.1" androidxFragment = "1.5.3" androidxLifecycle = "2.5.1" androidx-paging = "3.1.1" @@ -119,7 +120,8 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppCompat" } androidx-browser = { module = "androidx.browser:browser", version.ref = "androidxBrowser" } androidx-constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidxConstraintLayout" } -androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } +androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxKtx" } +androidx-core-splashScreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplashScreen" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidxFragment" } androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" } lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidxLifecycle" } From 8fd894f5d58232b9ca561f8e6d0198bc14aa957c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Jun 2023 13:46:21 +0500 Subject: [PATCH 11/14] Updated on 2026-08-14 --- .../com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index e5a3579e07..d84b7e10b3 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -15,10 +15,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.backup.BackupService import com.tangem.tap.* import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor -import com.tangem.tap.common.extensions.addContext -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa -import com.tangem.tap.common.extensions.setContext +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -36,7 +33,6 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.Onboardi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.rekotlin.Action internal object LegacyScanProcessor { @@ -165,7 +161,7 @@ internal object LegacyScanProcessor { scope.launch { delay(DELAY_SDK_DIALOG_CLOSE) disclaimerWillShow() - arrayOf( + store.dispatchWithMain( DisclaimerAction.Show( fromScreen = AppScreen.Home, callback = DisclaimerCallback( From 45013d61887d4ad2d076ef5eb3d97bda9a5afc45 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Jun 2023 17:11:34 +0500 Subject: [PATCH 12/14] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 4 - .../converters/ParamCardCurrencyConverter.kt | 2 - .../CardContextInterceptor.kt | 5 - .../tap/common/extensions/BackupService.kt | 12 - .../tap/common/extensions/ImageRequest.kt | 24 +- .../tap/common/extensions/WalletManager.kt | 22 - .../com/tangem/tap/common/redux/AppState.kt | 2 - .../tap/common/redux/global/GlobalAction.kt | 9 +- .../common/redux/global/GlobalMiddleware.kt | 33 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 1 - .../com/tangem/tap/domain/extensions/Card.kt | 3 +- .../tap/domain/model/WalletDataModel.kt | 2 - .../model/builders/UserWalletBuilder.kt | 1 - .../model/builders/UserWalletIdBuilder.kt | 1 - .../model/builders/WalletStoreBuilder.kt | 6 - .../moduleMessage/ModuleMessageConverter.kt | 3 - .../saltPay/SaltPayMessageConverter.kt | 34 - .../SaltPayActivationErrorConverter.kt | 53 - .../domain/scanCard/LegacyScanProcessor.kt | 133 +-- .../domain/scanCard/UseCaseScanProcessor.kt | 4 - .../chains/CheckForOnboardingChain.kt | 56 +- .../CheckForUnfinishedSaltPayBackupChain.kt | 44 - .../scanCard/chains/ScanChainException.kt | 12 - .../utils/ScanCardExceptionConverter.kt | 5 - .../domain/tasks/product/ScanProductTask.kt | 10 +- .../userWalletList/GetCardImageUseCase.kt | 2 - .../DefaultWalletAmountsRepository.kt | 10 +- .../utils/WalletDataOperations.kt | 17 - .../utils/WalletStoreOperations.kt | 6 - .../features/details/redux/DetailsReducer.kt | 4 +- .../details/ui/details/DetailsViewModel.kt | 9 +- .../tap/features/disclaimer/Disclaimer.kt | 6 - .../tap/features/disclaimer/DisclaimerType.kt | 4 - .../features/onboarding/OnboardingHelper.kt | 1 - .../onboarding/OnboardingSaltPayHelper.kt | 43 - .../wallet/redux/OnboardingWalletAction.kt | 7 +- .../redux/OnboardingWalletMiddleware.kt | 70 +- .../wallet/redux/OnboardingWalletReducer.kt | 25 +- .../wallet/redux/OnboardingWalletState.kt | 41 +- .../wallet/saltPay/GnosisRegistrator.kt | 230 ---- .../products/wallet/saltPay/PinFilters.kt | 18 - .../saltPay/SaltPayActivationManager.kt | 341 ------ .../wallet/saltPay/SaltPayExceptionHandler.kt | 53 - .../wallet/saltPay/SaltPayRegistrationTask.kt | 167 --- .../products/wallet/saltPay/UtorgWebView.kt | 168 --- .../dialog/NoFundsForActivationDialog.kt | 40 - .../saltPay/dialog/PutVisaCardDialog.kt | 25 - .../saltPay/dialog/RegistrationErrorDialog.kt | 31 - .../wallet/saltPay/dialog/SaltPayDialog.kt | 15 - .../saltPay/message/SaltPayModuleMessage.kt | 56 - .../saltPay/redux/OnboardingSaltPayAction.kt | 46 - .../redux/OnboardingSaltPayMiddleware.kt | 466 -------- .../saltPay/redux/OnboardingSaltPayReducer.kt | 76 -- .../saltPay/redux/OnboardingSaltPayState.kt | 46 - .../ui/OnboardingSaltPayStateHandler.kt | 387 ------- .../wallet/ui/OnboardingWalletFragment.kt | 107 +- .../dialogs}/InterruptOnboardingDialog.kt | 2 +- .../tap/features/wallet/redux/WalletState.kt | 1 - .../tap/features/wallet/ui/WalletFragment.kt | 40 +- .../wallet/ui/wallet/MultiWalletView.kt | 1 - .../wallet/ui/wallet/SingleWalletView.kt | 1 - .../ui/wallet/saltPay/SaltPayWalletView.kt | 244 ---- .../ui/wallet/saltPay/rv/HistoryItemData.kt | 79 -- .../ui/wallet/saltPay/rv/TxHistoryAdapter.kt | 113 -- .../redux/WalletSelectorMiddleware.kt | 13 +- .../welcome/redux/WelcomeMiddleware.kt | 5 +- .../exchangeServices/BuyExchangeService.kt | 12 +- .../utorg/UtorgEnvironment.kt | 86 -- .../utorg/UtorgExchangeService.kt | 116 -- .../exchangeServices/utorg/api/UtorgApi.kt | 62 - .../utorg/api/model/UtorgCurrencyResponse.kt | 25 - .../utorg/mock/MockUtorgApi.kt | 58 - .../mock/MockUtorgSuccessDataResponse.kt | 12 - .../utorg/mock/json/CurrencyResponseJson.kt | 1025 ----------------- .../main/res/color/selector_saltpay_btn.xml | 5 - .../res/color/selector_saltpay_btn_text.xml | 5 - .../main/res/drawable/img_salt_pay_visa.png | Bin 28037 -> 0 bytes ...out_onboarding_saltpay_kyc_in_progress.xml | 87 -- .../res/layout/fragment_onboarding_wallet.xml | 9 +- app/src/main/res/layout/fragment_wallet.xml | 9 - .../res/layout/item_salt_pay_tx_history.xml | 89 -- .../layout/item_salt_pay_tx_history_date.xml | 20 - .../item_salt_pay_tx_history_shimmer.xml | 60 - .../res/layout/layout_onboarding_saltpay.xml | 40 - ...layout_onboarding_saltpay_connect_card.xml | 78 -- ...out_onboarding_saltpay_kyc_in_progress.xml | 87 -- .../layout_onboarding_saltpay_kyc_start.xml | 76 -- .../layout_onboarding_saltpay_pin_code.xml | 72 -- .../res/layout/layout_salt_pay_balance.xml | 134 --- .../res/layout/layout_salt_pay_tx_history.xml | 136 --- .../res/layout/layout_salt_pay_wallet.xml | 32 - app/src/main/res/values/styles.xml | 13 - .../tangem/common/module/ModuleErrorCode.kt | 1 - .../datasource/config/ConfigManagerImpl.kt | 7 - .../tangem/datasource/config/models/Config.kt | 1 - .../datasource/config/models/JsonModels.kt | 2 - .../datasource/config/models/SaltPayConfig.kt | 36 - .../com/tangem/domain/card/ScanCardUseCase.kt | 1 - .../tangem/domain/common/CardTypesResolver.kt | 3 - .../tangem/domain/common/SaltPayWorkaround.kt | 84 -- .../domain/common/TangemCardTypesResolver.kt | 24 +- .../tangem/domain/common/TapWorkarounds.kt | 9 - .../domain/common/extensions/Blockchain.kt | 4 - .../tangem/domain/models/scan/ScanResponse.kt | 2 +- gradle/dependencies.toml | 2 +- 105 files changed, 123 insertions(+), 5898 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt rename app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/{saltPay/dialog => ui/dialogs}/InterruptOnboardingDialog.kt (92%) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt delete mode 100644 app/src/main/res/color/selector_saltpay_btn.xml delete mode 100644 app/src/main/res/color/selector_saltpay_btn_text.xml delete mode 100644 app/src/main/res/drawable/img_salt_pay_visa.png delete mode 100644 app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml delete mode 100644 app/src/main/res/layout/item_salt_pay_tx_history.xml delete mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_date.xml delete mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml delete mode 100644 app/src/main/res/layout/layout_onboarding_saltpay.xml delete mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml delete mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml delete mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml delete mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml delete mode 100644 app/src/main/res/layout/layout_salt_pay_balance.xml delete mode 100644 app/src/main/res/layout/layout_salt_pay_tx_history.xml delete mode 100644 app/src/main/res/layout/layout_salt_pay_wallet.xml delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt delete mode 100644 domain/legacy/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 1147faf13a..a66fd6caa0 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -12,7 +12,6 @@ import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.* import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.* import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.ui.dialogs.* @@ -118,9 +117,6 @@ class DialogManager : StoreSubscriber { context = context, cardId = state.dialog.cardId, ) - is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) - is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context) - is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog) is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index 61d71c5a14..0a7928efd5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -3,7 +3,6 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.Blockchain import com.tangem.common.Converter import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.analytics.events.AnalyticsParam /** @@ -17,7 +16,6 @@ class ParamCardCurrencyConverter : Converter AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) - value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay)) value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!) else -> null diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 748a82a14e..864c9dadbb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -45,11 +45,6 @@ class CardContextInterceptor( ProductType.Note -> "Note" ProductType.Twins -> "Twin" ProductType.Wallet -> "Wallet" - ProductType.SaltPay -> if (scanResponse.cardTypesResolver.isSaltPayVisa()) { - "Visa" - } else { - "Visa Backup" - } ProductType.Start2Coin -> "Start2Coin" else -> if (DemoHelper.isDemoCard(scanResponse)) { if (DemoHelper.isTestDemoCard(scanResponse)) { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt deleted file mode 100644 index 140f0946b4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.common.extensions - -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.operations.backup.BackupService - -/** -[REDACTED_AUTHOR] - */ -@Suppress("MagicNumber") -fun BackupService.primaryCardIsSaltPayVisa(): Boolean { - return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt index 9da1448d9c..2eb87310f9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt @@ -1,32 +1,10 @@ package com.tangem.tap.common.extensions -import android.widget.ImageView -import coil.ImageLoader -import coil.imageLoader -import coil.load -import coil.request.Disposable import coil.request.ImageRequest -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply { - data(any?.parseData()) -} - -fun ImageView.loadCardImage( - data: Any?, - imageLoader: ImageLoader = context.imageLoader, - builder: ImageRequest.Builder.() -> Unit = {}, -): Disposable = this.load( - data = data?.parseData(), - imageLoader = imageLoader, - builder = builder, -) - -private fun Any?.parseData(): Any? = when { - this is String && this == Artwork.SALT_PAY_URL -> R.drawable.img_salt_pay_visa - else -> this + data(any) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 925cb0c728..36dfe253b5 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -2,9 +2,6 @@ package com.tangem.tap.common.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionHistoryProvider import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.common.services.Result @@ -38,9 +35,6 @@ suspend fun WalletManager.safeUpdate(): Result = try { Result.Success(wallet) } else { update() - if (wallet.blockchain == Blockchain.SaltPay && this is TransactionHistoryProvider) { - this.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens()) - } Result.Success(wallet) } } catch (exception: Exception) { @@ -87,26 +81,10 @@ fun WalletManager?.getAddressData(): WalletDataModel.AddressData? { return if (addressDataList.isEmpty()) null else addressDataList[0] } -fun WalletManager.getBlockchainTxHistory(): List = wallet.getBlockchainTxHistory() - -fun WalletManager.getTokenTxHistory(token: Token): List = wallet.getTokenTxHistory(token) - fun WalletManager.Companion.stub(): T { val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf()) return object : WalletManager(wallet) { override val currentHost: String = "" override suspend fun update() {} } as T -} - -fun Wallet.getBlockchainTxHistory(): List { - return historyTransactions.filter { - it.contractAddress.isNullOrEmpty() - } -} - -fun Wallet.getTokenTxHistory(token: Token): List { - return historyTransactions.filter { - it.contractAddress == token.contractAddress - } } \ 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 de6bae2411..4ebc39d1d2 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 @@ -24,7 +24,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware @@ -94,7 +93,6 @@ data class AppState( HomeMiddleware.handler, OnboardingNoteMiddleware.handler, OnboardingWalletMiddleware.handler, - OnboardingSaltPayMiddleware.handler, OnboardingOtherCardsMiddleware.handler, WalletMiddleware().walletMiddleware, TwinCardsMiddleware.handler, 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 f589ca6743..d599f9c378 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 @@ -38,19 +38,16 @@ sealed class GlobalAction : Action { sealed class Onboarding : GlobalAction() { /** * Initiate an onboarding process. - * For resuming unfinished backup of standard Wallet and SaltPay cards see + * For resuming unfinished backup of standard Wallet see * BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup */ data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding() /** - * Initiate resuming of unfinished backup for standard Wallet and SaltPay cards. + * Initiate resuming of unfinished backup for standard Wallet. * See more BackupAction.CheckForUnfinishedBackup */ - data class StartForUnfinishedBackup( - val addedBackupCardsCount: Int, - val isSaltPayVisa: Boolean, - ) : Onboarding() + data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding() object Stop : Onboarding() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 01d00c9a82..46835f3076 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -6,9 +6,7 @@ import com.tangem.common.extensions.guard import com.tangem.datasource.config.models.Config import com.tangem.domain.common.LogConfig import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -26,16 +24,12 @@ import com.tangem.tap.network.exchangeServices.ExchangeService import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService -import com.tangem.tap.network.exchangeServices.utorg.UtorgAuthProvider -import com.tangem.tap.network.exchangeServices.utorg.UtorgEnvironment -import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService 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.walletCurrenciesManager -import com.tangem.wallet.BuildConfig import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -109,13 +103,8 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di return } - val scanResponse = globalState.scanResponse ?: globalState.onboardingState.onboardingManager?.scanResponse - // if config not set -> try to get it based on a scanResponse.productType - val unsafeChatConfig = action.chatConfig ?: when { - scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.sprinklr - else -> config.zendesk - } + val unsafeChatConfig = action.chatConfig ?: config.zendesk val chatConfig = unsafeChatConfig.guard { store.dispatchDebugErrorNotification("ZendeskConfig not initialized") @@ -139,11 +128,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di store.state.globalState.scanResponse ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse } - val productTypeProvider: () -> ProductType? = { scanResponseProvider.invoke()?.productType } val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } val exchangeManager = CurrencyExchangeManager( - buyService = makeBuyExchangeService(config, productTypeProvider), + buyService = makeBuyExchangeService(config), sellService = makeSellExchangeService(config), primaryRules = CardExchangeRules(cardProvider), ) @@ -213,28 +201,13 @@ private fun makeSellExchangeService(config: Config): ExchangeService { ) } -private fun makeBuyExchangeService(config: Config, productTypeProvider: () -> ProductType?): ExchangeService { +private fun makeBuyExchangeService(config: Config): ExchangeService { return BuyExchangeService( - productTypeProvider = productTypeProvider, mercuryoService = makeMercuryoExchangeService(config), - utorgService = makeUtorgExchangeService(config), ) } private fun makeMercuryoExchangeService(config: Config): MercuryoService { val mercuryoEnvironment = MercuryoEnvironment.prod(config.mercuryoWidgetId, config.mercuryoSecret) return MercuryoService(mercuryoEnvironment) -} - -private fun makeUtorgExchangeService(config: Config): UtorgExchangeService { - val saltPayConfig = requireNotNull(config.saltPayConfig) - - val utorgAuthProvider = UtorgAuthProvider(saltPayConfig.kycProvider.sidValue) - val utorgEnvironment = if (BuildConfig.DEBUG) { - UtorgEnvironment.stage(utorgAuthProvider, LogConfig.network.utorgService) - // UtorgEnvironment.mock() - } else { - UtorgEnvironment.prod(utorgAuthProvider) - } - return UtorgExchangeService(utorgEnvironment) } \ No newline at end of file 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 ffb1ece346..c395569c5c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -229,7 +229,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co tangemSdk.config.cardIdDisplayFormat = when { scanResponse == null -> CardIdDisplayFormat.Full scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) - scanResponse.cardTypesResolver.isSaltPay() -> CardIdDisplayFormat.None else -> CardIdDisplayFormat.Full } } 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 19bbc1ae52..f963c6f982 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 @@ -4,7 +4,6 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber import com.tangem.operations.attestation.CardVerifyAndGetInfo @@ -21,7 +20,7 @@ val CardDTO.isFirmwareMultiwalletAllowed: Boolean get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1 val CardDTO.isHdWalletAllowedByApp: Boolean - get() = settings.isHDWalletAllowed && !isSaltPay + get() = settings.isHDWalletAllowed @Suppress("UnnecessaryParentheses") fun CardDTO.hasSignedHashes(): Boolean { 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 e6782972eb..fc0acca07c 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 @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.domain.model.WalletDataModel.AddressData import com.tangem.tap.domain.model.WalletDataModel.Status @@ -28,7 +27,6 @@ data class WalletDataModel( val fiatRate: BigDecimal?, val isCardSingleToken: Boolean, val isCustom: Boolean, - val historyTransactions: List?, ) { data class WalletAddresses( 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 3cc1d2e5f5..d4e6cbd1fc 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 @@ -21,7 +21,6 @@ class UserWalletBuilder( get() = when (productType) { ProductType.Note -> "Note" ProductType.Twins -> "Twin" - ProductType.SaltPay -> "SaltPay" ProductType.Start2Coin -> "Start2Coin" ProductType.Wallet -> when { card.isBackupNotAllowed -> "Tangem card" 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 68626b34eb..64f5f8156d 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 @@ -60,7 +60,6 @@ class UserWalletIdBuilder private constructor( ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() ProductType.Note, ProductType.Wallet, - ProductType.SaltPay, ProductType.Start2Coin, -> null }, 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 ecca6f2314..e7d01ecdb6 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 @@ -9,8 +9,6 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.tap.common.extensions.getBlockchainTxHistory -import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel @@ -114,7 +112,6 @@ private fun BlockchainNetwork.getBlockchainWalletData( fiatRate = null, isCardSingleToken = false, isCustom = currency.isCustomCurrency(cardDerivationStyle), - historyTransactions = walletManager?.getBlockchainTxHistory(), ) } @@ -138,7 +135,6 @@ private fun BlockchainNetwork.getTokensWalletsData( fiatRate = null, isCardSingleToken = token == primaryToken, isCustom = currency.isCustomCurrency(cardDerivationStyle), - historyTransactions = walletManager?.getTokenTxHistory(token), ) } } @@ -156,7 +152,6 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal fiatRate = null, isCardSingleToken = false, isCustom = false, - historyTransactions = walletManager.getBlockchainTxHistory(), ) } @@ -174,7 +169,6 @@ private fun Token.toTokenWalletData(walletManager: WalletManager, primaryToken: fiatRate = null, isCardSingleToken = this == primaryToken, isCustom = false, - historyTransactions = walletManager.getTokenTxHistory(this), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt index 7c665e5cdd..efca92a6e8 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt @@ -5,8 +5,6 @@ import com.tangem.common.module.ModuleMessage import com.tangem.common.module.ModuleMessageConverter import com.tangem.domain.DomainModuleMessage import com.tangem.tap.domain.moduleMessage.domain.DomainMessageConverter -import com.tangem.tap.domain.moduleMessage.saltPay.SaltPayMessageConverter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage class ModuleMessageConverter( private val context: Context, @@ -15,7 +13,6 @@ class ModuleMessageConverter( override fun convert(message: ModuleMessage): ConvertedMessage { val convertedMessage = when (message) { is DomainModuleMessage -> DomainMessageConverter(context).convert(message) - is SaltPayModuleMessage -> SaltPayMessageConverter(context).convert(message) else -> null } return convertedMessage ?: convertUnknownMessage(message) diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt deleted file mode 100644 index 9db1c78996..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.domain.moduleMessage.saltPay - -import android.content.Context -import com.tangem.common.module.ModuleMessageConverter -import com.tangem.tap.domain.moduleMessage.ConvertedMessage -import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayActivationErrorConverter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage - -/** -[REDACTED_AUTHOR] - */ -class SaltPayMessageConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayModuleMessage): ConvertedMessage? { - return when (message) { - is SaltPayModuleError -> SaltPayErrorConverter(context).convert(message) - else -> null - } - } -} - -class SaltPayErrorConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayModuleError): ConvertedMessage? = when (message) { - is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message) - else -> null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt deleted file mode 100644 index 1771a2db47..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tap.domain.moduleMessage.saltPay.converter - -import android.content.Context -import com.tangem.common.module.ModuleMessageConverter -import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -internal class SaltPayActivationErrorConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? { - val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException() - - val dialogMessage = when (saltPayError) { - SaltPayActivationError.NoGas -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_no_gas_title), - message = context.getString(R.string.saltpay_error_no_gas_message), - ) - } - SaltPayActivationError.EmptyBackupCardScanned -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_empty_backup_title), - message = context.getString(R.string.saltpay_error_empty_backup_message), - ) - } - SaltPayActivationError.WeakPin -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_pin_weak_title), - message = context.getString(R.string.saltpay_error_pin_weak_message), - ) - } - else -> { - val errorMessage = if (saltPayError.message == SaltPayActivationError.EMPTY_MESSAGE) { - saltPayError::class.java.simpleName - } else { - saltPayError.message - } - ConvertedDialogMessage( - title = context.getString(R.string.common_error), - message = errorMessage, - ) - } - } - - return dialogMessage - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index d84b7e10b3..f885b4fa13 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -5,14 +5,11 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService import com.tangem.tap.* import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor import com.tangem.tap.common.extensions.* @@ -23,13 +20,8 @@ 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.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -81,24 +73,16 @@ internal object LegacyScanProcessor { onScanStateChange(false) sendAnalytics(analyticsEvent, scanResponse) - checkForUnfinishedBackupForSaltPay( - backupService = backupService, + showDisclaimerIfNeed( scanResponse = scanResponse, + disclaimerWillShow = disclaimerWillShow, onFailure = onFailure, - nextHandler = { scanResponse1 -> - showDisclaimerIfNeed( - scanResponse = scanResponse1, - disclaimerWillShow = disclaimerWillShow, - onFailure = onFailure, - nextHandler = { scanResponse2 -> - onScanSuccess( - scanResponse = scanResponse2, - onProgressStateChange = onProgressStateChange, - onSuccess = onSuccess, - onWalletNotCreated = onWalletNotCreated, - onFailure = onFailure, - ) - }, + nextHandler = { scanResponse2 -> + onScanSuccess( + scanResponse = scanResponse2, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, ) }, ) @@ -117,35 +101,6 @@ internal object LegacyScanProcessor { } } - /** - * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards - * see BackupAction.CheckForUnfinishedBackup - * If user touches card other than Visa SaltPay - show dialog and block next processing - */ - private suspend inline fun checkForUnfinishedBackupForSaltPay( - backupService: BackupService, - scanResponse: ScanResponse, - nextHandler: (ScanResponse) -> Unit, - onFailure: suspend (error: TangemError) -> Unit, - ) { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - nextHandler(scanResponse) - return - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == scanResponse.card.cardId } - ?: false - - if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - nextHandler(scanResponse) - } - } - private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, crossinline disclaimerWillShow: () -> Unit = {}, @@ -188,7 +143,6 @@ internal object LegacyScanProcessor { crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline onWalletNotCreated: suspend () -> Unit, crossinline onSuccess: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, ) { val globalState = store.state.globalState val tapWalletManager = globalState.tapWalletManager @@ -196,66 +150,21 @@ internal object LegacyScanProcessor { store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - val cardTypesResolver = scanResponse.cardTypesResolver - if (cardTypesResolver.isSaltPay()) { - if (cardTypesResolver.isSaltPayVisa()) { - val manager = SaltPayActivationManagerFactory( - blockchain = scanResponse.cardTypesResolver.getBlockchain(), - card = scanResponse.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) - delay(500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(scanResponse) - val isOnboardingCase = result.data - if (isOnboardingCase) { - onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - onFailure(TangemSdkError.ExceptionError(result.error)) - } - } - } + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) + onWalletNotCreated() + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + Analytics.setContext(scanResponse) + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + onWalletNotCreated() + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) - if (scanResponse.card.backupStatus?.isActive == false) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - Analytics.setContext(scanResponse) - onSuccess(scanResponse) - } - } - } else { - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - Analytics.addContext(scanResponse) - onWalletNotCreated() - store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) { onProgressStateChange(it) } - } else { - Analytics.setContext(scanResponse) - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - onWalletNotCreated() - store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) - navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } + onSuccess(scanResponse) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 52d6cf4339..45a463343a 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -7,7 +7,6 @@ import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.card.ScanCardException import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.backupService import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -52,7 +51,6 @@ internal object UseCaseScanProcessor { if (analyticsEvent != null) { add(AnalyticsChain(analyticsEvent)) } - add(CheckForUnfinishedSaltPayBackupChain(backupService)) add(DisclaimerChain(store, disclaimerWillShow)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage)) } @@ -91,9 +89,7 @@ internal object UseCaseScanProcessor { navigateTo(exception.onboardingRoute) onWalletNotCreated() } - is ScanChainException.PutSaltPayVisaCard, is ScanChainException.DisclaimerWasCanceled, - is ScanChainException.CheckForSaltPayOnboardingCaseException, -> onFailure(scanCardExceptionConverter.convertBack(exception)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index 21d0e75290..ce46f2f527 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -3,12 +3,9 @@ package com.tangem.tap.domain.scanCard.chains import arrow.core.Either import arrow.core.left import arrow.core.right -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.ScanCardException -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.core.chain.Chain import com.tangem.domain.models.scan.ScanResponse @@ -21,13 +18,8 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import kotlinx.coroutines.delay import org.rekotlin.Store @@ -35,9 +27,7 @@ import org.rekotlin.Store * Handles the verification process to determine if the scanned card requires onboarding. * * Returns: - * - [ScanChainException.OnboardingNeeded] if onboarding required for none SaltPay cards. - * - [ScanChainException.CheckForSaltPayOnboardingCaseException] if unable to check for SaltPay onboarding case. - * - [ScanChainException.PutSaltPayVisaCard] if scanned SaltPay card is not activated and onboarding required. + * - [ScanChainException.OnboardingNeeded] if onboarding required. * * @param store the [Store] that holds the state of the app. * @param tapWalletManager manager responsible for handling operations related to the Wallet. @@ -54,54 +44,10 @@ class CheckForOnboardingChain( override suspend fun invoke( previousChainResult: ScanResponse, ): Either { - val cardTypesResolver = previousChainResult.cardTypesResolver - tapWalletManager.updateConfigManager(previousChainResult) store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult)) return when { - cardTypesResolver.isSaltPayVisa() -> { - val manager = SaltPayActivationManagerFactory( - blockchain = previousChainResult.cardTypesResolver.getBlockchain(), - card = previousChainResult.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(previousChainResult, manager) - delay(timeMillis = 500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(previousChainResult) - val isOnboardingCase = result.data - if (isOnboardingCase) { - store.dispatch( - GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = false), - ) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - ScanChainException.OnboardingNeeded(AppScreen.OnboardingWallet).left() - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - previousChainResult.right() - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - ScanChainException.CheckForSaltPayOnboardingCaseException(result.error).left() - } - } - } - } - cardTypesResolver.isSaltPayWallet() -> { - delay(DELAY_SDK_DIALOG_CLOSE) - if (previousChainResult.card.backupStatus?.isActive == false) { - SaltPayExceptionHandler.handle(SaltPayActivationError.PutVisaCard) - ScanChainException.PutSaltPayVisaCard.left() - } else { - Analytics.setContext(previousChainResult) - previousChainResult.right() - } - } OnboardingHelper.isOnboardingCase(previousChainResult) -> { Analytics.addContext(previousChainResult) store.dispatchOnMain(GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = true)) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt deleted file mode 100644 index f01f9f76d7..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.tap.domain.scanCard.chains - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.domain.card.ScanCardException -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.core.chain.Chain -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa - -/** - * Handles the verification for unfinished SaltPay backup processes after the card scanning operation. - * - * Returns [ScanChainException.PutSaltPayVisaCard] if backup is not finished. - * - * @param backupService a service responsible for managing backup operations, used here to check for unfinished backups - * and verify the primary card type. - * - * @see Chain for more information about the Chain interface. - */ -internal class CheckForUnfinishedSaltPayBackupChain( - private val backupService: BackupService, -) : Chain { - - override suspend fun invoke( - previousChainResult: ScanResponse, - ): Either { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - return previousChainResult.right() - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == previousChainResult.card.cardId } - ?: false - - return if (previousChainResult.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - ScanChainException.PutSaltPayVisaCard.left() - } else { - previousChainResult.right() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt index 626465c7ee..6709cb8966 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt @@ -5,11 +5,6 @@ import com.tangem.tap.common.redux.navigation.AppScreen sealed class ScanChainException : ScanCardException.ChainException() { - /** - * May be returned from [CheckForOnboardingChain] and [CheckForUnfinishedSaltPayBackupChain] - * */ - object PutSaltPayVisaCard : ScanChainException() - /** * May be returned from [DisclaimerChain] * */ @@ -21,11 +16,4 @@ sealed class ScanChainException : ScanCardException.ChainException() { * @param onboardingRoute route where to navigate * */ data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException() - - /** - * May be returned from [CheckForOnboardingChain] - * - * @param cause cause of exception - * */ - data class CheckForSaltPayOnboardingCaseException(override val cause: Throwable?) : ScanChainException() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt index f04df13017..53062e6918 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt @@ -4,7 +4,6 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.domain.card.ScanCardException import com.tangem.tap.domain.scanCard.chains.ScanChainException -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.utils.converter.TwoWayConverter internal class ScanCardExceptionConverter : TwoWayConverter { @@ -35,10 +34,6 @@ internal class ScanCardExceptionConverter : TwoWayConverter TangemSdkError.UserCancelled() - is ScanChainException.PutSaltPayVisaCard -> TangemSdkError.ExceptionError( - SaltPayActivationError.PutVisaCard, - ) - is ScanChainException.CheckForSaltPayOnboardingCaseException, is ScanChainException.OnboardingNeeded, null, -> TangemSdkError.ExceptionError(e?.cause) 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 821e82be32..69a78dfb46 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 @@ -20,7 +20,6 @@ import com.tangem.crypto.CryptoUtils import com.tangem.crypto.hdWallet.DerivationPath 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.isTangemTwins import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation @@ -189,9 +188,7 @@ private class ScanWalletProcessor( val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) @Suppress("ComplexCondition") - if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && - (activationInProgress || card.isSaltPay) - ) { + if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && activationInProgress) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { is CompletionResult.Success -> { @@ -213,10 +210,7 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val productType = when (card.isSaltPay) { - true -> ProductType.SaltPay - else -> ProductType.Wallet - } + val productType = ProductType.Wallet scope.launch { val derivations = collectDerivations(card) if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt index 71415134a2..9d218c840f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.userWalletList import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.attestation.OnlineCardVerifier @@ -43,7 +42,6 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV return when { cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL - SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL else -> when (TwinsHelper.getTwinCardNumber(cardId)) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 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 6b2aa5ccff..e276bd51db 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 @@ -2,9 +2,7 @@ package com.tangem.tap.domain.walletStores.repository.implementation import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionHistoryProvider import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.Result.Failure @@ -35,7 +33,6 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithTxHistories import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage @@ -248,11 +245,6 @@ internal class DefaultWalletAmountsRepository( TestActions.testAmountInjectionForWalletManagerEnabled = false } else { walletManager.update() - - val wallet = walletManager.wallet - if (wallet.blockchain == Blockchain.SaltPay && walletManager is TransactionHistoryProvider) { - walletManager.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens()) - } } } @@ -349,7 +341,7 @@ internal class DefaultWalletAmountsRepository( it.updateWithDemoAmounts(wallet = updatedWallet) } else { it.updateWithAmounts(wallet = updatedWallet) - }.updateWithTxHistories(wallet = updatedWallet) + } }, ) } 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 396329ad4f..85fd9d5c8e 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 @@ -5,8 +5,6 @@ import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType import com.tangem.common.core.TangemError -import com.tangem.tap.common.extensions.getBlockchainTxHistory -import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.WalletDataModel @@ -28,21 +26,6 @@ internal fun List.updateWithFiatRates(fiatRates: Map wallet.getBlockchainTxHistory() - is Currency.Token -> wallet.getTokenTxHistory(currency.token) - }, - ) -} - -internal fun List.updateWithTxHistories(wallet: Wallet): List { - return this.map { walletData -> - walletData.updateWithTxHistory(wallet) - } -} - internal fun List.updateWithAmounts(wallet: Wallet): List { return this.map { walletData -> walletData.updateWithAmount(wallet) 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 627c5f93d3..069f77fe4b 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 @@ -40,12 +40,6 @@ internal fun WalletStoreModel.updateWithError(wallet: Wallet, error: TangemError ) } -internal fun WalletStoreModel.updateWithTxHistories(wallet: Wallet): WalletStoreModel { - return this.copy( - walletsData = walletsData.updateWithTxHistories(wallet = wallet), - ) -} - internal fun WalletStoreModel.updateWithAmounts(wallet: Wallet): WalletStoreModel { return this.copy( walletsData = walletsData.updateWithAmounts(wallet = wallet), 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 85e80df897..e28b9cd398 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 @@ -98,7 +98,7 @@ private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesRe } } val allowedSecurityOptions = when { - cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() || cardTypesResolver.isSaltPay() -> { + cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> { EnumSet.of(SecurityOption.LongTap) } card.settings.isBackupAllowed -> { @@ -116,7 +116,7 @@ private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesRe private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean { val hasPermanentWallet = card.wallets.any { it.settings.isPermanent } - val isNotAllowed = hasPermanentWallet || cardTypesResolver.isSaltPay() || cardTypesResolver.isStart2Coin() + val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin() return !isNotAllowed } 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 10edfda464..e9766c6147 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 @@ -32,13 +32,8 @@ class DetailsViewModel(private val store: Store) { SettingsElement.WalletConnect -> { if (cardTypesResolver?.isMultiwalletAllowed() == true) it else null } - SettingsElement.SendFeedback -> - if (cardTypesResolver?.isSaltPay() != true) it else null - SettingsElement.LinkMoreCards -> { - // if (state.createBackupAllowed) it else null - // TODO: SaltPay: temporary excluding backup process for Visa cards - if (state.createBackupAllowed && cardTypesResolver?.isSaltPay() != true) it else null - } + SettingsElement.SendFeedback -> it + SettingsElement.LinkMoreCards -> if (state.createBackupAllowed) it else null SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null } 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 index 27dd88ee13..6f09751342 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -40,12 +40,6 @@ class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(da 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())) 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 index 56519de4ff..b01b2b6f60 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.disclaimer -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.models.scan.CardDTO import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage @@ -12,14 +11,12 @@ import java.util.Locale */ enum class DisclaimerType { Tangem, - SaltPay, Start2Coin, ; companion object { fun get(cardDTO: CardDTO): DisclaimerType { return when { - cardDTO.isSaltPay -> SaltPay cardDTO.isStart2Coin -> Start2Coin else -> Tangem } @@ -31,7 +28,6 @@ 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) } } 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 b9e5db5876..6079110e39 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 @@ -62,7 +62,6 @@ object OnboardingHelper { AppScreen.OnboardingOther } ProductType.Twins -> AppScreen.OnboardingTwins - ProductType.SaltPay -> AppScreen.OnboardingWallet ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( "Onboarding for Start2Coin cards is not supported", ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt deleted file mode 100644 index ab267f9dda..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.tangem.tap.features.onboarding - -import com.tangem.common.services.Result -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.common.extensions.successOr -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.update -import com.tangem.tap.preferencesStorage - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayHelper { - - suspend fun isOnboardingCase(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result { - return try { - val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - - val cardStorage = preferencesStorage.usedCardsPrefStorage - val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) { - cardStorage.activationFinished(scanResponse.card.cardId) - } - val isActivationCase = updatedStep != SaltPayActivationStep.Success - val isBackupCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isActivationCase || isBackupCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayActivationError) { - Result.Failure(error) - } - } - - @Suppress("UnusedPrivateMember") - fun testProceedToOnboarding(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result = - Result.Success(true) - - @Suppress("UnusedPrivateMember") - fun testProceedToMainScreen(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result = - Result.Success(false) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 227cdb626d..c021c9fad5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -10,8 +10,6 @@ import org.rekotlin.Action sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() object GetToCreateWalletStep : OnboardingWalletAction() - object GetToSaltPayStep : OnboardingWalletAction() - object CreateWallet : OnboardingWalletAction() data class WalletWasCreated( val result: CompletionResult, @@ -57,7 +55,7 @@ sealed class BackupAction : Action { object ScanPrimaryCard : BackupAction() /** - * Check for unfinished backup of standard Wallets and SaltPay cards + * Check for unfinished backup of standard Wallets * See more GlobalAction.Onboarding.StartForUnfinishedBackup */ object CheckForUnfinishedBackup : BackupAction() @@ -82,9 +80,6 @@ sealed class BackupAction : Action { data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction() data class WriteBackupCard(val cardNumber: Int) : BackupAction() - /** - * It always calls for the SaltPay cards when resuming the activation process. - */ data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction() object DiscardBackup : BackupAction() 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 ce33520089..2f4be5416d 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 @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.ifNotNull import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO @@ -19,7 +18,6 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -30,8 +28,6 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.Artwork import kotlinx.coroutines.launch @@ -70,7 +66,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: when (action) { OnboardingWalletAction.Init -> { ifNotNull(onboardingManager, card) { manager, card -> - if (!card.isSaltPay && !manager.isActivationStarted(card.cardId)) { + if (!manager.isActivationStarted(card.cardId)) { Analytics.send(Onboarding.Started()) } } @@ -83,20 +79,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: store.dispatch(OnboardingWalletAction.ResumeBackup) } card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> { - when { - // check for unfinished backup for saltPay cards. See more - scanResponse.cardTypesResolver.isSaltPay() && backupService.hasIncompletedBackup -> { - store.dispatch(OnboardingWalletAction.ResumeBackup) - } - - scanResponse.cardTypesResolver.isSaltPay() -> { - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - store.dispatch(BackupAction.FinishBackup(withAnalytics = false)) - store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) - } - - else -> store.dispatch(BackupAction.FinishBackup()) - } + store.dispatch(BackupAction.FinishBackup()) } else -> { store.dispatch(OnboardingWalletAction.GetToCreateWalletStep) @@ -180,12 +163,8 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: if (onboardingWalletState.backupState.backupStep == BackupStep.InitBackup || onboardingWalletState.backupState.backupStep == BackupStep.Finished ) { - if (onboardingWalletState.isSaltPay) { - BackupAction.StartBackup - } else { - Analytics.send(Onboarding.Backup.ScreenOpened()) - BackupAction.IntroduceBackup - } + Analytics.send(Onboarding.Backup.ScreenOpened()) + BackupAction.IntroduceBackup } else { null } @@ -348,11 +327,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.AddBackupCard -> { - onboardingWalletState.backupCardIdFilter?.let { - backupService.skipCompatibilityChecks = true - tangemSdk.config.filter.cardIdFilter = it - } - backupService.addBackupCard { result -> backupService.skipCompatibilityChecks = false tangemSdk.config.filter.cardIdFilter = null @@ -400,7 +374,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (action.accessCodeConfirmation == backupState.accessCode) { store.dispatch(BackupAction.SetAccessCodeError(null)) backupService.setAccessCode(action.accessCodeConfirmation) - store.dispatch(OnboardingSaltPayAction.SetAccessCode(action.accessCodeConfirmation)) store.dispatch(BackupAction.PrepareToWritePrimaryCard) } else { store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodesDoNotMatch)) @@ -421,7 +394,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (result) { is CompletionResult.Success -> { if (backupService.currentState == BackupService.State.Finished) { - initSaltPayOnBackupFinishedIfNeeded(scanResponse, onboardingWalletState) store.dispatchOnMain(BackupAction.FinishBackup()) } else { store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1)) @@ -450,14 +422,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.dispatch( GlobalAction.Onboarding.StartForUnfinishedBackup( addedBackupCardsCount = backupService.addedBackupCardsCount, - isSaltPayVisa = backupService.primaryCardIsSaltPayVisa(), ), ) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.SkipBackup -> { Analytics.send(Onboarding.Backup.Skipped()) - if (onboardingWalletState.isSaltPay) return Analytics.send(Onboarding.Finished()) finishCardActivation(gatherCardIds(backupState, card)) @@ -466,7 +436,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (action.withAnalytics) { Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) } - if (onboardingWalletState.isSaltPay) return val notActivatedCardIds = gatherCardIds(backupState, card) .mapNotNull { if (cardActivationIsFinished(it)) null else it } @@ -492,7 +461,6 @@ private fun cardActivationIsFinished(cardId: String): Boolean { /** * Standard Wallet cards start activation at OnboardingWalletAction.CreateWallet - * SaltPay cards start activation at OnboardingWalletAction.StartSaltPayCardActivation */ private fun startCardActivation(scanResponse: ScanResponse) { preferencesStorage.usedCardsPrefStorage.activationStarted(scanResponse.card.cardId) @@ -500,7 +468,6 @@ private fun startCardActivation(scanResponse: ScanResponse) { /** * Standard Wallet cards finish activation at BackupAction.SkipBackup and BackupAction.FinishBackup - * SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding */ internal fun finishCardActivation(cardIds: List) { cardIds.forEach { cardId -> @@ -514,31 +481,10 @@ internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List { - if (state.isSaltPay) { - showInterruptOnboardingDialog() - } else { - OnboardingHelper.onInterrupted() - store.dispatch(NavigationAction.PopBackTo()) - } + OnboardingHelper.onInterrupted() + store.dispatch(NavigationAction.PopBackTo()) } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 30b94a73c6..077e4b82aa 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -1,11 +1,8 @@ package com.tangem.tap.features.onboarding.products.wallet.redux -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.tap.backupService import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayReducer import org.rekotlin.Action object OnboardingWalletReducer { @@ -17,15 +14,11 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet return when (action) { is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state) - is OnboardingSaltPayAction -> OnboardingSaltPayReducer.reduce(action, state) - is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState, state.isSaltPay)) + is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState)) is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state) is OnboardingWalletAction.GetToCreateWalletStep -> state.copy( step = OnboardingWalletStep.CreateWallet, ) - is OnboardingWalletAction.GetToSaltPayStep -> state.copy( - step = OnboardingWalletStep.SaltPay, - ) is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup) is OnboardingWalletAction.SetArtworkUrl -> { state.copy(cardArtworkUri = action.artworkUri) @@ -36,16 +29,17 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet } private object ReducerForGlobalAction { + + private const val MAX_BACKUP_CARDS = 2 + fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { return when (action) { is GlobalAction.Onboarding.Start -> { - val isSaltPay = action.scanResponse.cardTypesResolver.isSaltPay() OnboardingWalletState( backupState = state.backupState.copy( - maxBackupCards = maxBackupCards(isSaltPay), - canSkipBackup = if (isSaltPay) false else action.canSkipBackup, + maxBackupCards = MAX_BACKUP_CARDS, + canSkipBackup = action.canSkipBackup, ), - isSaltPay = isSaltPay, ) } is GlobalAction.Onboarding.StartForUnfinishedBackup -> { @@ -55,14 +49,11 @@ private object ReducerForGlobalAction { canSkipBackup = false, isInterruptedBackup = true, ), - isSaltPay = action.isSaltPayVisa, ) } else -> state } } - - private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2 } private object OnboardingWallet2Reducer { @@ -78,11 +69,11 @@ private object OnboardingWallet2Reducer { private object BackupReducer { @Suppress("ComplexMethod") - fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState { + fun reduce(action: BackupAction, state: BackupState): BackupState { return when (action) { is BackupAction.IntroduceBackup -> BackupState( backupStep = BackupStep.InitBackup, - maxBackupCards = if (isSaltPay) 1 else 2, + maxBackupCards = 2, canSkipBackup = state.canSkipBackup, ) BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 94ce0d1f54..cc59a4fb61 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -2,11 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap import android.net.Uri -import com.tangem.common.CardFilter -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep import org.rekotlin.StateType /** @@ -16,32 +12,21 @@ data class OnboardingWalletState( val step: OnboardingWalletStep = OnboardingWalletStep.None, val wallet2State: OnboardingWallet2State? = null, val backupState: BackupState = BackupState(), - val onboardingSaltPayState: OnboardingSaltPayState? = null, - val isSaltPay: Boolean = false, val cardArtworkUri: Uri? = null, val showConfetti: Boolean = false, ) : StateType { - val backupCardIdFilter: CardFilter.Companion.CardIdFilter? - get() = when { - isSaltPay -> CardFilter.Companion.CardIdFilter.Allow( - items = SaltPayWorkaround.walletCardIds.toSet(), - ranges = SaltPayWorkaround.walletCardIdRanges, - ) - else -> null - } - @Suppress("MagicNumber") fun getMaxProgress(): Int { - val baseProgress = if (isSaltPay) 12 else 6 + val baseProgress = 6 return getWallet2Progress() + baseProgress } @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { - val progressByStep = when { - step == OnboardingWalletStep.CreateWallet -> 1 - step == OnboardingWalletStep.Backup -> { + val progressByStep = when (step) { + OnboardingWalletStep.CreateWallet -> 1 + OnboardingWalletStep.Backup -> { when (backupState.backupStep) { BackupStep.InitBackup -> 2 BackupStep.ScanOriginCard -> 3 @@ -53,21 +38,7 @@ data class OnboardingWalletState( BackupStep.Finished -> getMaxProgress() } } - step == OnboardingWalletStep.SaltPay && isSaltPay -> { - when (onboardingSaltPayState!!.step) { - SaltPayActivationStep.None, SaltPayActivationStep.NoGas -> 0 - SaltPayActivationStep.NeedPin -> 7 - SaltPayActivationStep.CardRegistration -> 8 - SaltPayActivationStep.KycIntro, SaltPayActivationStep.KycStart, - SaltPayActivationStep.KycWaiting, SaltPayActivationStep.KycReject, - -> 9 - SaltPayActivationStep.Claim -> 10 - SaltPayActivationStep.ClaimInProgress -> 11 - SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, - -> getMaxProgress() - } - } - step == OnboardingWalletStep.Done -> getMaxProgress() + OnboardingWalletStep.Done -> getMaxProgress() else -> 1 } @@ -82,7 +53,7 @@ data class OnboardingWallet2State( ) enum class OnboardingWalletStep { - None, CreateWallet, SaltPay, Backup, Done + None, CreateWallet, Backup, Done } data class BackupState( 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 deleted file mode 100644 index b5dfe53817..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ /dev/null @@ -1,230 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.blockchains.ethereum.CompiledEthereumTransaction -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionSigner -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.extensions.Result -import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.extensions.guard -import com.tangem.domain.common.extensions.successOr -import com.tangem.tap.common.extensions.isPositive -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.stub -import com.tangem.tap.domain.getFirstToken -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode -import java.util.concurrent.atomic.AtomicLong - -/** -[REDACTED_AUTHOR] - */ -class GnosisRegistrator( - val walletManager: EthereumWalletManager, -) { - - private val walletAddress = walletManager.wallet.address - private val blockchain = walletManager.wallet.blockchain - private val token = walletManager.wallet.getFirstToken().guard { - throw NullPointerException("WalletManager for GnosisRegistrator must contain token") - } - - private val otpProcessorContractAddress: String = when (blockchain) { - Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522" - else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") - } - - private val addressTreasureSafe = "0x24A3c2382497075b6D93258f5938f7B661c06318" - - private val atomicNonce = AtomicLong() - - suspend fun checkHasGas(): Result { - // return Result.Success(true) // test - val wallet = walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - - val hasGas = wallet.amounts[AmountType.Coin]?.value?.isPositive() ?: false - return Result.Success(hasGas) - } - - suspend fun prepareBeforeMakingTxs(): Result { - walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - atomicNonce.set(walletManager.txCount) - - return Result.Success(Unit) - } - - suspend fun sendTransactions(transactions: List): Result> { - val results = transactions.map { - coroutineScope { async { walletManager.sendRaw(it.compiledTransaction, it.signature) } } - }.awaitAll() - - val errors = results.filterIsInstance() - return if (errors.isEmpty()) { - Result.Success(listOf()) - } else { - Result.Failure(errors[0].error) - } - } - - suspend fun makeSetSpendLimitTx(value: BigDecimal): Result { - val limitAmount = Amount(walletManager.cardTokens.first(), value) - val feeAmount = walletManager.getFeeToSetSpendLimit(otpProcessorContractAddress, limitAmount) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildSetSpendLimitToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - amount = limitAmount, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToSetSpendLimit, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'setSpendLimit' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeInitOtpTx(rootOTP: ByteArray, rootOTPCounter: Int): Result { - val feeAmount = walletManager.getFeeToInitOTP(otpProcessorContractAddress, rootOTP, rootOTPCounter) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildInitOTPToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - otp = rootOTP, - otpCounter = rootOTPCounter, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToInitOTP, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'initOtp' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeSetWalletTx(): Result { - val feeAmount = walletManager.getFeeToSetWallet(otpProcessorContractAddress) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildSetWalletToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToSetWallet, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'setWallet' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeApprovalTx(value: BigDecimal): Result { - val approveAmount = Amount(token, value) - val feeAmount = walletManager.getFeeToApprove(approveAmount, otpProcessorContractAddress) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign( - transactionData = walletManager.createTransaction( - amount = approveAmount, - fee = feeAmount, - destination = otpProcessorContractAddress, - ), - nonce = atomicNonce.getAndIncrement().toBigInteger(), - gasLimit = walletManager.gasLimitToApprove, - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'approval' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun getAllowance(): Result { - // return Result.Success(Amount(BigDecimal(0.1), walletManager.wallet.blockchain)) // test - return walletManager.getAllowance(addressTreasureSafe, token) - } - - suspend fun transferFrom(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - if (walletManager.txCount == -1L) { - walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - } - - // return transferFromStandardWay(amountToClaim, signer) - return transferFromHardcodeWay(amountToClaim, signer) - } - - @Suppress("UnusedPrivateMember") - private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - val amount = Amount(token, amountToClaim) - val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe) - .extractFeeAmount().successOr { return it } - - val transactionData = walletManager.createTransferFromTransaction(amount, feeAmount, addressTreasureSafe) - walletManager.transferFrom(transactionData, signer).successOr { return Result.Failure(it.error) } - - return Result.Success(Unit) - } - - private suspend fun transferFromHardcodeWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - val amount = Amount(token, amountToClaim) - val gasPrice = walletManager.getGasPrice().successOr { return it } - - val hardcodeGasLimit = BigInteger("300000") - val hardcodeFeeValue = (hardcodeGasLimit * gasPrice).toBigDecimal( - scale = blockchain.decimals(), - mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), - ).movePointLeft(blockchain.decimals()) - val hardcodeFeeAmount = Amount(hardcodeFeeValue, blockchain) - - val transactionData = walletManager.createTransferFromTransaction( - amount = amount, - fee = hardcodeFeeAmount, - source = addressTreasureSafe, - ) - val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign( - transactionData, - walletManager.txCount.toBigInteger(), - hardcodeGasLimit, - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Not enough data")) - - return when (val result = walletManager.signAndSend(transactionToSign, signer)) { - SimpleResult.Success -> Result.Success(Unit) - is SimpleResult.Failure -> Result.Failure(result.error) - } - } - - @Suppress("MagicNumber") - private fun Result>.extractFeeAmount(): Result { - return when (this) { - is Result.Success -> { - if (this.data.size != 3) { - Result.Failure(BlockchainSdkError.FailedToLoadFee) - } else { - Result.Success(this.data[1]) - } - } - is Result.Failure -> this - } - } - - private fun Long?.toBigInteger(): BigInteger? = this?.let { BigInteger.valueOf(it) } - - private fun Long.toBigInteger(): BigInteger = BigInteger.valueOf(this) - - companion object { - fun stub(): GnosisRegistrator { - return GnosisRegistrator(WalletManager.stub()) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt deleted file mode 100644 index fd359c25a4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.common.Filter - -/** -[REDACTED_AUTHOR] - */ -interface PinFilter : Filter - -class AllSymbolsTheSameFilter : PinFilter { - override fun filter(value: String): Boolean { - if (value.isEmpty()) return false - - val array = value.toCharArray() - val firstSymbol = array[0] - return array.all { it == firstSymbol } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt deleted file mode 100644 index 2f7f824f6c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ /dev/null @@ -1,341 +0,0 @@ -@file:Suppress("MaximumLineLength") - -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import android.net.Uri -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.TransactionSigner -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.isZero -import com.tangem.common.extensions.toHexString -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.datasource.api.paymentology.PaymentologyApiService -import com.tangem.datasource.api.paymentology.models.request.CheckRegistrationRequests -import com.tangem.datasource.api.paymentology.models.request.RegisterKYCRequest -import com.tangem.datasource.api.paymentology.models.request.RegisterWalletRequest -import com.tangem.datasource.api.paymentology.models.response.AttestationResponse -import com.tangem.datasource.api.paymentology.models.response.RegisterWalletResponse -import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse -import com.tangem.datasource.api.paymentology.models.response.tryExtractError -import com.tangem.datasource.config.models.KYCProvider -import com.tangem.datasource.config.models.SaltPayConfig -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.domain.common.extensions.successOr -import com.tangem.operations.attestation.AttestWalletKeyResponse -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.store -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.math.BigDecimal -import kotlin.ByteArray -import kotlin.Exception -import kotlin.IllegalStateException -import kotlin.NullPointerException -import kotlin.String -import kotlin.Suppress -import kotlin.Unit -import kotlin.UnsupportedOperationException -import kotlin.byteArrayOf -import kotlin.minus -import com.tangem.blockchain.extensions.Result as BlockchainResult - -/** -[REDACTED_AUTHOR] - */ -class SaltPayActivationManager( - val cardId: String, - val cardPublicKey: ByteArray, - private val kycProvider: KYCProvider, - private val paymentologyService: PaymentologyApiService, - private val gnosisRegistrator: GnosisRegistrator, -) { - - val walletPublicKey = gnosisRegistrator.walletManager.wallet.publicKey.seedKey - val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) - - private val approvalValue: BigDecimal = BigDecimal(2).pow(256).minus(BigDecimal.ONE) - .movePointLeft(gnosisRegistrator.walletManager.wallet.blockchain.decimals()) - - private val spendLimitValue: BigDecimal = BigDecimal("100") - - suspend fun checkHasGas(): Result { - return when (val hasGasResult = gnosisRegistrator.checkHasGas()) { - is BlockchainResult.Success -> if (hasGasResult.data) { - Result.Success(Unit) - } else { - Result.Failure(SaltPayActivationError.NoGas) - } - is BlockchainResult.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) - } - } - - suspend fun registerKYC(): Result { - val result = withContext(Dispatchers.IO) { - performRequest { paymentologyService.api.registerKYC(makeRegisterKYCRequest()) } - } - return when (result) { - is Result.Success -> Result.Success(Unit) - is Result.Failure -> result - } - } - - suspend fun checkRegistration(): Result { - val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) - val request = CheckRegistrationRequests(listOf(requestItem)) - - val response: RegistrationResponse = withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.checkRegistration(request) - } - } - .successOr { return it } - .tryExtractError() - .successOr { return it } - - return try { - if (response.results.isEmpty()) throw SaltPayActivationError.EmptyResponse - - val item = response.results[0] - if (item.error != null) throw IllegalStateException(response.makeErrorMessage()) - - Result.Success(item) - } catch (ex: Exception) { - Result.Failure(ex) - } - } - - suspend fun requestAttestationChallenge(): Result { - val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) - return withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.requestAttestationChallenge(requestItem) - } - } - .successOr { return it } - .tryExtractError() - } - - suspend fun sendTransactions(signedTransactions: List): BlockchainResult> { - return gnosisRegistrator.sendTransactions(signedTransactions) - } - - suspend fun registerWallet( - attestResponse: AttestWalletKeyResponse, - pinCode: String, - ): Result { - val request = RegisterWalletRequest( - cardId = cardId, - publicKey = cardPublicKey, - walletPublicKey = walletPublicKey, - walletSalt = attestResponse.salt, - walletSignature = attestResponse.walletSignature, - cardSalt = attestResponse.publicKeySalt ?: byteArrayOf(), - cardSignature = attestResponse.cardSignature ?: byteArrayOf(), - pin = pinCode, - ) - return withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.registerWallet(request) - } - } - .successOr { return it } - .tryExtractError() - } - - suspend fun getAmountToClaim(): Result { - val amount = gnosisRegistrator.getAllowance().successOr { - return Result.Failure(SaltPayActivationError.FailedToGetFundsToClaim) - } - - return if (amount.value == null || amount.value?.isZero() == true) { - Result.Failure(SaltPayActivationError.NoFundsToClaim) - } else { - Result.Success(amount) - } - } - - suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { - val userCancelledError = (it.error as? BlockchainSdkError.WrappedTangemError) - ?.tangemError as? TangemSdkError.UserCancelled - - return when (userCancelledError) { - null -> Result.Failure(SaltPayActivationError.ClaimTransactionFailed) - else -> Result.Failure(userCancelledError) - } - } - return Result.Success(Unit) - } - - suspend fun getTokenAmount(): Result { - val wallet = gnosisRegistrator.walletManager.safeUpdate().successOr { return it } - val token = wallet.getFirstToken().guard { - throw UnsupportedOperationException() - } - - val amountValue = wallet.amounts[AmountType.Token(token)]?.value.guard { - throw UnsupportedOperationException() - } - - return Result.Success(amountValue) - } - - fun makeRegistrationTask(challenge: ByteArray): SaltPayRegistrationTask { - return SaltPayRegistrationTask( - gnosisRegistrator = gnosisRegistrator, - challenge = challenge, - walletPublicKey = walletPublicKey, - approvalValue = approvalValue, - spendLimitValue = spendLimitValue, - ) - } - - private fun makeRegisterKYCRequest(): RegisterKYCRequest = RegisterKYCRequest( - cardId = cardId, - publicKey = cardPublicKey, - kycProvider = "UTORG", - kycRefId = kycUrlProvider.kycRefId, - ) - - companion object { - fun stub(): SaltPayActivationManager = SaltPayActivationManager( - kycProvider = SaltPayConfig.stub().kycProvider, - paymentologyService = PaymentologyApiService, - gnosisRegistrator = GnosisRegistrator.stub(), - cardId = "", - cardPublicKey = byteArrayOf(), - ) - } -} - -class KYCUrlProvider( - walletPublicKey: ByteArray, - kycProvider: KYCProvider, -) { - - val kycRefId = UserWalletIdBuilder.walletPublicKey(walletPublicKey).stringValue - - val requestUrl: String = makeWebRequestUrl(kycProvider) - - val doneUrl = "https://success.tangem.com/" - - private fun makeWebRequestUrl(kycProvider: KYCProvider): String { - val baseUri = Uri.parse(kycProvider.baseUrl) - return Uri.Builder() - .scheme(baseUri.scheme) - .authority(baseUri.authority) - .encodedPath(baseUri.path) - .appendQueryParameter(kycProvider.sidParameterKey, kycProvider.sidValue) - .appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId) - .build().toString() - } -} - -class SaltPayActivationManagerFactory( - private val blockchain: Blockchain, - private val card: CardDTO, -) { - - fun create(): SaltPayActivationManager { - val wallet = card.wallets.first() - val saltPayConfig = store.state.globalState.configManager?.config?.saltPayConfig.guard { - throw NullPointerException("SaltPayConfig is not initialized") - } - val wmFactory = store.state.globalState.tapWalletManager.walletManagerFactory - val paymentologyService = store.state.domainNetworks.paymentologyService - - // return createDummyActivationManager(saltPayConfig, wmFactory, paymentologyService) - return createActivationPayManager( - cardId = card.cardId, - cardPublicKey = card.cardPublicKey, - kycProvider = saltPayConfig.kycProvider, - gnosisRegistrator = GnosisRegistrator( - walletManager = makeSaltPayWalletManager( - blockchain = blockchain, - walletPublicKey = wallet.publicKey, - wmFactory = wmFactory, - ), - ), - paymentologyService = paymentologyService, - ) - } - - private fun makeSaltPayWalletManager( - blockchain: Blockchain, - walletPublicKey: ByteArray, - wmFactory: WalletManagerFactory, - ): EthereumWalletManager { - return wmFactory.makeWalletManager( - blockchain = blockchain, - publicKey = Wallet.PublicKey(walletPublicKey, null, null), - tokens = listOf(SaltPayWorkaround.tokenFrom(blockchain)), - ) as EthereumWalletManager - } - - private fun createActivationPayManager( - cardId: String, - cardPublicKey: ByteArray, - kycProvider: KYCProvider, - gnosisRegistrator: GnosisRegistrator, - paymentologyService: PaymentologyApiService, - ): SaltPayActivationManager { - return SaltPayActivationManager( - cardId = cardId, - cardPublicKey = cardPublicKey, - kycProvider = kycProvider, - paymentologyService = paymentologyService, - gnosisRegistrator = gnosisRegistrator, - ) - } - - @Suppress("UnusedPrivateMember") - private fun createDummyActivationManager( - saltPayConfig: SaltPayConfig, - wmFactory: WalletManagerFactory, - paymentologyService: PaymentologyApiService, - ): SaltPayActivationManager { - val fakeSource = DummySaltPayCardSource() - - val walletManager = wmFactory.makeWalletManager( - blockchain = fakeSource.blockchain, - publicKey = fakeSource.blockchainPublicKey, - tokens = listOf(fakeSource.token), - ) as EthereumWalletManager - - return createActivationPayManager( - cardId = fakeSource.cardId, - cardPublicKey = fakeSource.cardPublicKey, - kycProvider = saltPayConfig.kycProvider, - gnosisRegistrator = GnosisRegistrator(walletManager), - paymentologyService = paymentologyService, - ) - } - - private data class DummySaltPayCardSource( - val blockchain: Blockchain = Blockchain.SaltPay, - val cardId: String = "FF03000001001057", - private val cardPublicKeyString: String = "039C2D2F9B68003766BFC29766761F55F4084E48B8B012BF439E115A79AB122D48", - private val walletPublicKeyString: String = "0399B2DF5B129FBF69097DAA7C44FB5849E578CCFC22228A409870C74B56C4C3D1", - ) { - val cardPublicKey: ByteArray = cardPublicKeyString.hexToBytes() - val walletPublicKey: ByteArray = walletPublicKeyString.hexToBytes() - val blockchainPublicKey = Wallet.PublicKey(walletPublicKey, null, null) - val token: Token = SaltPayWorkaround.tokenFrom(blockchain) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt deleted file mode 100644 index 8d116a7385..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.common.core.TangemSdkError -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.redux.AppDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.store - -/** -[REDACTED_AUTHOR] - */ -object SaltPayExceptionHandler { - - fun handle(throwable: Throwable) { - when (throwable) { - is SaltPayActivationError -> { - val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> { - Analytics.send(Onboarding.NotEnoughGasError()) - SaltPayDialog.Activation.NoGas - } - is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard - is SaltPayActivationError.CardNotPassed -> { - Analytics.send(Onboarding.CardNotPassedError()) - SaltPayDialog.Activation.OnError(throwable) - } - else -> SaltPayDialog.Activation.OnError(throwable) - } - store.dispatchDialogShow(dialog) - } - is TangemSdkError -> { - when (throwable) { - is TangemSdkError.NetworkError -> { - val message = throwable.customMessage - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - else -> Unit - } - } - is BlockchainSdkError -> { - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) - } - else -> { - val message = throwable.localizedMessage ?: "SaltPay unknown error" - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt deleted file mode 100644 index fb4bd50ed9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.blockchains.ethereum.CompiledEthereumTransaction -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.CompletionResult -import com.tangem.common.core.CardSession -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.CompletionCallback -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.operations.CommandResponse -import com.tangem.operations.GenerateOTPCommand -import com.tangem.operations.GenerateOTPResponse -import com.tangem.operations.attestation.AttestWalletKeyCommand -import com.tangem.operations.attestation.AttestWalletKeyResponse -import com.tangem.operations.sign.SignHashesCommand -import com.tangem.tap.scope -import kotlinx.coroutines.launch -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class RegistrationTaskResponse( - val signedTransactions: List, - val attestResponse: AttestWalletKeyResponse, -) : CommandResponse - -class SaltPayRegistrationTask( - private var gnosisRegistrator: GnosisRegistrator, - private var challenge: ByteArray, - private val walletPublicKey: ByteArray, - private val approvalValue: BigDecimal, - private val spendLimitValue: BigDecimal, -) : CardSessionRunnable { - - private var generateOTPResponse: GenerateOTPResponse? = null - private var attestWalletResponse: AttestWalletKeyResponse? = null - private var signedTransactions: List = listOf() - - override fun run(session: CardSession, callback: CompletionCallback) { - generateOTP(session, callback) - } - - private fun generateOTP(session: CardSession, callback: CompletionCallback) { - GenerateOTPCommand().run(session) { result -> - when (result) { - is CompletionResult.Success -> { - generateOTPResponse = result.data - attestWallet(session, callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private fun attestWallet(session: CardSession, callback: CompletionCallback) { - val card = session.environment.card.guard { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return - } - - val walletPublicKey = card.wallets.firstOrNull()?.publicKey - if (walletPublicKey == null || !walletPublicKey.contentEquals(this.walletPublicKey)) { - callback(CompletionResult.Failure(TangemSdkError.WalletNotFound())) - return - } - - val attestWalletCommand = AttestWalletKeyCommand( - publicKey = walletPublicKey, - challenge = challenge, - confirmationMode = AttestWalletKeyCommand.ConfirmationMode.Dynamic, - ) - attestWalletCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - attestWalletResponse = result.data - scope.launch { prepareTransactions(session, callback) } - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private suspend fun prepareTransactions( - session: CardSession, - callback: CompletionCallback, - ) { - val generateOTPResponse = this.generateOTPResponse.guard { - callback(CompletionResult.Failure(TangemSdkError.UnknownError())) - return - } - - gnosisRegistrator.prepareBeforeMakingTxs().successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - val approvalTx = gnosisRegistrator.makeApprovalTx(approvalValue).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - val setWalletTx = gnosisRegistrator.makeSetWalletTx().successOr { - callback(CompletionResult.Failure(it.error)) - return - } - val initOtpTx = gnosisRegistrator.makeInitOtpTx( - generateOTPResponse.rootOTP, - generateOTPResponse.rootOTPCounter, - ).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - val setSpendLimitTx = gnosisRegistrator.makeSetSpendLimitTx(spendLimitValue).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - signTransactions( - transactions = listOf( - approvalTx, - setWalletTx, - initOtpTx, - setSpendLimitTx, - ), - session = session, - callback = callback, - ) - } - - private fun signTransactions( - transactions: List, - session: CardSession, - callback: CompletionCallback, - ) { - val walletPublicKey = session.environment.card?.wallets?.firstOrNull()?.publicKey.guard { - callback(CompletionResult.Failure(TangemSdkError.WalletNotFound())) - return - } - - val hashes = transactions.map { it.hash }.toTypedArray() - val signHashesCommand = SignHashesCommand(hashes, walletPublicKey) - - signHashesCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - signedTransactions = transactions.zip(result.data.signatures).map { - SignedEthereumTransaction(it.first, it.second) - } - complete(callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private fun complete(callback: CompletionCallback) { - if (signedTransactions.isEmpty() || attestWalletResponse == null) { - callback(CompletionResult.Failure(TangemSdkError.UnknownError())) - } else { - val result = RegistrationTaskResponse(signedTransactions, attestWalletResponse!!) - callback(CompletionResult.Success(result)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt deleted file mode 100644 index 2a33de86f4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import android.app.Activity -import android.content.Intent -import android.net.Uri -import android.provider.MediaStore -import android.text.TextUtils -import android.webkit.PermissionRequest -import android.webkit.ValueCallback -import android.webkit.WebChromeClient -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.activity.ComponentActivity -import androidx.core.app.ActivityCompat -import com.tangem.tap.common.ActivityResultCallbackHolder -import com.tangem.tap.common.OnActivityResultCallback -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.isPermissionGranted -import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.store -import com.tangem.wallet.R -import java.io.File - -/** -[REDACTED_AUTHOR] - */ -class UtorgWebViewClient( - private val onSuccess: () -> Unit, - private val successUrl: String, -) : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = - if (successUrl == request.url.toString()) { - onSuccess() - true - } else { - false - } -} - -class UtorgWebChromeClient( - private val activity: ComponentActivity, - private val activityResultCallbackHolder: ActivityResultCallbackHolder = activity as ActivityResultCallbackHolder, -) : WebChromeClient() { - - private val fileChooseRequestCode = 1002 - private val permissionRequestCode = 1003 - private val onActivityResultCallback: OnActivityResultCallback = ::onActivityResult - - private val permissionList = listOf( - "android.permission.CAMERA", - ) - - private var filePathCallback: ValueCallback>? = null - private var mediaUri: Uri? = null - - override fun onShowFileChooser( - webView: WebView?, - filePathCallback: ValueCallback>, - fileChooserParams: FileChooserParams?, - ): Boolean { - activityResultCallbackHolder.addOnActivityResultCallback(onActivityResultCallback) - if (!readyToShowFileChooser()) return false - - val type: String - if (fileChooserParams != null && fileChooserParams.acceptTypes != null && - fileChooserParams.acceptTypes.isNotEmpty() - ) { - type = if (!TextUtils.isEmpty(fileChooserParams.acceptTypes[0])) fileChooserParams.acceptTypes[0] else "*/*" - this.filePathCallback = filePathCallback - } else { - return false - } - proceedOnType(type, fileChooserParams.isCaptureEnabled) - - return true - } - - private fun readyToShowFileChooser(): Boolean { - val permissionsToGrant = permissionList.filterNot { activity.isPermissionGranted(it) }.toTypedArray() - return if (permissionsToGrant.isNotEmpty()) { - ActivityCompat.requestPermissions(activity, permissionsToGrant, permissionRequestCode) - false - } else { - true - } - } - - private fun proceedOnType(type: String, isCaptureEnabled: Boolean) { - if (isCaptureEnabled) { - mediaUri = Uri.fromFile(File(getImageCaptureCachePath())) - val imageCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - imageCaptureIntent.putExtra("output", mediaUri) - activity.startActivityForResult( - Intent.createChooser(imageCaptureIntent, "Image Chooser"), - fileChooseRequestCode, - ) - } else { - mediaUri = Uri.fromFile(File(getImageCaptureCachePath())) - - val getContentIntent = Intent(Intent.ACTION_GET_CONTENT) - getContentIntent.addCategory(Intent.CATEGORY_OPENABLE) - getContentIntent.type = "image/*" - - // val imageCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - // imageCaptureIntent.putExtra("output", mediaUri) - - val chooserIntent = Intent.createChooser(getContentIntent, "Pick a photo") - // chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(imageCaptureIntent)) - - activity.startActivityForResult( - Intent.createChooser(chooserIntent, "Image Chooser"), - fileChooseRequestCode, - ) - } - } - - private fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - fileChooseRequestCode -> { - if (resultCode != Activity.RESULT_OK) { - filePathCallback?.onReceiveValue(null) - clean() - return - } - if (data == null) { - filePathCallback?.onReceiveValue(arrayOf(mediaUri!!)) - } else { - val uri = FileChooserParams.parseResult(resultCode, data) - filePathCallback?.onReceiveValue(uri) - } - clean() - } - permissionRequestCode -> { - val notGranted = permissionList.filterNot { activity.isPermissionGranted(it) } - if (notGranted.isNotEmpty()) { - val permissionsName = notGranted.joinToString("\n") { it.permissionName() } - store.dispatchDialogShow( - AppDialog.SimpleOkDialog( - header = activity.getString(R.string.common_warning), - message = "Permission access is required to pass authorization." + - "To grant permission \n" + - "$permissionsName \n" + - "you must enable them in the app's settings", - ), - ) - } - } - } - } - - override fun onPermissionRequest(request: PermissionRequest) { - request.grant(request.resources) - } - - private fun getImageCaptureCachePath(): String = "cached_photo_" + System.currentTimeMillis() + ".jpg" - - private fun clean() { - activityResultCallbackHolder.removeOnActivityResultCallback(onActivityResultCallback) - filePathCallback = null - mediaUri = null - } - - private fun String.permissionName(): String { - return this.slice(this.lastIndexOf(".") until this.length) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt deleted file mode 100644 index 5121db94db..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.common.extensions.guard -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object NoFundsForActivationDialog { - - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_no_gas_title) - setMessage(R.string.saltpay_error_no_gas_message) - setPositiveButton(R.string.chat_button_title) { _, _ -> - val sprinklrConfig = store.state.globalState.configManager?.config - ?.saltPayConfig - ?.sprinklr - .guard { - store.dispatchDebugErrorNotification("SaltPayConfig not initialized") - return@setPositiveButton - } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), sprinklrConfig)) - } - setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt deleted file mode 100644 index d3daf798fe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object PutVisaCardDialog { - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_empty_backup_title) - setMessage(R.string.saltpay_error_empty_backup_message) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt deleted file mode 100644 index f3c184157f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage -import com.tangem.tap.domain.moduleMessage.saltPay.SaltPayErrorConverter -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object RegistrationErrorDialog { - fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { - val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage - - return AlertDialog.Builder(context).apply { - setTitle(convertedMessage.title) - setMessage(convertedMessage.message) - setPositiveButton(R.string.common_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt deleted file mode 100644 index 26228ba27f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError - -/** -[REDACTED_AUTHOR] - */ -sealed class SaltPayDialog : StateDialog { - sealed class Activation : SaltPayDialog() { - object NoGas : SaltPayDialog() - object PutVisaCard : SaltPayDialog() - data class OnError(val error: SaltPayActivationError) : SaltPayDialog() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt deleted file mode 100644 index c6e8d9526e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.message - -import com.tangem.common.module.ModuleError -import com.tangem.common.module.ModuleErrorCode -import com.tangem.common.module.ModuleMessage - -sealed interface SaltPayModuleMessage : ModuleMessage - -sealed class SaltPayModuleError( - subCode: Int, - override val message: String, - override val data: Any? = null, -) : SaltPayModuleMessage, ModuleError() { - override val code: Int = ModuleErrorCode.SALT_PAY + subCode - - companion object { - // base code used for all errors in the module - internal const val ERROR_CODE_REGISTRATION = 100 -// const val CODE_ANY_OTHER = 200..299, 300..399 etc - } -} - -sealed class SaltPayActivationError( - subCode: Int, - message: String? = null, - override val data: Any? = null, -) : SaltPayModuleError( - subCode = ERROR_CODE_REGISTRATION + subCode, - message = message ?: EMPTY_MESSAGE, -) { - object Unknown : SaltPayActivationError(subCode = 4) - object Empty : SaltPayActivationError(subCode = 4) - - // gnosis - object FailedToMakeTxData : SaltPayActivationError(subCode = 1) - object FailedToSendTx : SaltPayActivationError(subCode = 2) - - object NeedPin : SaltPayActivationError(subCode = 3) - object NoGas : SaltPayActivationError(subCode = 5) - object EmptyResponse : SaltPayActivationError(subCode = 6) - object CardNotFound : SaltPayActivationError(subCode = 8) - class CardDisabled(message: String?) : SaltPayActivationError(subCode = 9, message = message) - class CardNotPassed(message: String?) : SaltPayActivationError(subCode = 10, message = message) - object EmptyDynamicAttestResponse : SaltPayActivationError(subCode = 11) - object EmptyBackupCardScanned : SaltPayActivationError(subCode = 12) - object WeakPin : SaltPayActivationError(subCode = 13) - - object FailedToGetFundsToClaim : SaltPayActivationError(subCode = 14) - object NoFundsToClaim : SaltPayActivationError(subCode = 15) - object ClaimTransactionFailed : SaltPayActivationError(subCode = 16) - object PutVisaCard : SaltPayActivationError(subCode = 18) - - companion object { - const val EMPTY_MESSAGE = "" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt deleted file mode 100644 index 91b791925d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.blockchain.common.Amount -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import org.rekotlin.Action -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -sealed class OnboardingSaltPayAction : Action { - object Init : OnboardingSaltPayAction() - - /** - * Notification about the switching from the Wallet view to the SaltPay view. - */ - object OnSwitchedToSaltPayProcess : OnboardingSaltPayAction() - - data class SetDependencies( - val registrationManager: SaltPayActivationManager, - ) : OnboardingSaltPayAction() - - data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() - data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() - - data class SetAccessCode(val accessCode: String?) : OnboardingSaltPayAction() - - data class Update(val withAnalytics: Boolean = true) : OnboardingSaltPayAction() - object RegisterCard : OnboardingSaltPayAction() - object OpenUtorgKYC : OnboardingSaltPayAction() - object UtorgKYCRedirectSuccess : OnboardingSaltPayAction() - object RegisterKYC : OnboardingSaltPayAction() - object Claim : OnboardingSaltPayAction() - object RefreshClaim : OnboardingSaltPayAction() - - data class TrySetPin(val pin: String) : OnboardingSaltPayAction() - data class SetPin(val pin: String) : OnboardingSaltPayAction() - - data class SetStep( - val newStep: SaltPayActivationStep, - val withAnalytics: Boolean = true, - ) : OnboardingSaltPayAction() - - data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction() - data class SetTokenBalance(val balanceValue: BigDecimal) : OnboardingSaltPayAction() -} \ No newline at end of file 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 deleted file mode 100644 index 5418a8d42e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ /dev/null @@ -1,466 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.Message -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.Filter -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.common.services.Result -import com.tangem.core.analytics.Analytics -import com.tangem.datasource.api.paymentology.models.response.KYCStatus -import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse -import com.tangem.domain.common.extensions.successOr -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.isPositive -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TangemSigner -import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.features.onboarding.OnboardingManager -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.products.wallet.redux.finishCardActivation -import com.tangem.tap.features.onboarding.products.wallet.redux.gatherCardIds -import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import com.tangem.tap.tangemSdkManager -import com.tangem.wallet.R -import kotlinx.coroutines.launch -import org.rekotlin.Action -import org.rekotlin.Middleware -import timber.log.Timber - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayMiddleware { - val handler = onboardingSaltPayMiddleware -} - -private val onboardingSaltPayMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - handleOnboardingSaltPayAction(action, state) - next(action) - } - } -} - -@Suppress("LongMethod", "ComplexMethod") -private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> AppState?) { - if (DemoHelper.tryHandle(appState, anyAction)) return - val action = anyAction as? OnboardingSaltPayAction ?: return - appState() ?: return - - fun getAppState(): AppState = appState()!! - fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState - fun getOnboardingManager(): OnboardingManager? = getAppState().globalState.onboardingState.onboardingManager - fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!! - - when (action) { - is OnboardingSaltPayAction.Init -> { - if (!getOnboardingWalletState().isSaltPay) return - val onboardingManager = getOnboardingManager().guard { - // onboardingManager may be null if it's started from standard Wallet unfinished backup - return - } - - val card = onboardingManager.scanResponse.card - if (!onboardingManager.isActivationStarted(card.cardId)) { - Analytics.send(Onboarding.Started()) - onboardingManager.activationStarted(card.cardId) - } - } - is OnboardingSaltPayAction.OnSwitchedToSaltPayProcess -> { - sendAnalyticsScreenOpened(SaltPayActivationStep.None, newStep = getState().step) - } - is OnboardingSaltPayAction.Update -> { - handleInProgress = true - - val state = getState() - scope.launch { - val updateStep = state.saltPayManager.update(state.step, state.amountToClaim).successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep, action.withAnalytics)) - - if (updateStep == SaltPayActivationStep.Claim) { - handleClaimRefreshInProgress = true - val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { - SaltPayExceptionHandler.handle(it.error) - handleClaimRefreshInProgress = false - return@launch - } - dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - handleClaimRefreshInProgress = false - } - } - } - is OnboardingSaltPayAction.RegisterCard -> { - handleInProgress = true - val state = getState() - - scope.launch { - val saltPayManager = state.saltPayManager - checkGasIfNeeded(state.saltPayManager, state.step).successOr { - onException(it.error) - return@launch - } - - val pinCode = state.pinCode.guard { - onError(SaltPayActivationError.NeedPin) - return@launch - } - - val attestationResponse = saltPayManager.requestAttestationChallenge().successOr { - onException(it.error) - return@launch - } - - val registrationResponse = tangemSdkManager.runTaskAsync( - runnable = saltPayManager.makeRegistrationTask(attestationResponse.challenge!!), - cardId = saltPayManager.cardId, - initialMessage = Message( - header = null, - body = tangemSdkManager.getString(R.string.registration_task_alert_message), - ), - accessCode = state.accessCode, - ).successOr { - onException(it.error as Exception) - return@launch - } - - saltPayManager.sendTransactions(registrationResponse.signedTransactions).successOr { - onError(SaltPayActivationError.FailedToSendTx) - return@launch - } - - saltPayManager.registerWallet(registrationResponse.attestResponse, pinCode).successOr { - onException(it.error) - return@launch - } - - Analytics.send(Onboarding.PinCodeSet()) - - var nextStep = SaltPayActivationStep.KycIntro - nextStep = saltPayManager.update(nextStep, state.amountToClaim).successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(nextStep)) - } - } - is OnboardingSaltPayAction.OpenUtorgKYC -> { - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycStart)) - } - is OnboardingSaltPayAction.UtorgKYCRedirectSuccess -> { - store.dispatch(OnboardingSaltPayAction.RegisterKYC) - } - is OnboardingSaltPayAction.RegisterKYC -> { - val state = getState() - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycWaiting)) - handleInProgress = true - - scope.launch { - Timber.d("saltPayManager.registerKYC()") - state.saltPayManager.registerKYC().successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.Update()) - } - } - is OnboardingSaltPayAction.TrySetPin -> { - try { - assertPinValid(action.pin, getState().pinLength) - Analytics.send(Onboarding.ButtonSetPinCode()) - store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) - } catch (error: SaltPayActivationError) { - SaltPayExceptionHandler.handle(error) - } - } - is OnboardingSaltPayAction.Claim -> { - val state = getState() - handleInProgress = true - - scope.launch { - val amountToClaim = getAmountToClaimIfNeeded(state.saltPayManager, state.amountToClaim).guard { - onException(SaltPayActivationError.NoFundsToClaim) - return@launch - } - - val scanResponse = store.state.globalState.scanResponse - ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse - val signer = TangemSigner( - card = scanResponse!!.card, - tangemSdk = tangemSdk, - initialMessage = Message(), - accessCode = state.accessCode, - ) { signResponse -> - store.dispatch( - GlobalAction.UpdateWalletSignedHashes( - walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = state.saltPayManager.walletPublicKey, - remainingSignatures = signResponse.remainingSignatures, - ), - ) - } - state.saltPayManager.claim(amountToClaim.value!!, signer).successOr { - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim)) - if (it.error is TangemSdkError.UserCancelled) { - handleInProgress = false - } else { - onException(it.error) - } - return@launch - } - - handleInProgress = false - Analytics.send(Onboarding.ClaimWasSuccessfully()) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress)) - dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) - } - } - is OnboardingSaltPayAction.RefreshClaim -> { - handleClaimRefreshInProgress = true - - val state = getState() - scope.launch { - val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { - SaltPayExceptionHandler.handle(it.error) - handleClaimRefreshInProgress = false - return@launch - } - dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - - val amountToClaim = state.saltPayManager.getAmountToClaim().successOr { - if (it.error !is SaltPayActivationError.NoFundsToClaim) { - handleClaimRefreshInProgress = false - SaltPayExceptionHandler.handle(it.error) - return@launch - } else { - null - } - } - handleClaimRefreshInProgress = false - - if (tokenAmountValue.isPositive() && amountToClaim == null) { - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) - } else { - // dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) - } - } - } - is OnboardingSaltPayAction.SetStep -> { - if (action.withAnalytics) { - sendAnalyticsScreenOpened(getState().step, action.newStep) - } - if (action.newStep == SaltPayActivationStep.ClaimSuccess || - action.newStep == SaltPayActivationStep.Success - ) { - val onboardingManager = getOnboardingManager().guard { - // Null is possible if it is started from a standard pending backup and in this case - // it is impossible to get here - store.dispatchDebugErrorNotification("OnboardingManager can't be NULL") - return - } - val cardIds = gatherCardIds(getOnboardingWalletState().backupState, onboardingManager.scanResponse.card) - finishCardActivation(cardIds) - } - } - else -> { - /* do nothing, only reduce */ - } - } -} - -private fun sendAnalyticsScreenOpened(currentStep: SaltPayActivationStep, newStep: SaltPayActivationStep) { - if (currentStep == newStep) return - - when (newStep) { - SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) - SaltPayActivationStep.CardRegistration -> Analytics.send(Onboarding.CardConnectionScreenOpened()) - SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) - SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) - SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) - SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) - SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened()) - SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success -> Analytics.send(Onboarding.Finished()) - else -> {} - } -} - -suspend fun SaltPayActivationManager.update( - currentStep: SaltPayActivationStep, - currentAmountToClaim: Amount?, -): Result { - Timber.d("updateSaltPayStatus") - - val registrationResponse = checkRegistration().successOr { return it } - val amountToClaim = getAmountToClaimIfNeeded(this, currentAmountToClaim) - dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(amountToClaim)) - - val newStep = try { - determineStep(currentStep, amountToClaim, registrationResponse) - } catch (ex: Exception) { - return Result.Failure(ex) - } - - checkGasIfNeeded(this, newStep).successOr { return it } - - Timber.d("update: success: %s", newStep) - return Result.Success(newStep) -} - -@Suppress("ComplexMethod") -private fun determineStep( - currentStep: SaltPayActivationStep, - amountToClaim: Amount?, - response: RegistrationResponse.Item, -): SaltPayActivationStep { - fun getStepBaseOnAmountToClaim(amount: Amount?): SaltPayActivationStep = when (amount) { - null -> SaltPayActivationStep.Success - else -> SaltPayActivationStep.Claim - } - - return when { - response.passed != true -> throw SaltPayActivationError.CardNotPassed(response.error) - response.disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(response.error) - - // go to claim screen - response.active == true -> getStepBaseOnAmountToClaim(amountToClaim) - - // pinSet is false, go toPin screen - response.pinSet == false -> SaltPayActivationStep.NeedPin - - response.kycStatus != null -> { - when (currentStep) { - SaltPayActivationStep.KycWaiting -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - SaltPayActivationStep.KycReject -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart - KYCStatus.REJECTED -> SaltPayActivationStep.KycStart - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - else -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - } - } - - // kycDate is set, go to kyc waiting screen - response.kycDate != null -> SaltPayActivationStep.KycWaiting - - else -> SaltPayActivationStep.KycIntro - } -} - -private suspend fun getAmountToClaimIfNeeded( - saltPayManager: SaltPayActivationManager, - amountToClaim: Amount?, -): Amount? { - Timber.d("getAmountToClaimIfNeeded: for amount: %s", amountToClaim) - if (amountToClaim != null) { - Timber.d("getAmountToClaimIfNeeded: checks no need, amount already persist") - return amountToClaim - } - - // return Amount(BigDecimal(0.1), Blockchain.SaltPay) - return when (val result = saltPayManager.getAmountToClaim()) { - is Result.Success -> { - Timber.d("getAmountToClaimIfNeeded: success: %s", result.data) - return result.data - } - is Result.Failure -> { - Timber.e(result.error, "getAmountToClaimIfNeeded: failure") - null - } - } -} - -private var handleClaimRefreshInProgress: Boolean = false - set(value) { - field = value - store.dispatchOnMain(OnboardingSaltPayAction.SetClaimRefreshInProgress(value)) - } - -private var handleInProgress: Boolean = false - set(value) { - field = value - store.dispatchOnMain(OnboardingSaltPayAction.SetInProgress(value)) - } - -private suspend fun onError(error: SaltPayActivationError) { - withMainContext { - handleInProgress = false - SaltPayExceptionHandler.handle(error) - } -} - -private suspend fun onException(error: Throwable) { - withMainContext { - handleInProgress = false - SaltPayExceptionHandler.handle(error) - } -} - -private suspend fun checkGasIfNeeded( - saltPayManager: SaltPayActivationManager, - step: SaltPayActivationStep, -): Result { - Timber.d("checkGasIfNeeded: for step: %s", step) - if (step.ordinal >= SaltPayActivationStep.KycIntro.ordinal) { - Timber.d("checkGasIfNeeded: no need to check for step: %s", step) - return Result.Success(Unit) - } - - val result = saltPayManager.checkHasGas() - when (result) { - is Result.Success -> Timber.d("checkGasIfNeeded: has GAS") - is Result.Failure -> Timber.d("checkGasIfNeeded: has NO GAS") - } - return result -} - -@Throws(SaltPayActivationError::class) -private fun assertPinValid(pin: String, pinLength: Int) { - val array = pin.toCharArray() - - if (array.size < pinLength) throw SaltPayActivationError.WeakPin - - val filters: List> = listOf( - AllSymbolsTheSameFilter(), - ) - - if (filters.any { it.filter(pin) }) { - throw SaltPayActivationError.WeakPin - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt deleted file mode 100644 index ad3c493336..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import org.rekotlin.Action - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayReducer { - fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { - return internalReduce(action, state) - } -} - -@Suppress("ComplexMethod") -private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState { - val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState - - if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.SetDependencies) { - return onboardingWalletState.copy( - onboardingSaltPayState = OnboardingSaltPayState( - saltPayManager = action.registrationManager, - ), - ) - } - - val oldState = onboardingWalletState.onboardingSaltPayState ?: return onboardingWalletState - - val newState = when (action) { - is OnboardingSaltPayAction.SetAccessCode -> { - oldState.copy( - accessCode = action.accessCode, - ) - } - is OnboardingSaltPayAction.SetPin -> { - oldState.copy(pinCode = action.pin) - } - is OnboardingSaltPayAction.SetStep -> { - if (action.newStep == null) { - oldState - } else { - oldState.copy( - step = action.newStep, - ) - } - } - is OnboardingSaltPayAction.SetAmountToClaim -> { - oldState.copy( - amountToClaim = action.amount, - ) - } - is OnboardingSaltPayAction.SetTokenBalance -> { - oldState.copy( - tokenAmount = oldState.tokenAmount.copy(value = action.balanceValue), - ) - } - is OnboardingSaltPayAction.SetInProgress -> { - oldState.copy( - inProgress = action.isInProgress, - ) - } - is OnboardingSaltPayAction.SetClaimRefreshInProgress -> { - oldState.copy( - claimInProgress = action.isInProgress, - ) - } - else -> oldState - } - - return when (newState) { - oldState -> onboardingWalletState - else -> { - onboardingWalletState.copy(onboardingSaltPayState = newState) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt deleted file mode 100644 index 7b68518634..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.tap.common.toggleWidget.WidgetState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.wallet.redux.ProgressState -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class OnboardingSaltPayState( - @Transient - val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(), - val pinCode: String? = null, - val accessCode: String? = null, - val amountToClaim: Amount? = null, - val tokenAmount: Amount = Amount(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay), BigDecimal.ZERO), - val step: SaltPayActivationStep = SaltPayActivationStep.None, - val saltPayCardArtworkUrl: String? = null, - val inProgress: Boolean = false, - val claimInProgress: Boolean = false, -) { - - val mainButtonState: WidgetState - get() = if (inProgress) ProgressState.Loading else ProgressState.Done - - val pinLength: Int = 4 -} - -enum class SaltPayActivationStep { - None, - NoGas, - NeedPin, - CardRegistration, - KycIntro, - KycStart, - KycWaiting, - KycReject, - Claim, - ClaimInProgress, - ClaimSuccess, - Success, -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt deleted file mode 100644 index b4c5bf2c60..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt +++ /dev/null @@ -1,387 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui - -import android.net.Uri -import android.view.View -import android.view.ViewGroup -import android.view.animation.OvershootInterpolator -import android.widget.ProgressBar -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes -import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.view.children -import androidx.transition.TransitionManager -import coil.load -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.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.compose.PinCodeWidget -import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.feedback.SupportInfo -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.common.toggleWidget.IndeterminateProgressButtonWidget -import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget -import com.tangem.tap.common.transitions.InternalNoteLayoutTransition -import com.tangem.tap.features.onboarding.products.wallet.redux.* -import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep -import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.LayoutOnboardingContainerBottomBinding -import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding -import com.tangem.wallet.databinding.LayoutOnboardingMainBinding - -/** -[REDACTED_AUTHOR] - */ -internal class OnboardingSaltPayStateHandler( - private val walletFragment: OnboardingWalletFragment, -) { - - private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } - private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } - private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { - claimBinding.onboardingActionContainer - } - - private val btnRefreshBalanceWidget by lazy { - RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) - } - - private var progressButton: SaltPayProgressButton? = null - private var previousStep: SaltPayActivationStep = SaltPayActivationStep.None - - fun newState(state: OnboardingWalletState) { - handleCardArtworks(state) - - when (state.step) { - OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) - OnboardingWalletStep.Backup -> { - if (state.backupState.backupStep == BackupStep.Finished && !state.backupState.isInterruptedBackup) { - // if backup finished -> switch OnboardingWalletStep to the SaltPay - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) - } else { - // if not -> back to the standard backup process - walletFragment.handleOnboardingStep(state) - } - } - else -> walletFragment.handleOnboardingStep(state) - } - } - - private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { - // TODO: SaltPay: remove hardCode - if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - imvFrontCard.load(R.drawable.img_salt_pay_visa) { - scale(Scale.FILL) - crossfade(enable = true) - } - } else { - val uri = Uri.parse(state.onboardingSaltPayState.saltPayCardArtworkUrl) - walletFragment.loadImageIntoImageView(uri, imvFrontCard) - } - imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) - - // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - // //if saltPay url not loaded -> load from resource - // val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) - // imvFrontCard.setImageBitmap(bitmap) - // } else { - // walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) - // } - // walletFragment.loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard) - } - - fun showAddBackupCards(state: BackupState) = with(walletFragment.binding) { - when (state.backupCardsNumber) { - 0 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card) - } - else -> {} - } - } - - private fun setSaltPayStep(onboardingSaltPayState: OnboardingSaltPayState?) { - val state = onboardingSaltPayState.guard { - store.dispatchDebugErrorNotification("SaltPayView: setSaltPayStep: onboardingSaltPayState = null") - return - } - - walletFragment.binding.onboardingWalletContainer.hide() - walletFragment.bindingSaltPay.onboardingSaltpayContainer.show() - - when (state.step) { - SaltPayActivationStep.NoGas -> handleNoGas() - SaltPayActivationStep.NeedPin -> handleNeedPin() - SaltPayActivationStep.CardRegistration -> handleCardRegistration() - SaltPayActivationStep.KycIntro -> handleKycIntro() - SaltPayActivationStep.KycStart -> handleKycStart(state) - SaltPayActivationStep.KycWaiting -> handleKycWaiting() - SaltPayActivationStep.KycReject -> handleKycReject() - SaltPayActivationStep.Claim -> handleClaim(state) - SaltPayActivationStep.ClaimInProgress -> handleClaim(state) - SaltPayActivationStep.ClaimSuccess -> handleClaim(state) - SaltPayActivationStep.Success -> handleSuccess() - else -> Unit - } - progressButton?.changeState(state.mainButtonState) - } - - private fun handleNoGas() { - // normally this shouldn't happen - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatchDialogShow(SaltPayDialog.Activation.NoGas) - } - - private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_pin) - showOnlyView(pinCode.root) { - progressButton = SaltPayProgressButton(pinCode.root) - progressButton?.mainView?.isEnabled = false - - var enteredPinCode = "" - pinCode.flPinCodeContainer.setContent { - PinCodeWidget { code, isLastSymbolEntered -> - enteredPinCode = code - progressButton?.mainView?.isEnabled = isLastSymbolEntered - } - } - pinCode.btnSetPin.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.TrySetPin(enteredPinCode)) - } - } - } - - private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_register_wallet) - showOnlyView(connectCard.root) { - progressButton = SaltPayProgressButton(connectCard.root) - connectCard.btnConnect.setOnClickListener { - Analytics.send(Onboarding.ButtonConnect()) - store.dispatch(OnboardingSaltPayAction.RegisterCard) - } - } - } - - private fun handleKycIntro() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_start) - showOnlyView(verifyIdentity.root) { - progressButton = SaltPayProgressButton(verifyIdentity.root) - verifyIdentity.btnVerify.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.OpenUtorgKYC) - } - } - } - - private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_start) - showOnlyView(webvVerifyIdentity) { - progressButton = null - webvVerifyIdentity.configureSettings() - webvVerifyIdentity.webViewClient = UtorgWebViewClient( - successUrl = state.saltPayManager.kycUrlProvider.doneUrl, - onSuccess = { - webvVerifyIdentity.stop() - store.dispatch(OnboardingSaltPayAction.UtorgKYCRedirectSuccess) - }, - ) - webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity()) - webvVerifyIdentity.loadUrl(state.saltPayManager.kycUrlProvider.requestUrl) - } - } - - private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_progress) - showOnlyView(root) - - imvInProgress.setDrawable(R.drawable.ic_in_progress) - tvHeader.text = getText(R.string.onboarding_title_kyc_waiting) - tvBody.text = getText(R.string.onboarding_subtitle_kyc_waiting) - - btnOpenSupportChat.hide() - btnOpenSupportChat.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - - btnKycAction.text = getText(R.string.onboarding_button_kyc_waiting) - btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update()) - } - progressButton = SaltPayProgressButton(root) - } - - private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_progress) - showOnlyView(root) - - imvInProgress.setDrawable(R.drawable.ic_reject) - tvHeader.text = getText(R.string.onboarding_title_kyc_retry) - tvBody.text = getText(R.string.onboarding_subtitle_kyc_retry) - - btnOpenSupportChat.hide() - btnKycAction.text = getText(R.string.onboarding_button_kyc_start) - btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update()) - } - progressButton = SaltPayProgressButton(root) - } - - @Suppress("LongMethod") - private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_getting_started) - val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) - val tvHeader = actionContainer.tvHeader - val tvBody = actionContainer.tvBody - - showOnlyView(claimBinding.root) { - // topContainer.progress.pbState.hide() - progressButton = SaltPayProgressButton(actionContainer.root) - topContainer.imvCardBackground.apply { - setBackgroundDrawable(context.getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - } - topContainer.imvFrontCard.load(R.drawable.img_salt_pay_visa) { - scale(Scale.FILL) - crossfade(enable = true) - } - actionContainer.btnAlternativeAction.hide() - updateConstraints(state.step, R.layout.lp_onboarding_topup_wallet) - } - - btnRefreshBalanceWidget.changeState(if (state.claimInProgress) ProgressState.Loading else ProgressState.Done) - if (btnRefreshBalanceWidget.isShowing != true) { - btnRefreshBalanceWidget.mainView.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.RefreshClaim) - } - } - val tokenAmountString = tokenAmountString(state) - topContainer.onboardingTvBalance.tvBalanceValue.text = tokenAmountString ?: "" - topContainer.onboardingTvBalance.tvBalanceCurrency.text = "" - - when (state.step) { - SaltPayActivationStep.Claim -> { - claimValueString(state)?.let { - tvHeader.text = getText(R.string.onboarding_title_claim, it) - } - tvBody.text = getText(R.string.onboarding_subtitle_claim) - - btnMain.text = getText(R.string.onboarding_button_claim) - btnMain.isEnabled - btnMain.setOnClickListener { - Analytics.send(Onboarding.ButtonClaim()) - store.dispatch(OnboardingSaltPayAction.Claim) - } - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = true - } - SaltPayActivationStep.ClaimInProgress -> { - tvHeader.text = getText(R.string.onboarding_title_claim_progress) - tvBody.text = getText(R.string.onboarding_subtitle_claim_progress) - btnMain.text = getText(R.string.onboarding_button_claim) - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = false - } - SaltPayActivationStep.ClaimSuccess -> { - tvHeader.setText(R.string.common_success) - tvBody.setText(R.string.onboarding_subtitle_success_claim) - - btnMain.setText(R.string.onboarding_button_continue_wallet) - btnMain.setOnClickListener { - walletFragment.showConfetti(false) - store.dispatch(OnboardingWalletAction.FinishOnboarding) - } - - btnRefreshBalanceWidget.mainView.setOnClickListener(null) - updateConstraints(state.step, R.layout.lp_onboarding_done_activation) - walletFragment.showConfetti(true) - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = true - } - else -> {} - } - } - - private fun handleSuccess() = with(walletFragment.binding) { - walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() - onboardingWalletContainer.show() - walletFragment.showSuccess() - tvBody.text = getText(R.string.onboarding_subtitle_success_claim) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet) - } - - private fun tokenAmountString(state: OnboardingSaltPayState): String? { - return state.tokenAmount.value?.toFormattedCurrencyString( - decimals = state.tokenAmount.decimals, - currency = state.tokenAmount.currencySymbol, - ) - } - - private fun claimValueString(state: OnboardingSaltPayState): String? { - return state.amountToClaim?.value?.toFormattedCurrencyString( - decimals = state.amountToClaim.decimals, - currency = state.amountToClaim.currencySymbol, - ) - } - - private fun updateConstraints(currentStep: SaltPayActivationStep, @LayoutRes layoutId: Int) { - if (this.previousStep == currentStep) return - - with(claimBinding.onboardingTopContainer) { - val constraintSet = ConstraintSet() - constraintSet.clone(root.context, layoutId) - constraintSet.applyTo(onboardingWalletContainer) - val transition = InternalNoteLayoutTransition() - transition.interpolator = OvershootInterpolator() - TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) - } - } - - private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { - walletFragment.bindingSaltPay.onboardingSaltpayContainer.children - .filter { it.id != view.id } - .forEach { it.hide() } - view.show { onShowListener?.invoke() } - } - - private fun getText(@StringRes resId: Int): String { - return walletFragment.getString(resId) - } - - private fun getText(@StringRes resId: Int, vararg formatArgs: Any?): String { - return walletFragment.getString(resId, *formatArgs) - } - - private class SaltPayProgressButton( - root: ViewGroup, - ) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) { - companion object { - fun findButton(view: ViewGroup): MaterialButton { - return findContainer(view).children.firstOrNull { it is MaterialButton } as MaterialButton - } - - fun findProgress(view: ViewGroup): View { - return findContainer(view).children.firstOrNull { it is ProgressBar }!! - } - - fun findContainer(view: ViewGroup): ViewGroup { - return view.children.first { it.id == R.id.btn_container } as ViewGroup - } - } - } -} \ No newline at end of file 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 13c2a3842e..269449599e 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 @@ -37,14 +37,11 @@ import com.tangem.tap.features.FragmentOnBackPressedHandler import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.OnboardingMenuProvider import com.tangem.tap.features.onboarding.products.wallet.redux.* -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayStateHandler import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding -import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import dagger.hilt.android.AndroidEntryPoint @@ -62,13 +59,10 @@ class OnboardingWalletFragment : internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer } - internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler() private val seedPhraseViewModel by viewModels() - private val saltPayStateHandler: OnboardingSaltPayStateHandler = OnboardingSaltPayStateHandler(this) - private lateinit var cardsWidget: WalletCardsWidget private var accessCodeDialog: AccessCodeDialog? = null @@ -108,7 +102,6 @@ class OnboardingWalletFragment : store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress)) store.dispatch(OnboardingWalletAction.Init) - store.dispatch(OnboardingSaltPayAction.Init) store.dispatch( OnboardingWalletAction.LoadArtwork( cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet), @@ -170,7 +163,6 @@ class OnboardingWalletFragment : override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return - if (state.isSaltPay) reInitCardsWidgetIfNeeded(1) animator.updateBackupState(state.backupState) requireActivity().invalidateOptionsMenu() @@ -182,9 +174,6 @@ class OnboardingWalletFragment : state.wallet2State != null -> { seedPhraseStateHandler.newState(this, state, seedPhraseViewModel) } - state.isSaltPay -> { - saltPayStateHandler.newState(state) - } else -> { loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) @@ -207,7 +196,6 @@ class OnboardingWalletFragment : OnboardingWalletStep.CreateWallet -> setupCreateWalletState() OnboardingWalletStep.Backup -> setBackupState( state = state.backupState, - isSaltPay = state.isSaltPay, isWallet2 = state.wallet2State != null, ) @@ -231,21 +219,21 @@ class OnboardingWalletFragment : animator.setupCreateWalletState() } - private fun setBackupState(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) { + private fun setBackupState(state: BackupState, isWallet2: Boolean) { when (state.backupStep) { - BackupStep.InitBackup -> showBackupIntro(state, isSaltPay, isWallet2) + BackupStep.InitBackup -> showBackupIntro(state, isWallet2) BackupStep.ScanOriginCard -> showScanOriginCard() - BackupStep.AddBackupCards -> showAddBackupCards(state, isSaltPay) + BackupStep.AddBackupCards -> showAddBackupCards(state) BackupStep.SetAccessCode -> showSetAccessCode() BackupStep.EnterAccessCode -> showEnterAccessCode(state) BackupStep.ReenterAccessCode -> showReenterAccessCode(state) - is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state, isSaltPay) - is BackupStep.WriteBackupCard -> showWriteBackupCard(state, isSaltPay) + is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state) + is BackupStep.WriteBackupCard -> showWriteBackupCard(state) BackupStep.Finished -> showSuccess() } } - private fun showBackupIntro(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) = with(binding) { + private fun showBackupIntro(state: BackupState, isWallet2: Boolean) = with(binding) { imvFirstBackupCard.show() imvSecondBackupCard.show() @@ -260,7 +248,7 @@ class OnboardingWalletFragment : btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup) btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) } - btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay && !isWallet2) + btnWalletAlternativeAction.show(state.canSkipBackup && !isWallet2) } animator.showBackupIntro(state) } @@ -281,7 +269,7 @@ class OnboardingWalletFragment : animator.showScanOriginCard() } - private fun showAddBackupCards(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showAddBackupCards(state: BackupState) = with(binding) { prepareBackupView() accessCodeDialog?.dismiss() @@ -299,25 +287,20 @@ class OnboardingWalletFragment : layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup) layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) } layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0 - - if (isSaltPay) { - saltPayStateHandler.showAddBackupCards(state) - } else { - when (state.backupCardsNumber) { - 0 -> { - tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = getText(R.string.onboarding_title_one_backup_card) - tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) - } - 2 -> { - tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) - } - else -> {} + when (state.backupCardsNumber) { + 0 -> { + tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) } + 1 -> { + tvHeader.text = getText(R.string.onboarding_title_one_backup_card) + tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) + } + 2 -> { + tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) + } + else -> {} } animator.showAddBackupCards(state, state.backupCardsNumber) @@ -363,25 +346,19 @@ class OnboardingWalletFragment : accessCodeDialog?.showError(state.accessCodeError) } - private fun showWritePrimaryCard(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showWritePrimaryCard(state: BackupState) = with(binding) { accessCodeDialog?.dismiss() reInitCardsWidgetIfNeeded(state.backupCardsNumber) prepareViewForFinalizeStep() val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) - if (isSaltPay) { - tvHeader.text = getText(R.string.onboarding_saltpay_title_prepare_origin) - tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_saltpay_button_backup_origin) - } else { - tvHeader.text = getText(R.string.common_origin_card) - tvBody.text = getString( - R.string.onboarding_subtitle_scan_primary_card_format, - state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, - ) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin) - } + tvHeader.text = getText(R.string.common_origin_card) + tvBody.text = getString( + R.string.onboarding_subtitle_scan_primary_card_format, + state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, + ) + layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin) layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } animator.showWritePrimaryCard(state) @@ -399,27 +376,21 @@ class OnboardingWalletFragment : imvSecondBackupCard.show() } - private fun showWriteBackupCard(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showWriteBackupCard(state: BackupState) = with(binding) { prepareViewForFinalizeStep() reInitCardsWidgetIfNeeded(state.backupCardsNumber) val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 - if (isSaltPay) { - tvHeader.text = getString(R.string.onboarding_saltpay_title_backup_card) - tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnWalletMainAction.text = getString(R.string.onboarding_saltpay_title_backup_card) - } else { - val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) - tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) - tvBody.text = getString( - R.string.onboarding_subtitle_scan_backup_card_format, - cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]), - ) - layoutButtonsCommon.btnWalletMainAction.text = getString( - R.string.onboarding_button_backup_card_format, - cardNumber, - ) - } + val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) + tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) + tvBody.text = getString( + R.string.onboarding_subtitle_scan_backup_card_format, + cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]), + ) + layoutButtonsCommon.btnWalletMainAction.text = getString( + R.string.onboarding_button_backup_card_format, + cardNumber, + ) layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt index 36827b96a4..678c2ca2df 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs import android.app.Dialog import android.content.Context 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 1dde548a55..9c1feed6b4 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 @@ -132,6 +132,5 @@ data class Artwork( const val MARTA_CARD_ID = "BC02" const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" - const val SALT_PAY_URL = "key_for_switch_url_to_drawableId_of_salt_pay_card" } } \ 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 b15b018e09..47d07b91b0 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 @@ -24,7 +24,6 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.datasource.connection.NetworkConnectionManager -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.feature.swap.api.SwapFeatureToggleManager import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity @@ -47,7 +46,6 @@ import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView -import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView import com.tangem.tap.store import com.tangem.wallet.BuildConfig import com.tangem.wallet.R @@ -166,20 +164,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber { - walletView.onViewDestroy() - walletView = SaltPayWalletView() - walletView.changeWalletView(this, binding) - } state.isMultiwalletAllowed && walletView !is MultiWalletView -> { walletView.onViewDestroy() walletView = MultiWalletView() walletView.changeWalletView(this, binding) } - !state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> { + !state.isMultiwalletAllowed && walletView !is SingleWalletView -> { walletView.onViewDestroy() walletView = SingleWalletView() walletView.changeWalletView(this, binding) @@ -199,9 +190,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber ViewGroup = { - val inflater = LayoutInflater.from(it.context) - ItemSaltPayTxHistoryShimmerBinding.inflate(inflater, it, false).root - } - val adapter = ShimmerRecyclerAdapter(vhViewFactory) - rvTxHistoryShimmer.adapter = adapter - rvTxHistoryShimmer.addItemDecoration(itemDecorator) - adapter.submitList( - listOf( - ShimmerData(), - ShimmerData(), - ShimmerData(), - ), - ) - - rvTxHistory.adapter = TxHistoryAdapter() - rvTxHistory.addItemDecoration(itemDecorator) - } - - override fun onNewState(state: WalletState) { - val balanceWidget = balanceWidget ?: return - val txWidget = txWidget ?: return - - val exchangeManager = store.state.globalState.exchangeManager - setupBalanceWidget(balanceWidget, exchangeManager, state) - setupTxHistoryWidget(txWidget, state) - } - - private fun setupBalanceWidget( - balanceWidget: LayoutSaltPayBalanceBinding, - exchangeManager: CurrencyExchangeManager, - state: WalletState, - ) = with(balanceWidget) { - Timber.d("setupBalanceWidget") - val tokenData = state.primaryTokenData ?: return - - val appCurrency = store.state.globalState.appCurrency - val mainProgressState = state.state - - if (mainProgressState == ProgressState.Loading) { - veilBalance.veil() - veilBalanceCrypto.veil() - } else { - veilBalanceCrypto.unVeil() - } - - tvUnreachable.animateVisibility(show = mainProgressState == ProgressState.Error) - veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error) - - if (tokenData.fiatRate == null) { - veilBalance.veil() - } else { - veilBalance.unVeil() - tvBalance.text = tokenData.getFormattedFiatAmount(appCurrency) - } - - tvBalanceCrypto.text = tokenData.getFormattedAmount() - - tvCurrencyName.text = appCurrency.code - tvCurrencyName.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) - } - - btnBuy.show(tokenData.isAvailableToBuy(exchangeManager)) - btnBuy.setOnClickListener { - Analytics.send(MainScreen.ButtonBuy()) - store.dispatch(WalletAction.TradeCryptoAction.Buy(false)) - } - } - - private fun setupTxHistoryWidget(txWidget: LayoutSaltPayTxHistoryBinding, state: WalletState) { - Timber.d("setupTxHistoryWidget") - if (state.state == ProgressState.Loading) { - handleTxLoading(txWidget) - return - } - if (state.state == ProgressState.Error) { - handleTxError(txWidget) - return - } - if (state.state != ProgressState.Done) return - val walletAddress = state.primaryWalletManager?.wallet?.address ?: return - val tokenData = state.primaryTokenData ?: return - val historyTransactions = tokenData.historyTransactions ?: return - if (historyTransactions.isEmpty()) { - handleTxEmpty(txWidget) - return - } - - scope.launch { - val dateAssociatedHistory = mutableListOf>>() - historyTransactions.forEach { tx -> - val txHistoryData = HistoryTransactionData(tx, walletAddress) - val item = dateAssociatedHistory.firstOrNull { it.first == txHistoryData.date } - if (item == null) { - dateAssociatedHistory.add(Pair(txHistoryData.date, mutableListOf(txHistoryData))) - } else { - item.second.add(txHistoryData) - } - } - - val rvDataList = mutableListOf() - dateAssociatedHistory.forEach { (txData, txHistoryData) -> - rvDataList.add(HistoryItemData.Date(txData)) - rvDataList.addAll(txHistoryData.map { HistoryItemData.TransactionData(it) }) - } - - withMainContext { handleTxSuccess(txWidget, rvDataList) } - } - } - - private fun handleTxLoading(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxLoading") - groupEmpty.hide() - groupError.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - groupShimmer.show() - } - - private fun handleTxSuccess(txWidget: LayoutSaltPayTxHistoryBinding, dataList: List) = - with(txWidget) { - Timber.d("handleTxSuccess") - groupShimmer.hide() - groupEmpty.hide() - groupError.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, dataList) - groupSuccess.show() - } - - private fun handleTxEmpty(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxEmpty") - groupShimmer.hide() - groupError.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, emptyList()) - groupEmpty.show() - } - - private fun handleTxError(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxError") - groupShimmer.hide() - groupEmpty.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, emptyList()) - groupError.show() - } - - private fun updateTxHistoryWidget(txWidget: LayoutSaltPayTxHistoryBinding, dataList: List) = - with(txWidget) { - (rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList) - } - - override fun onViewDestroy() { - Timber.d("onViewDestroy") - txWidget?.apply { - rvTxHistoryShimmer.removeItemDecoration(itemDecorator) - rvTxHistory.removeItemDecoration(itemDecorator) - } - super.onViewDestroy() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt deleted file mode 100644 index 7f194e047e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionStatus -import com.tangem.common.extensions.guard -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.features.wallet.models.PendingTransactionType -import java.text.SimpleDateFormat -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -sealed class HistoryItemData( - val viewType: Int, - val itemId: Long, -) { - data class Date(val date: String) : HistoryItemData(0, date.hashCode().toLong()) - data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.address.hashCode().toLong()) -} - -data class HistoryTransactionData( - private val transactionData: TransactionData, - private val walletAddress: String, -) { - - val isInProgress: Boolean = transactionData.status == TransactionStatus.Unconfirmed - - val transactionType: PendingTransactionType = when { - transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing - transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming - else -> PendingTransactionType.Unknown - } - - val address: String = when (transactionType) { - PendingTransactionType.Incoming -> transactionData.sourceAddress.reduceAddress() - PendingTransactionType.Outgoing -> transactionData.destinationAddress.reduceAddress() - PendingTransactionType.Unknown -> "Unknown address" - } - - val time: String = transactionData.date?.let { - SimpleDateFormat("HH:mm").format(it.time) - } ?: "00:00" - - val date: String = calculateDate() - - val txSign: String = when (transactionType) { - PendingTransactionType.Incoming -> "+" - PendingTransactionType.Outgoing -> "-" - PendingTransactionType.Unknown -> "" - } - - val amountValue: String = transactionData.amount.value - ?.toFormattedCurrencyString(8, transactionData.amount.currencySymbol) ?: "0" - - private fun calculateDate(): String { - val calendarTx = transactionData.date.guard { return "Unknown" } - - val calendarNow = Calendar.getInstance() - val oldDateFormatter = SimpleDateFormat("dd.MM.yyyy") - - val date = if (calendarTx.get(Calendar.YEAR) == calendarNow.get(Calendar.YEAR)) { - val dayOfYearTx = calendarTx.get(Calendar.DAY_OF_YEAR) - val dayOfYearNow = calendarNow.get(Calendar.DAY_OF_YEAR) - when { - dayOfYearNow == dayOfYearTx -> "Today" - dayOfYearNow - dayOfYearTx == 1 -> "Tomorrow" - else -> oldDateFormatter.format(calendarTx.time) - } - } else { - oldDateFormatter.format(calendarTx.time) - } - - return date - } - - @Suppress("MagicNumber") - private fun String.reduceAddress(): String = "${substring(0..5)}...${substring(length - 4)}" -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt deleted file mode 100644 index 0363cffefe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.setDrawable -import com.tangem.tap.common.extensions.show -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.wallet.R -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding - -/** -[REDACTED_AUTHOR] - */ -internal class TxHistoryAdapter : ListAdapter(DiffUtilCallback) { - - override fun getItemId(position: Int): Long = if (currentList.isEmpty()) { - 0L - } else { - currentList[position].itemId - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH { - val inflater = LayoutInflater.from(parent.context) - - return when (viewType) { - 0 -> { - val layout = ItemSaltPayTxHistoryDateBinding.inflate(inflater, parent, false) - DateItemVH(layout) - } - 1 -> { - val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) - TransactionItemVH(layout) - } - else -> throw UnsupportedOperationException() - } - } - - override fun getItemViewType(position: Int): Int = currentList[position].viewType - - override fun onBindViewHolder(holder: BaseTxHistoryVH, position: Int) { - holder.bind(currentList[position]) - } - - private object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem - override fun areItemsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem - } -} - -internal abstract class BaseTxHistoryVH(layout: View) : RecyclerView.ViewHolder(layout) { - abstract fun bind(data: HistoryItemData) -} - -private class DateItemVH( - private val binding: ItemSaltPayTxHistoryDateBinding, -) : BaseTxHistoryVH(binding.root) { - - override fun bind(data: HistoryItemData) = with(binding) { - val vhData = (data as? HistoryItemData.Date)?.date ?: return - - tvTitleDate.text = vhData - } -} - -private class TransactionItemVH( - private val binding: ItemSaltPayTxHistoryBinding, -) : BaseTxHistoryVH(binding.root) { - - override fun bind(data: HistoryItemData) { - val vhData = (data as? HistoryItemData.TransactionData)?.data ?: return - setupImage(vhData) - setupTitle(vhData) - setupSubtitle(vhData) - setupBalance(vhData) - } - - private fun setupImage(data: HistoryTransactionData) = with(binding) { - val drawable = when (data.transactionType) { - PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming - PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing - PendingTransactionType.Unknown -> null - } - drawable?.let { imvTx.setDrawable(it) } - - if (data.isInProgress) { - imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.SRC_ATOP) - } else { - imvTx.clearColorFilter() - } - } - - private fun setupTitle(data: HistoryTransactionData) = with(binding) { - tvTxHash.text = data.address - } - - private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { - tvTxStatus.show(data.isInProgress) - tvTxTime.show(!data.isInProgress) - tvTxTime.text = data.time - } - - private fun setupBalance(data: HistoryTransactionData) = with(binding) { - tvTxAmountSign.text = data.txSign - tvTxAmountValue.text = data.amountValue - } -} \ No newline at end of file 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 077c90ca2c..f1c972ae02 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 @@ -22,7 +22,6 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull @@ -156,16 +155,8 @@ internal class WalletSelectorMiddleware { onFailure = { error -> // Rollback policy if card scanning was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) - when { - error is TangemSdkError.ExceptionError && error.cause is SaltPayActivationError -> { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - else -> { - Timber.e(error, "Unable to scan card") - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - } + Timber.e(error, "Unable to scan card") + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) }, ) } 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 a7dc158068..9f7143077a 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 @@ -20,7 +20,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.signin.redux.SignInAction import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -133,8 +132,8 @@ internal class WelcomeMiddleware { scope.launch { onCardScanned(scanResponse) } }, onFailure = { - when { - it is TangemSdkError.ExceptionError && it.cause is SaltPayActivationError -> { + when (it) { + is TangemSdkError.ExceptionError -> { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) } else -> { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt index 180f96317b..07804272ed 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt @@ -1,36 +1,26 @@ package com.tangem.tap.network.exchangeServices import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.models.scan.ProductType import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService -import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService import com.tangem.tap.scope import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] * Temporary wrapper for the buy services. Service switches based on selected product type. - * Just now - UtorgService used only for the SaltPay cards */ internal class BuyExchangeService( - private val productTypeProvider: () -> ProductType?, private val mercuryoService: MercuryoService, - private val utorgService: UtorgExchangeService, ) : ExchangeService, ExchangeUrlBuilder { init { scope.launch { mercuryoService.update() - utorgService.update() } } - private val currentService: ExchangeService - get() = when (productTypeProvider.invoke()) { - ProductType.SaltPay -> utorgService - else -> mercuryoService - } + private val currentService: ExchangeService = mercuryoService override suspend fun update() { currentService.update() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt deleted file mode 100644 index 0aac5a8b43..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg - -import android.net.Uri -import com.tangem.datasource.api.common.createRetrofitInstance -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi -import com.tangem.tap.network.exchangeServices.utorg.mock.MockUtorgApi -import okhttp3.Interceptor -import okhttp3.Response -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -data class UtorgEnvironment( - val baseUri: Uri, - val sidValue: String, - val apiVersion: String, - val utorgApi: UtorgApi, - val successUrl: String = "https://success.tangem.com", -) { - companion object { - private val PROD_BASE_URL = Uri.parse("https://app.utorg.pro") - private const val PROD_VERSION = "v1" - - private val STAGE_BASE_URL = Uri.parse("https://app-stage.utorg.pro") - private const val STAGE_VERSION = "v1" - - fun prod(authProvider: UtorgAuthProvider, apiVersion: String = PROD_VERSION): UtorgEnvironment = - UtorgEnvironment( - baseUri = PROD_BASE_URL, - sidValue = authProvider.sidValue, - apiVersion = apiVersion, - utorgApi = createApi(PROD_BASE_URL, authProvider, false), - ) - - fun stage( - authProvider: UtorgAuthProvider, - logEnabled: Boolean, - apiVersion: String = STAGE_VERSION, - ): UtorgEnvironment = UtorgEnvironment( - baseUri = STAGE_BASE_URL, - sidValue = authProvider.sidValue, - apiVersion = apiVersion, - utorgApi = createApi(STAGE_BASE_URL, authProvider, logEnabled), - ) - - fun mock(): UtorgEnvironment = UtorgEnvironment( - baseUri = Uri.EMPTY, - sidValue = "", - apiVersion = "", - utorgApi = MockUtorgApi(), - ) - - private fun createApi(baseUri: Uri, authProvider: UtorgAuthProvider, logEnabled: Boolean): UtorgApi { - return createRetrofitInstance( - baseUrl = "$baseUri/", - interceptors = listOf(UtorgAuthHeaderInterceptor(authProvider)), - logEnabled = logEnabled, - ).create(UtorgApi::class.java) - } - } -} - -data class UtorgAuthProvider( - val sidValue: String, - val headerSidKey: String = "X-AUTH-SID", - val headerNonceKey: String = "X-AUTH-NONCE", -) { - val clientNonce: String - get() = UUID.randomUUID().toString() -} - -private class UtorgAuthHeaderInterceptor( - private val authProvider: UtorgAuthProvider, -) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - .newBuilder() - .addHeader(authProvider.headerSidKey, authProvider.sidValue) - .addHeader(authProvider.headerNonceKey, authProvider.clientNonce) - .build() - - return chain.proceed(request) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt deleted file mode 100644 index 820751a14b..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg - -import android.net.Uri -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyData -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyType -import com.tangem.utils.converter.Converter - -/** -[REDACTED_AUTHOR] - */ -class UtorgExchangeService( - private val environment: UtorgEnvironment, - private val currencyConverter: Converter = ChainToBlockchainConverter(), -) : ExchangeService { - - private val api = environment.utorgApi - - private val utorgCurrencies = mutableListOf() - - override fun featureIsSwitchedOn(): Boolean = true - - override suspend fun update() { - when (val result = performRequest { api.getCurrency(environment.apiVersion) }) { - is Result.Success -> { - if (!result.data.isSuccess()) return - - val utorgCryptos = result.data.toSuccess().data.filter { - it.enabled && it.type == UtorgCurrencyType.CRYPTO - } - utorgCurrencies.clear() - utorgCurrencies.addAll(utorgCryptos) - - performRequest { - api.setSuccessUrl(environment.apiVersion, RequestSuccessUrl(environment.successUrl)) - } - } - is Result.Failure -> { - utorgCurrencies.clear() - } - } - } - - override fun isBuyAllowed(): Boolean = true - - override fun isSellAllowed(): Boolean = false - - override fun availableForBuy(currency: Currency): Boolean { - if (!isBuyAllowed()) return false - - val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency -> - val utorgBlockchain = currencyConverter.convert(utorgCurrency) ?: return@firstOrNull false - - val isSameBlockchain = utorgBlockchain == currency.blockchain - val isSameSymbol = utorgCurrency.symbol.lowercase() == currency.currencySymbol.lowercase() - isSameBlockchain && isSameSymbol - } - - return foundUtorgCurrency != null - } - - override fun availableForSell(currency: Currency): Boolean = false - - override fun getUrl( - action: CurrencyExchangeManager.Action, - blockchain: Blockchain, - cryptoCurrencyName: CryptoCurrencyName, - fiatCurrencyName: String, - walletAddress: String, - ): String { - if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() - - // https://app-stage.utorg.pro/direct/testSID/mvmeaWyWiuVYdZdySgofAt6CmKhJbRhaWA/?¤cy=BTC - val builder = Uri.Builder() - .scheme(environment.baseUri.scheme) - .authority(environment.baseUri.authority) - .appendPath("direct") - .appendPath(environment.sidValue) - .appendPath(walletAddress) - .appendQueryParameter("currency", cryptoCurrencyName) - // if we set the paymentCurrency, then the Utorg widget didn't work - // .appendQueryParameter("paymentCurrency", "USD") - - val url = builder.build().toString() - return url - } - - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null -} - -private class ChainToBlockchainConverter : Converter { - override fun convert(value: UtorgCurrencyData): Blockchain? { - return when (value.chain?.uppercase()) { - "ARBITRUM" -> Blockchain.Arbitrum - "AVALANCHE" -> Blockchain.Avalanche - "BINANCE_SMART_CHAIN" -> Blockchain.BSC - "BITCOIN" -> Blockchain.Bitcoin - "BNB" -> Blockchain.Binance - "ETHEREUM" -> Blockchain.Ethereum - "GNOSIS" -> Blockchain.Gnosis - "POLYGON" -> Blockchain.Polygon - "RIPPLE" -> Blockchain.XRP - "RSK" -> Blockchain.RSK - "SOLANA" -> Blockchain.Solana - else -> null - // "APTOS", "ATOM", "NEAR", "ZKSYNC", "VECHAIN", "VELAS", "VELAS_EVM" -> null - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt deleted file mode 100644 index b18618e38b..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.api - -import com.squareup.moshi.Json -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse -import retrofit2.http.Body -import retrofit2.http.POST -import retrofit2.http.Path - -/** -[REDACTED_AUTHOR] - */ -interface UtorgApi { - - @POST("api/merchant/{apiVersion}/settings/currency") - suspend fun getCurrency(@Path("apiVersion") apiVersion: String): UtorgCurrencyResponse - - @POST("api/merchant/{apiVersion}/settings/successUrl") - suspend fun setSuccessUrl(@Path("apiVersion") apiVersion: String, @Body request: RequestSuccessUrl) -} - -data class RequestSuccessUrl( - val url: String, -) - -interface UtorgResponse { - val success: Boolean - val timestamp: Long - val data: Data? - val error: UtorgErrorResponse? - - fun isSuccess(): Boolean = success && data != null && error == null - - @Throws(NullPointerException::class) - fun toSuccess(): UtorgSuccessResponse { - val internalTimestamp = this.timestamp - val internalData = this.data - - return object : UtorgSuccessResponse { - override val timestamp: Long = internalTimestamp - override val data: Data = internalData!! - } - } - - @Throws(NullPointerException::class) - fun toError(): UtorgErrorResponse = error!! -} - -interface UtorgSuccessResponse { - val timestamp: Long - val data: T -} - -data class UtorgErrorResponse( - @Json(name = "message") val message: String?, - @Json(name = "type") val type: UtorgErrorType, -) - -enum class UtorgErrorType { - UNAUTHORIZED, - UNKNOWN_ERROR, - BAD_REQUEST, -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt deleted file mode 100644 index 5df98687d8..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.api.model - -import com.squareup.moshi.Json -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgResponse - -class UtorgCurrencyResponse( - @Json(name = "success") override val success: Boolean, - @Json(name = "timestamp") override val timestamp: Long, - @Json(name = "data") override val data: List?, - @Json(name = "error") override val error: UtorgErrorResponse?, -) : UtorgResponse> - -data class UtorgCurrencyData( - @Json(name = "currency") val currency: String, - @Json(name = "symbol") val symbol: String, - @Json(name = "enabled") val enabled: Boolean, - @Json(name = "type") val type: UtorgCurrencyType, - @Json(name = "caption") val caption: String?, - @Json(name = "chain") val chain: String?, -) - -enum class UtorgCurrencyType { - FIAT, CRYPTO -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt deleted file mode 100644 index e50b8b8833..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock - -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorType -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse -import kotlinx.coroutines.delay - -/** -[REDACTED_AUTHOR] - */ -class MockUtorgApi : UtorgApi { - - var nextDelay = 500L - - var nextResponseType = ResponseType.Success - var nextErrorType = UtorgErrorType.UNKNOWN_ERROR - - override suspend fun getCurrency(apiVersion: String): UtorgCurrencyResponse { - delay(nextDelay) - return UtorgCurrencyResponse( - success = createIsSuccess(), - timestamp = createTime(), - data = createData(MockUtorgSuccessDataResponse.GetCurrency), - error = createError(), - ) - } - - override suspend fun setSuccessUrl(apiVersion: String, request: RequestSuccessUrl) { - delay(nextDelay) - } - - private fun createIsSuccess(): Boolean = when (nextResponseType) { - ResponseType.Success -> true - ResponseType.Error -> false - } - - private fun createTime(): Long = System.currentTimeMillis() - - private inline fun createData(response: MockUtorgSuccessDataResponse): T? = when (nextResponseType) { - ResponseType.Success -> MoshiJsonConverter.INSTANCE.fromJson(response.json)!! - ResponseType.Error -> null - } - - private fun createError(): UtorgErrorResponse? = when (nextResponseType) { - ResponseType.Success -> null - ResponseType.Error -> UtorgErrorResponse( - message = "Optional message", - type = nextErrorType, - ) - } - - enum class ResponseType { - Success, Error - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt deleted file mode 100644 index 959e65a4d5..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock - -import com.tangem.tap.network.exchangeServices.utorg.mock.json.getCurrencyFull - -/** -[REDACTED_AUTHOR] - */ -sealed class MockUtorgSuccessDataResponse( - val json: String, -) { - object GetCurrency : MockUtorgSuccessDataResponse(getCurrencyFull) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt deleted file mode 100644 index 3d50462305..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt +++ /dev/null @@ -1,1025 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock.json - -/** -[REDACTED_AUTHOR] - */ -internal val getCurrencyFull = """ -[ - { - "currency": "APT", - "symbol": "APT", - "chain": "APTOS", - "display": "APT", - "caption": "Aptos", - "explorerTx": "https://aptoscan.com/version/", - "explorerAddr": "https://aptoscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.8492, - "depositMax": 739.6673, - "withdrawalMin": 1.8492, - "withdrawalMax": 739.6673, - "addressValidator": "APT", - "precision": 4, - "allowTag": false - }, - { - "currency": "ATOM", - "symbol": "ATOM", - "chain": "ATOM", - "display": "ATOM", - "caption": "Cosmos", - "explorerTx": "https://atomscan.com/transactions/", - "explorerAddr": "https://atomscan.com/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.8020, - "depositMax": 720.7911, - "withdrawalMin": 1.8020, - "withdrawalMax": 720.7911, - "addressValidator": "COSMOS", - "precision": 4, - "allowTag": true, - "udKey": "crypto.ATOM.address" - }, - { - "currency": "AUD", - "symbol": "AUD", - "display": "AUD", - "type": "FIAT", - "enabled": true, - "depositMin": 43.73, - "depositMax": 15013.68, - "withdrawalMin": 43.73, - "withdrawalMax": 15013.68, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "AVAXAVA", - "symbol": "AVAX", - "chain": "AVALANCHE", - "display": "Avax", - "caption": "Avalanche", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.337428, - "depositMax": 534.971102, - "withdrawalMin": 1.337428, - "withdrawalMax": 534.971102, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false, - "udKey": "crypto.AVAX.address" - }, - { - "currency": "BNB", - "symbol": "BNB", - "chain": "BNB", - "display": "BNB", - "caption": "BEP2", - "explorerTx": "https://explorer.binance.org/tx/", - "explorerAddr": "https://explorer.binance.org/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.082738, - "depositMax": 33.095138, - "withdrawalMin": 0.082738, - "withdrawalMax": 33.095138, - "addressValidator": "BNB", - "precision": 6, - "allowTag": true, - "udKey": "crypto.BNB.address" - }, - { - "currency": "BNBBSC", - "symbol": "BNB", - "chain": "BINANCE_SMART_CHAIN", - "display": "BNB", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.0828, - "depositMax": 33.0951, - "withdrawalMin": 0.0828, - "withdrawalMax": 33.0951, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "BRL", - "symbol": "BRL", - "display": "BRL", - "type": "FIAT", - "enabled": true, - "depositMin": 158.08, - "depositMax": 54293.21, - "withdrawalMin": 158.08, - "withdrawalMax": 54293.21, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "BTC", - "symbol": "BTC", - "chain": "BITCOIN", - "display": "BTC", - "caption": "", - "explorerTx": "https://blockstream.info/tx/", - "explorerAddr": "https://blockstream.info/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 528764.805415, - "depositMax": 211505922.165820, - "withdrawalMin": 528764.805415, - "withdrawalMax": 211505922.165820, - "addressValidator": "BTC", - "precision": 6, - "allowTag": false, - "udKey": "crypto.BTC.address" - }, - { - "currency": "BUSDAVA", - "symbol": "BUSD", - "chain": "AVALANCHE", - "display": "BUSD", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "BUSDBSC", - "symbol": "BUSD", - "chain": "BINANCE_SMART_CHAIN", - "display": "BUSD", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6414, - "depositMax": 10656.5563, - "withdrawalMin": 26.6414, - "withdrawalMax": 10656.5563, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.BUSD.version.BEP20.address" - }, - { - "currency": "BUSDETH", - "symbol": "BUSD", - "chain": "ETHEREUM", - "display": "BUSD", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6414, - "depositMax": 10656.5563, - "withdrawalMin": 26.6414, - "withdrawalMax": 10656.5563, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.BUSD.version.ERC20.address" - }, - { - "currency": "CAD", - "symbol": "CAD", - "display": "CAD", - "type": "FIAT", - "enabled": true, - "depositMin": 40.85, - "depositMax": 14024.33, - "withdrawalMin": 40.85, - "withdrawalMax": 14024.33, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "CZK", - "symbol": "CZK", - "display": "CZK", - "type": "FIAT", - "enabled": true, - "depositMin": 672.71, - "depositMax": 231068.12, - "withdrawalMin": 672.71, - "withdrawalMax": 231068.12, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "DAIBSC", - "symbol": "DAI", - "chain": "BINANCE_SMART_CHAIN", - "display": "DAI", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6462, - "depositMax": 10658.4634, - "withdrawalMin": 26.6462, - "withdrawalMax": 10658.4634, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DAIEAVA", - "symbol": "DAIE", - "chain": "AVALANCHE", - "display": "DAI.e", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6052, - "depositMax": 10642.0513, - "withdrawalMin": 26.6052, - "withdrawalMax": 10642.0513, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DAIETH", - "symbol": "DAI", - "chain": "ETHEREUM", - "display": "DAI", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6436, - "depositMax": 10657.4090, - "withdrawalMin": 26.6436, - "withdrawalMax": 10657.4090, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.DAI.address" - }, - { - "currency": "DAIPOL", - "symbol": "DAI", - "chain": "POLYGON", - "display": "DAI", - "caption": "on Polygon", - "explorerTx": "https://polygonscan.com/tx/", - "explorerAddr": "https://polygonscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6436, - "depositMax": 10657.4090, - "withdrawalMin": 26.6436, - "withdrawalMax": 10657.4090, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DKK", - "symbol": "DKK", - "display": "DKK", - "type": "FIAT", - "enabled": true, - "depositMin": 210.64, - "depositMax": 72348.14, - "withdrawalMin": 210.64, - "withdrawalMax": 72348.14, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "ETH", - "symbol": "ETH", - "chain": "ETHEREUM", - "display": "ETH", - "caption": "", - "explorerTx": "https://ropsten.etherscan.io/tx/", - "explorerAddr": "https://ropsten.etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 37688.767964, - "depositMax": 15075507.185288, - "withdrawalMin": 37688.767964, - "withdrawalMax": 15075507.185288, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false, - "udKey": "crypto.ETH.address" - }, - { - "currency": "EUR", - "symbol": "EUR", - "display": "EUR", - "caption": "", - "type": "FIAT", - "enabled": true, - "depositMin": 27.24, - "depositMax": 9349.53, - "withdrawalMin": 27.24, - "withdrawalMax": 9349.53, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "GBP", - "symbol": "GBP", - "display": "GBP", - "type": "FIAT", - "enabled": true, - "depositMin": 25.14, - "depositMax": 8630.60, - "withdrawalMin": 25.14, - "withdrawalMax": 8630.60, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "GMTBSC", - "symbol": "GMT", - "chain": "BINANCE_SMART_CHAIN", - "display": "GMT", - "caption": "BEP20", - "explorerTx": "https://bscscan.com/tx/", - "explorerAddr": "https://bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 53.9899, - "depositMax": 21595.9496, - "withdrawalMin": 53.9899, - "withdrawalMax": 21595.9496, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "GNOETH", - "symbol": "GNO", - "chain": "ETHEREUM", - "display": "GNO", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.2250, - "depositMax": 89.9938, - "withdrawalMin": 0.2250, - "withdrawalMax": 89.9938, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.GNO.address" - }, - { - "currency": "HAIBSC", - "symbol": "HAI", - "chain": "BINANCE_SMART_CHAIN", - "display": "HAI", - "caption": "on BSC", - "explorerTx": "https://bscscan.com/tx/", - "explorerAddr": "https://bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 937.548948, - "depositMax": 375019.579098, - "withdrawalMin": 937.548948, - "withdrawalMax": 375019.579098, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false - }, - { - "currency": "HAIVET", - "symbol": "HAI", - "chain": "VECHAIN", - "display": "HAI", - "caption": "VET", - "explorerTx": "https://vechainstats.com/transaction/", - "explorerAddr": "https://vechainstats.com/account/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 949.3974, - "depositMax": 379758.9569, - "withdrawalMin": 949.3974, - "withdrawalMax": 379758.9569, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "IDR", - "symbol": "IDR", - "display": "IDR", - "type": "FIAT", - "enabled": true, - "depositMin": 459558, - "depositMax": 157855988, - "withdrawalMin": 459558, - "withdrawalMax": 157855988, - "addressValidator": "LUHN", - "precision": 0, - "allowTag": false - }, - { - "currency": "INR", - "symbol": "INR", - "display": "INR", - "type": "FIAT", - "enabled": true, - "depositMin": 2511.35, - "depositMax": 862635.95, - "withdrawalMin": 2511.35, - "withdrawalMax": 862635.95, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "JPY", - "symbol": "JPY", - "display": "JPY", - "type": "FIAT", - "enabled": true, - "depositMin": 3992, - "depositMax": 1370884, - "withdrawalMin": 3992, - "withdrawalMax": 1370884, - "addressValidator": "LUHN", - "precision": 0, - "allowTag": false - }, - { - "currency": "KZT", - "symbol": "KZT", - "display": "KZT", - "type": "FIAT", - "enabled": true, - "depositMin": 13843.18, - "depositMax": 4755080.66, - "withdrawalMin": 13843.18, - "withdrawalMax": 4755080.66, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "MH3ETH", - "symbol": "MH3", - "chain": "ETHEREUM", - "display": "NFT", - "caption": "Meta History", - "explorerTx": "https://rinkeby.etherscan.io/tx/", - "explorerAddr": "https://rinkeby.etherscan.io/address/", - "type": "CRYPTO", - "enabled": false, - "depositMin": 0, - "depositMax": 0, - "withdrawalMin": 0, - "withdrawalMax": 0, - "addressValidator": "ETH", - "precision": 0, - "allowTag": false, - "nftContract": "0xf0192b97135abaa218bcff255b8f4b22171a7590" - }, - { - "currency": "MXN", - "symbol": "MXN", - "display": "MXN", - "type": "FIAT", - "enabled": true, - "depositMin": 574.83, - "depositMax": 197445.63, - "withdrawalMin": 574.83, - "withdrawalMax": 197445.63, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "MYR", - "symbol": "MYR", - "display": "MYR", - "type": "FIAT", - "enabled": true, - "depositMin": 131.04, - "depositMax": 45006.86, - "withdrawalMin": 131.04, - "withdrawalMax": 45006.86, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "NEAR", - "symbol": "NEAR", - "chain": "NEAR", - "display": "NEAR", - "caption": "", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 10.560163, - "depositMax": 4224.064906, - "withdrawalMin": 10.560163, - "withdrawalMax": 4224.064906, - "addressValidator": "NEAR", - "precision": 6, - "allowTag": false, - "udKey": "crypto.NEAR.address" - }, - { - "currency": "NOK", - "symbol": "NOK", - "display": "NOK", - "type": "FIAT", - "enabled": true, - "depositMin": 312.23, - "depositMax": 107245.60, - "withdrawalMin": 312.23, - "withdrawalMax": 107245.60, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "NXUSDAVA", - "symbol": "NXUSD", - "chain": "AVALANCHE", - "display": "NXUSD", - "caption": "Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.8766, - "depositMax": 10750.6217, - "withdrawalMin": 26.8766, - "withdrawalMax": 10750.6217, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "NZD", - "symbol": "NZD", - "display": "NZD", - "type": "FIAT", - "enabled": true, - "depositMin": 47.90, - "depositMax": 16448.49, - "withdrawalMin": 47.90, - "withdrawalMax": 16448.49, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "PLN", - "symbol": "PLN", - "display": "PLN", - "type": "FIAT", - "enabled": true, - "depositMin": 134.06, - "depositMax": 46047.85, - "withdrawalMin": 134.06, - "withdrawalMax": 46047.85, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "RIF", - "symbol": "RIF", - "chain": "RSK", - "display": "RIF", - "caption": "RSK", - "explorerTx": "https://explorer.rsk.co/tx/", - "explorerAddr": "https://explorer.rsk.co/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 429.65, - "depositMax": 171859.31, - "withdrawalMin": 429.65, - "withdrawalMax": 171859.31, - "addressValidator": "ETH", - "precision": 2, - "allowTag": false, - "udKey": "crypto.RIF.address" - }, - { - "currency": "RUB", - "symbol": "RUB", - "display": "RUB", - "type": "FIAT", - "enabled": true, - "depositMin": 2217.40, - "depositMax": 761666.68, - "withdrawalMin": 2217.40, - "withdrawalMax": 761666.68, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "SOL", - "symbol": "SOL", - "chain": "SOLANA", - "display": "SOL", - "caption": "Solana", - "explorerTx": "https://explorer.solana.com/tx/", - "explorerAddr": "https://explorer.solana.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.155094, - "depositMax": 462.037519, - "withdrawalMin": 1.155094, - "withdrawalMax": 462.037519, - "addressValidator": "SOLANA", - "precision": 6, - "allowTag": false, - "udKey": "crypto.SOL.address" - }, - { - "currency": "TRY", - "symbol": "TRY", - "display": "TRY", - "type": "FIAT", - "enabled": true, - "depositMin": 572.28, - "depositMax": 196571.08, - "withdrawalMin": 572.28, - "withdrawalMax": 196571.08, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "TWTBNB", - "symbol": "TWT", - "chain": "BNB", - "display": "TWT", - "caption": "BEP2", - "explorerTx": "https://explorer.binance.org/tx/", - "explorerAddr": "https://explorer.binance.org/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 17.4457, - "depositMax": 6978.2637, - "withdrawalMin": 17.4457, - "withdrawalMax": 6978.2637, - "addressValidator": "BNB", - "precision": 4, - "allowTag": true, - "udKey": "crypto.TWT.address" - }, - { - "currency": "UAH", - "symbol": "UAH", - "display": "UAH", - "caption": "", - "type": "FIAT", - "enabled": true, - "depositMin": 1118.00, - "depositMax": 14005.59, - "withdrawalMin": 1118.00, - "withdrawalMax": 14005.59, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "USD", - "symbol": "USD", - "display": "USD", - "type": "FIAT", - "enabled": true, - "depositMin": 30.57, - "depositMax": 10382.96, - "withdrawalMin": 30.57, - "withdrawalMax": 10382.96, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "USDCARB", - "symbol": "USDC", - "chain": "ARBITRUM", - "display": "USDC", - "caption": "on arbitrum", - "explorerTx": "https://arbiscan.io/tx/", - "explorerAddr": "https://arbiscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCBSC", - "symbol": "USDC", - "chain": "BINANCE_SMART_CHAIN", - "display": "USDC", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": false, - "depositMin": 0, - "depositMax": 0, - "withdrawalMin": 0, - "withdrawalMax": 0, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCEAVA", - "symbol": "USDCE", - "chain": "AVALANCHE", - "display": "USDC.e", - "caption": "on Avalanche", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6078, - "depositMax": 10643.1155, - "withdrawalMin": 26.6078, - "withdrawalMax": 10643.1155, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCETH", - "symbol": "USDC", - "chain": "ETHEREUM", - "display": "USDC", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.USDC.address" - }, - { - "currency": "USDCZK", - "symbol": "USDC", - "chain": "ZKSYNC", - "display": "USDC", - "caption": "on zkSync", - "explorerTx": "https://zkscan.io/explorer/transactions/", - "explorerAddr": "https://zkscan.io/explorer/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6078, - "depositMax": 10643.1155, - "withdrawalMin": 26.6078, - "withdrawalMax": 10643.1155, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDTAVA", - "symbol": "USDT", - "chain": "AVALANCHE", - "display": "USDT", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6649, - "depositMax": 10665.9328, - "withdrawalMin": 26.6649, - "withdrawalMax": 10665.9328, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDTE", - "symbol": "USDT", - "chain": "ETHEREUM", - "display": "USDT", - "caption": "", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.66483213, - "depositMax": 10665.93285048, - "withdrawalMin": 26.66483213, - "withdrawalMax": 10665.93285048, - "addressValidator": "ETH", - "precision": 8, - "allowTag": false, - "udKey": "crypto.USDT.version.ERC20.address" - }, - { - "currency": "USDTPOL", - "symbol": "USDT", - "chain": "POLYGON", - "display": "USDT", - "caption": "on Polygon", - "explorerTx": "https://polygonscan.com/tx/", - "explorerAddr": "https://polygonscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6649, - "depositMax": 10665.9328, - "withdrawalMin": 26.6649, - "withdrawalMax": 10665.9328, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDVEL", - "symbol": "USDV", - "chain": "VELAS_EVM", - "display": "USDV", - "caption": "Velas EVM", - "explorerTx": "https://evmexplorer.velas.com/tx/", - "explorerAddr": "https://evmexplorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26611608.908400, - "depositMax": 10644643563.359833, - "withdrawalMin": 26611608.908400, - "withdrawalMax": 10644643563.359833, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false - }, - { - "currency": "USNNEAR", - "symbol": "USN", - "chain": "NEAR", - "display": "USN", - "caption": "Near", - "explorerTx": "https://explorer.near.org/transactions/", - "explorerAddr": "https://explorer.near.org/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6969, - "depositMax": 10678.7473, - "withdrawalMin": 26.6969, - "withdrawalMax": 10678.7473, - "addressValidator": "NEAR", - "precision": 4, - "allowTag": false - }, - { - "currency": "VLX", - "symbol": "VLX", - "chain": "VELAS", - "display": "VLX", - "caption": "Velas", - "explorerTx": "https://explorer.velas.com/tx/", - "explorerAddr": "https://explorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1021.951187, - "depositMax": 408780.474783, - "withdrawalMin": 1021.951187, - "withdrawalMax": 408780.474783, - "addressValidator": "SOLANA", - "precision": 6, - "allowTag": false, - "udKey": "crypto.VLX.address" - }, - { - "currency": "VLXETH", - "symbol": "VLX", - "chain": "VELAS_EVM", - "display": "VLX", - "caption": "Velas EVM", - "explorerTx": "https://evmexplorer.velas.com/tx/", - "explorerAddr": "https://evmexplorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1021.9512, - "depositMax": 408780.4747, - "withdrawalMin": 1021.9512, - "withdrawalMax": 408780.4747, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "WLKNSOL", - "symbol": "WLKN", - "chain": "SOLANA", - "display": "WLKN", - "caption": "Solana", - "explorerTx": "https://explorer.solana.com/tx/", - "explorerAddr": "https://explorer.solana.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 531.89, - "depositMax": 212755.45, - "withdrawalMin": 531.89, - "withdrawalMax": 212755.45, - "addressValidator": "SOLANA", - "precision": 2, - "allowTag": false - }, - { - "currency": "XDAIGNO", - "symbol": "xDAI", - "chain": "GNOSIS", - "display": "xDAI", - "caption": "Gnosis chain", - "explorerTx": "https://blockscout.com/xdai/mainnet/tx/", - "explorerAddr": "https://blockscout.com/xdai/mainnet/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6782, - "depositMax": 10671.2684, - "withdrawalMin": 26.6782, - "withdrawalMax": 10671.2684, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "XRP", - "symbol": "XRP", - "chain": "RIPPLE", - "display": "XRP", - "caption": "Ripple", - "explorerTx": "https://xrpscan.com/tx/", - "explorerAddr": "https://xrpscan.com/account/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 67.7645, - "depositMax": 27105.7684, - "withdrawalMin": 67.7645, - "withdrawalMax": 27105.7684, - "addressValidator": "XRP", - "precision": 4, - "allowTag": true, - "udKey": "crypto.XRP.address" - }, - { - "currency": "ZAR", - "symbol": "ZAR", - "display": "ZAR", - "type": "FIAT", - "enabled": true, - "depositMin": 538.27, - "depositMax": 184892.03, - "withdrawalMin": 538.27, - "withdrawalMax": 184892.03, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - } - ] -""".trimIndent() \ No newline at end of file diff --git a/app/src/main/res/color/selector_saltpay_btn.xml b/app/src/main/res/color/selector_saltpay_btn.xml deleted file mode 100644 index 98dd6f0aba..0000000000 --- a/app/src/main/res/color/selector_saltpay_btn.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_saltpay_btn_text.xml b/app/src/main/res/color/selector_saltpay_btn_text.xml deleted file mode 100644 index 28e2f2cfdd..0000000000 --- a/app/src/main/res/color/selector_saltpay_btn_text.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/img_salt_pay_visa.png b/app/src/main/res/drawable/img_salt_pay_visa.png deleted file mode 100644 index 02b199c1e5a6c5a92ec9809f19156b62157401ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28037 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 - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 59a23e9ab4..821f0d3ef8 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -221,13 +221,6 @@ - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 6f1a62cabf..b4287625d4 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -177,15 +177,6 @@ android:visibility="gone" app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" /> - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml deleted file mode 100644 index a21c2dbd6e..0000000000 --- a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml deleted file mode 100644 index 72aff47fa0..0000000000 --- a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay.xml b/app/src/main/res/layout/layout_onboarding_saltpay.xml deleted file mode 100644 index fbb3534b9b..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml b/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml deleted file mode 100644 index 5e3ff40fb1..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml deleted file mode 100644 index 0114973c36..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml deleted file mode 100644 index 4a9ef0a9e2..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml b/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml deleted file mode 100644 index 96562a0c1f..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_balance.xml b/app/src/main/res/layout/layout_salt_pay_balance.xml deleted file mode 100644 index 8ce65d0b6b..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_balance.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_tx_history.xml b/app/src/main/res/layout/layout_salt_pay_tx_history.xml deleted file mode 100644 index 2aef592910..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_tx_history.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_wallet.xml b/app/src/main/res/layout/layout_salt_pay_wallet.xml deleted file mode 100644 index 4712e76937..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_wallet.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9356f81daf..98addf9c01 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -97,19 +97,6 @@ 4sp - -