diff --git a/app/build.gradle b/app/build.gradle index 686a683503..3ff4d89620 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,13 +72,14 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' implementation 'com.google.android.material:material:1.4.0' + implementation "androidx.core:core-ktx:1.6.0" implementation 'com.google.android.play:core:1.10.2' implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-48' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-95' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-95' + implementation 'com.tangem:blockchain:develop-49' + implementation 'com.tangem.tangem-sdk-kotlin:core:CSK-206_backup-98' + implementation 'com.tangem.tangem-sdk-kotlin:android:CSK-206_backup-98' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index c0d6abea79..43eaa90ca3 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -8,6 +8,7 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import com.google.android.material.snackbar.Snackbar import com.tangem.TangemSdk +import com.tangem.operations.backup.BackupService import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.DialogManager import com.tangem.tap.common.IntentHandler @@ -32,6 +33,7 @@ import kotlin.coroutines.CoroutineContext lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager +lateinit var backupService: BackupService var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext @@ -67,6 +69,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { tangemSdk = TangemSdk.init(this, TangemSdkManager.config) tangemSdkManager = TangemSdkManager(tangemSdk, this) + backupService = BackupService.init(tangemSdk, this) store.dispatch(GlobalAction.SetResources(getAndroidResources())) store.dispatch(WalletConnectAction.RestoreSessions) @@ -94,7 +97,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanResponse != null val isOnboardingServiceActive = store.state.globalState.onboardingManager != null - if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { + if (backStackIsEmpty && (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } intentHandler.handleIntent(intent) 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 b3a0c345e2..6533bab1a3 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -9,6 +9,11 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.* import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog +import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog +import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BuyMoreBackupCardsDialog +import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog +import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -74,6 +79,10 @@ class DialogManager : StoreSubscriber { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) + is BackupDialog.BuyMoreBackupCards -> BuyMoreBackupCardsDialog.create(context) + is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) + is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) + is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt index 0d6b58db28..5ef395e4ad 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt @@ -72,9 +72,37 @@ object TangemSdk { is TangemSdkError.WalletCannotBeCreated -> TangemSdkError.WalletCannotBeCreated() is TangemSdkError.UnsupportedCurve -> TangemSdkError.UnsupportedCurve() is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig() - is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError() + is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError(error.customMessage) is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage) is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable) + is TangemSdkError.TooMuchBackupCards -> TangemSdkError.TooMuchBackupCards() + is TangemSdkError.BackupCardRequired -> TangemSdkError.BackupCardRequired() + is TangemSdkError.CertificateSignatureRequired -> TangemSdkError.CertificateSignatureRequired() + is TangemSdkError.AccessCodeOrPasscodeRequired -> TangemSdkError.AccessCodeOrPasscodeRequired() + is TangemSdkError.ResetPinNoCardsToReset -> TangemSdkError.ResetPinNoCardsToReset() + is TangemSdkError.ResetPinWrongCard -> TangemSdkError.ResetPinWrongCard() + is TangemSdkError.BackupFailedCardNotLinked -> TangemSdkError.BackupFailedCardNotLinked() + is TangemSdkError.BackupNotAllowed -> TangemSdkError.BackupNotAllowed() + is TangemSdkError.BackupCardAlreadyAdded -> TangemSdkError.BackupCardAlreadyAdded() + is TangemSdkError.MissingPrimaryCard -> TangemSdkError.MissingPrimaryCard() + is TangemSdkError.MissingPrimaryAttestSignature -> TangemSdkError.MissingPrimaryAttestSignature() + is TangemSdkError.NoBackupDataForCard -> TangemSdkError.NoBackupDataForCard() + is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets() + is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets() + is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup() + is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets() + is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState() + is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex() + is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards() + is TangemSdkError.BackupFailedWrongIssuer -> TangemSdkError.BackupFailedWrongIssuer() + is TangemSdkError.BackupFailedHDWalletSettings -> TangemSdkError.BackupFailedHDWalletSettings() + is TangemSdkError.BackupFailedNotEnoughCurves -> TangemSdkError.BackupFailedNotEnoughCurves() + is TangemSdkError.BackupFailedNotEnoughWallets -> TangemSdkError.BackupFailedNotEnoughWallets() + is TangemSdkError.FileSettingsUnsupported -> TangemSdkError.FileSettingsUnsupported() + is TangemSdkError.FilesIsEmpty -> TangemSdkError.FilesIsEmpty() + is TangemSdkError.FilesDisabled -> TangemSdkError.FilesDisabled() + is TangemSdkError.HDWalletDisabled -> TangemSdkError.HDWalletDisabled() + is TangemSdkError.WrongInteractionMode -> TangemSdkError.WrongInteractionMode() } } 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 f96f16f5ee..36723da434 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 @@ -17,7 +17,7 @@ import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment -import com.tangem.tap.features.onboarding.products.wallet.OnboardingWalletFragment +import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment 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 39b221f517..d3c6f3656b 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 @@ -18,6 +18,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsMiddleware 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.send.redux.middlewares.SendMiddleware @@ -60,7 +61,8 @@ data class AppState( DetailsMiddleware().detailsMiddleware, DisclaimerMiddleware().disclaimerMiddleware, TokensMiddleware().tokensMiddleware, - WalletConnectMiddleware().walletConnectMiddleware + WalletConnectMiddleware().walletConnectMiddleware, + BackupMiddleware().backupMiddleware ) } } 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 a1e1e7b2b1..af62d98fff 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 @@ -31,7 +31,7 @@ sealed class GlobalAction : Action { object HideDialog : GlobalAction() sealed class Onboarding { - data class Start(val scanResponse: ScanResponse) : GlobalAction() + data class Start(val scanResponse: ScanResponse, val fromHomeScreen: Boolean = true) : GlobalAction() object Stop : GlobalAction() } 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 27cb46c500..695f3b83f0 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -16,11 +16,11 @@ import com.tangem.operations.CommandResponse import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand -import com.tangem.operations.wallet.PurgeWalletCommand import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask +import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.wallet.R @@ -66,11 +66,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body))) } - suspend fun eraseWallet(card: Card): CompletionResult { + suspend fun resetToFactorySettings(card: Card): CompletionResult { return runTaskAsyncReturnOnMain( - PurgeWalletCommand(card.wallets.first().publicKey), + ResetToFactorySettingsTask(), card.cardId, - initialMessage = Message(context.getString(R.string.initial_message_purge_wallet_body))) + initialMessage = Message(context.getString(R.string.details_row_title_reset_factory_settings))) } suspend fun setPasscode(cardId: String?): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt index 70ef2df88e..45e19eb81a 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -29,8 +29,7 @@ object TapWorkarounds { } fun Card.isNotSupportedInThatRelease():Boolean { - return tangemWalletBatches.contains(batchId) - + return false } @Deprecated("Use ScanResponse.isTangemNote") diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt index f1024cdf11..12ce0fdb66 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt @@ -30,7 +30,9 @@ class CreateProductWalletAndRescanTask( } CreateProductWalletTask(card, type).run(session) { result -> when (result) { - is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback) + is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run( + session, + callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } @@ -42,7 +44,10 @@ private class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") @@ -57,7 +62,7 @@ private class CreateWalletTangemNote : ProductCommandProcessor) -> Unit + callback: (result: CompletionResult) -> Unit, ) { if (card.supportedCurves.isEmpty()) { callback(CompletionResult.Failure(TangemSdkError.CardError())) @@ -89,9 +94,17 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit + callback: (result: CompletionResult) -> Unit, ) { - //TODO: + val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519) + val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList() + CreateProductWalletsTask(curves).run(session) { result -> + when (result) { + is CompletionResult.Success -> + callback(CompletionResult.Success(result.data.createWalletResponses[0])) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } } } @@ -100,7 +113,7 @@ private class CreateWalletOtherCards : ProductCommandProcessor) -> Unit + callback: (result: CompletionResult) -> Unit, ) { val firmwareVersion = card.firmwareVersion val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt new file mode 100644 index 0000000000..f475ef12ef --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -0,0 +1,57 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.extensions.guard +import com.tangem.operations.backup.ResetBackupCommand +import com.tangem.operations.wallet.PurgeWalletCommand + +class ResetToFactorySettingsTask() : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + deleteWallets(session, callback) + } + + private fun deleteWallets( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + + val wallet = session.environment.card?.wallets?.lastOrNull().guard { + resetBackup(session, callback) + return + } + + PurgeWalletCommand(wallet.publicKey).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + deleteWallets(session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun resetBackup( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + + val backupStatus = session.environment.card?.backupStatus + if (backupStatus == null && backupStatus == Card.BackupStatus.NoBackup) { + callback(CompletionResult.Success(session.environment.card!!)) + return + } + + ResetBackupCommand().run(session) { result -> + when (result) { + is CompletionResult.Success -> { + callback(CompletionResult.Success(session.environment.card!!)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt index 9a6735ae2e..fc1ae2bcb2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt @@ -68,7 +68,7 @@ class CurrenciesRepository(val context: Application) { } return try { - tokensAdapter.fromJson(json)!!.map { it.toToken() } + tokensAdapter.fromJson(json)!!.map { it.toToken() }.distinct() } catch (exception: Exception) { try { obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() } @@ -86,7 +86,7 @@ class CurrenciesRepository(val context: Application) { private fun loadSavedBlockchains(cardId: String): List { return try { val json = context.readFileText(getFileNameForBlockchains(cardId)) - blockchainsAdapter.fromJson(json) ?: emptyList() + blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList() } catch (exception: Exception) { emptyList() } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 9c7a39b172..4cf2a6fde3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -25,24 +25,26 @@ sealed class DetailsAction : Action { object ShowDisclaimer : DetailsAction() data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction() - sealed class EraseWallet : DetailsAction() { - object Check : EraseWallet() - object Proceed : EraseWallet() { - object NotAllowedByCard : EraseWallet(), NotificationAction { + sealed class ResetToFactory : DetailsAction() { + object Check : ResetToFactory() + object Proceed : ResetToFactory() { + object NotAllowedByCard : ResetToFactory(), NotificationAction { override val messageResource = R.string.error_purge_prohibited } - object NotEmpty : EraseWallet(), NotificationAction { + object NotEmpty : ResetToFactory(), NotificationAction { override val messageResource = R.string.details_notification_erase_wallet_not_possible } } - object Confirm : EraseWallet() - object Cancel : EraseWallet() - object Failure : EraseWallet() - object Success : EraseWallet() + object Confirm : ResetToFactory() + object Cancel : ResetToFactory() + object Failure : ResetToFactory() + object Success : ResetToFactory() } + object CreateBackup : DetailsAction() + sealed class AppCurrencyAction : DetailsAction() { data class SetCurrencies(val currencies: List) : AppCurrencyAction() object ChooseAppCurrency : AppCurrencyAction() 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 05591c0e48..2a6d764f4c 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 @@ -36,7 +36,7 @@ class DetailsMiddleware { { action -> when (action) { is DetailsAction.PrepareScreen -> prepareData() - is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action) + is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) is DetailsAction.ShowDisclaimer -> { @@ -58,6 +58,12 @@ class DetailsMiddleware { } } } + is DetailsAction.CreateBackup -> { + store.state.detailsState.scanResponse?.let { + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false)) + } + } } next(action) } @@ -86,25 +92,25 @@ class DetailsMiddleware { } class EraseWalletMiddleware() { - fun handle(action: DetailsAction.EraseWallet) { + fun handle(action: DetailsAction.ResetToFactory) { when (action) { - is DetailsAction.EraseWallet.Proceed -> { + is DetailsAction.ResetToFactory.Proceed -> { when (store.state.detailsState.eraseWalletState) { EraseWalletState.Allowed -> store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) EraseWalletState.NotAllowedByCard -> - store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard) + store.dispatch(DetailsAction.ResetToFactory.Proceed.NotAllowedByCard) EraseWalletState.NotEmpty -> - store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty) + store.dispatch(DetailsAction.ResetToFactory.Proceed.NotEmpty) } } - is DetailsAction.EraseWallet.Cancel -> { + is DetailsAction.ResetToFactory.Cancel -> { store.dispatch(NavigationAction.PopBackTo()) } - is DetailsAction.EraseWallet.Confirm -> { + is DetailsAction.ResetToFactory.Confirm -> { val card = store.state.detailsState.scanResponse?.card ?: return scope.launch { - val result = tangemSdkManager.eraseWallet(card) + val result = tangemSdkManager.resetToFactorySettings(card) withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { 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 ca2b6fb4bb..cdbde3bb92 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 @@ -26,7 +26,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { is DetailsAction.PrepareScreen -> { handlePrepareScreen(action, detailsState) } - is DetailsAction.EraseWallet -> { + is DetailsAction.ResetToFactory -> { handleEraseWallet(action, detailsState) } is DetailsAction.AppCurrencyAction -> { @@ -39,23 +39,34 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { } } -private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { +private fun handlePrepareScreen( + action: DetailsAction.PrepareScreen, + state: DetailsState, +): DetailsState { + val backupIsActive = action.scanResponse.card.backupStatus?.isActive ?: false + return DetailsState( scanResponse = action.scanResponse, wallets = action.wallets, cardInfo = action.scanResponse.card.toCardInfo(), appCurrencyState = AppCurrencyState(action.fiatCurrencyName), - cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card) + cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), + createBackupAllowed = action.scanResponse.isTangemWallet() && !backupIsActive, ) } -private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState { +private fun handleEraseWallet( + action: DetailsAction.ResetToFactory, + state: DetailsState, +): DetailsState { return when (action) { - DetailsAction.EraseWallet.Check -> { + DetailsAction.ResetToFactory.Check -> { val card = state.scanResponse?.card val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false val notAllowedByCard = notAllowedByAnyWallet || - (card?.isWalletDataSupported == true && !state.scanResponse.isTangemNote()) + (card?.isWalletDataSupported == true && + (!state.scanResponse.isTangemNote() && !state.scanResponse.isTangemWallet())) + val notEmpty = state.wallets.any { it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty() } @@ -66,22 +77,22 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS } state.copy(eraseWalletState = eraseWalletState) } - DetailsAction.EraseWallet.Proceed -> { + DetailsAction.ResetToFactory.Proceed -> { if (state.eraseWalletState == EraseWalletState.Allowed) { state.copy(confirmScreenState = ConfirmScreenState.EraseWallet) } else { state } } - DetailsAction.EraseWallet.Cancel -> state.copy(eraseWalletState = null) - DetailsAction.EraseWallet.Failure -> state.copy(eraseWalletState = null) - DetailsAction.EraseWallet.Success -> state.copy(eraseWalletState = null) + DetailsAction.ResetToFactory.Cancel -> state.copy(eraseWalletState = null) + DetailsAction.ResetToFactory.Failure -> state.copy(eraseWalletState = null) + DetailsAction.ResetToFactory.Success -> state.copy(eraseWalletState = null) else -> state } } private fun handleAppCurrencyAction( - action: DetailsAction.AppCurrencyAction, state: DetailsState + action: DetailsAction.AppCurrencyAction, state: DetailsState, ): DetailsState { return when (action) { is DetailsAction.AppCurrencyAction.SetCurrencies -> { @@ -105,7 +116,7 @@ private fun handleAppCurrencyAction( } private fun handleSecurityAction( - action: DetailsAction.ManageSecurity, state: DetailsState + action: DetailsAction.ManageSecurity, state: DetailsState, ): DetailsState { return when (action) { is DetailsAction.ManageSecurity.SetCurrentOption -> { @@ -123,18 +134,18 @@ private fun handleSecurityAction( state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption)) } is DetailsAction.ManageSecurity.OpenSecurity -> { - if (state.scanResponse?.card?.isStart2Coin == true || - state.scanResponse?.isTangemNote() == true - ) { - return state.copy(securityScreenState = state.securityScreenState?.copy( - allowedOptions = EnumSet.of(SecurityOption.LongTap), - selectedOption = state.securityScreenState.currentOption - )) + val allowedSecurityOptions = when { + state.scanResponse?.card?.isStart2Coin == true || + state.scanResponse?.isTangemNote() == true -> { + EnumSet.of(SecurityOption.LongTap) + } + state.scanResponse?.isTangemWallet() == true -> { + EnumSet.of(state.securityScreenState?.currentOption) + } + else -> prepareAllowedSecurityOptions( + state.scanResponse?.card, state.securityScreenState?.currentOption + ) } - - val allowedSecurityOptions = prepareAllowedSecurityOptions( - state.scanResponse?.card, state.securityScreenState?.currentOption - ) state.copy(securityScreenState = state.securityScreenState?.copy( allowedOptions = allowedSecurityOptions, selectedOption = state.securityScreenState.currentOption @@ -171,7 +182,7 @@ private fun handleSecurityAction( } private fun prepareAllowedSecurityOptions( - card: Card?, currentSecurityOption: SecurityOption? + card: Card?, currentSecurityOption: SecurityOption?, ): EnumSet { val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index cad9493b63..8f55ff5901 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -22,6 +22,7 @@ data class DetailsState( val confirmScreenState: ConfirmScreenState? = null, val securityScreenState: SecurityScreenState? = null, val cardTermsOfUseUrl: Uri? = null, + val createBackupAllowed: Boolean = false, ) : StateType { // if you do not delegate - the application crashes on startup, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt index 7439dee4ec..8e6dc116d8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt @@ -58,13 +58,13 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm), when (state.confirmScreenState) { ConfirmScreenState.EraseWallet -> { - toolbar.title = getString(R.string.details_row_title_erase_wallet) - tv_warning_description.text = getString(R.string.details_erase_wallet_warning) - btn_confirm.text = getString(R.string.details_row_title_erase_wallet) + toolbar.title = getString(R.string.details_row_title_reset_factory_settings) + tv_warning_description.text = getString(R.string.details_row_title_reset_factory_settings_warning) + btn_confirm.text = getString(R.string.details_row_title_reset_factory_settings) btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( null, null, getDrawable(R.drawable.ic_send), null ) - btn_confirm.setOnClickListener { store.dispatch(DetailsAction.EraseWallet.Confirm) } + btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) } } ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode, ConfirmScreenState.PassCode -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 5a1864ec3f..74d3f40fd9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -89,20 +89,24 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber = { dispatch, state -> store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.GetMoonPayStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) + store.dispatch(BackupAction.CheckForUnfinishedBackup) } is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt deleted file mode 100644 index 69c66581a6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet - -import android.os.Bundle -import android.view.View -import androidx.transition.TransitionInflater -import com.tangem.tap.features.BaseStoreFragment -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.store -import com.tangem.wallet.R -import org.rekotlin.StoreSubscriber - -/** -[REDACTED_AUTHOR] - */ -class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.fade) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - } - - override fun subscribeToStore() { - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.onboardingWalletState == newState.onboardingWalletState - }.select { it.onboardingWalletState } - } - storeSubscribersList.add(this) - } - - override fun newState(state: OnboardingWalletState) { - if (activity == null) return - - } -} \ 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 5cfd213d57..364f97ce71 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 @@ -1,19 +1,65 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import android.graphics.Bitmap +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.Action -/** -[REDACTED_AUTHOR] - */ sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() + object GetToCreateWalletStep : OnboardingWalletAction() object CreateWallet : OnboardingWalletAction() - object TopUp : OnboardingWalletAction() object Done : OnboardingWalletAction() + object FinishOnboarding : OnboardingWalletAction() object ProceedBackup : OnboardingWalletAction() - sealed class Backup : Action { + object LoadArtwork : OnboardingWalletAction() + class SetArtworkUrl(val artwork: Artwork) : OnboardingWalletAction() + object OnBackPressed : OnboardingWalletAction() +} + +sealed class BackupAction : Action { + + object DetermineBackupStep : BackupAction() + object IntroduceBackup : BackupAction() + object StartBackup : BackupAction() + object DismissBackup : BackupAction() + object ScanPrimaryCard : BackupAction() + + object CheckForUnfinishedBackup : BackupAction() + + object StartAddingBackupCards : BackupAction() + object AddBackupCard : BackupAction() { + object Success : BackupAction() } + + data class LoadBackupCardArtwork( + val cardId: CardId, + val cardPublicKey: ByteArray, + ) : BackupAction() { + data class Success(val cardId: CardId, val artwork: Bitmap) + } + + object GoToShop : BackupAction() + object FinishAddingBackupCards : BackupAction() + + object ShowAccessCodeInfoScreen : BackupAction() + object ShowEnterAccessCodeScreen : BackupAction() + data class CheckAccessCode(val accessCode: String) : BackupAction() + data class SetAccessCodeError(val error: AccessCodeError?) : BackupAction() + data class SaveFirstAccessCode(val accessCode: String) : BackupAction() + object ShowReenterAccessCodeScreen : BackupAction() + data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction() + + data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction() + data class WriteBackupCard(val cardNumber: Int) : BackupAction() + + object PrepareToWritePrimaryCard : BackupAction() + object WritePrimaryCard: BackupAction() + + object FinishBackup : BackupAction() + object DiscardBackup : BackupAction() + object ResumeBackup : BackupAction() + } \ No newline at end of file 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 c442090de4..f0c695ebd0 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 @@ -1,6 +1,22 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.operations.backup.BackupService +import com.tangem.tap.* +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.home.redux.HomeMiddleware +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction +import kotlinx.coroutines.launch +import org.rekotlin.Action import org.rekotlin.Middleware class OnboardingWalletMiddleware { @@ -12,7 +28,215 @@ class OnboardingWalletMiddleware { private val onboardingWalletMiddleware: Middleware = { dispatch, state -> { next -> { action -> + handleWalletAction(action) next(action) } } +} + +private fun handleWalletAction(action: Action) { + if (action !is OnboardingWalletAction) return + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager + + val scanResponse = onboardingManager?.scanResponse + val card = scanResponse?.card + + val walletState = store.state.onboardingWalletState + + when (action) { + OnboardingWalletAction.Init -> { + val action = when { + card == null -> { + OnboardingWalletAction.ProceedBackup + } + card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup -> + OnboardingWalletAction.ProceedBackup + card.hasWallets() && card.backupStatus?.isActive == true -> + BackupAction.FinishBackup + else -> OnboardingWalletAction.GetToCreateWalletStep + } + store.dispatch(action) + } + is OnboardingNoteAction.LoadCardArtwork -> { +// scope.launch { +// val artwork = onboardingManager.loadArtwork() +// store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(artwork)) +// } + } + is OnboardingWalletAction.CreateWallet -> { + scope.launch { + scanResponse ?: return@launch + val result = tangemSdkManager.createProductWallet(scanResponse) + withMainContext { + when (result) { + is CompletionResult.Success -> { + val updatedResponse = scanResponse.copy(card = result.data) + onboardingManager.scanResponse = updatedResponse + onboardingManager.activationStarted(updatedResponse.card.cardId) + store.dispatch(OnboardingWalletAction.ProceedBackup) + } + is CompletionResult.Failure -> { +// do nothing + } + } + } + } + } + OnboardingWalletAction.FinishOnboarding -> { + store.dispatch(GlobalAction.Onboarding.Stop) + + (listOf(walletState.backupState.primaryCardId) + + walletState.backupState.backupCardIds + scanResponse?.card?.cardId) + .distinct().filterNotNull() + .forEach { cardId -> + preferencesStorage.usedCardsPrefStorage.activationFinished(cardId) + } + + if (scanResponse == null) { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(HomeAction.ReadCard) + } else { + scope.launch { globalState.tapWalletManager.onCardScanned(scanResponse) } + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } + OnboardingWalletAction.ProceedBackup -> { + val newAction = when (val backupState = backupService.currentState) { + BackupService.State.Preparing -> BackupAction.IntroduceBackup + BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard + is BackupService.State.FinalizingBackupCard -> + BackupAction.PrepareToWriteBackupCard(backupState.index) + BackupService.State.Finished -> BackupAction.FinishBackup + } + store.dispatch(newAction) + } + OnboardingWalletAction.OnBackPressed -> { + if (walletState.backupState.backupStep is BackupStep.WriteBackupCard || + walletState.backupState.backupStep is BackupStep.WritePrimaryCard) { + store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) + } else { + store.dispatch(NavigationAction.PopBackTo()) + } + } + } +} + +class BackupMiddleware { + val backupMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + val backupState = state()?.onboardingWalletState?.backupState + when (action) { + is BackupAction.StartBackup -> { + backupService.discardSavedBackup() + } + is BackupAction.ScanPrimaryCard -> { + backupService.readPrimaryCard { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain(BackupAction.StartAddingBackupCards) + } + is CompletionResult.Failure -> { + } + } + } + } + is BackupAction.AddBackupCard -> { + backupService.addBackupCard { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain(BackupAction.AddBackupCard.Success) + } + is CompletionResult.Failure -> { + + } + } + } + } + is BackupAction.GoToShop -> { + store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) + } + is BackupAction.FinishAddingBackupCards -> { + if (backupService.addedBackupCardsCount == 1) { + store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.BuyMoreBackupCards)) + } + if (backupService.addedBackupCardsCount == 2) { + store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen) + } + } + is BackupAction.CheckAccessCode -> { + if (action.accessCode.length < 4) { + store.dispatch(BackupAction.SetAccessCodeError( + AccessCodeError.CodeTooShort + )) + } else { + store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode)) + } + } + is BackupAction.SaveAccessCodeConfirmation -> { + if (action.accessCodeConfirmation == backupState?.accessCode) { + backupService.setAccessCode(action.accessCodeConfirmation) + store.dispatch(BackupAction.PrepareToWritePrimaryCard) + } else { + store.dispatch(BackupAction.SetAccessCodeError( + AccessCodeError.CodesDoNotMatch + )) + } + } + is BackupAction.WritePrimaryCard -> { + backupService.proceedBackup { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain( + BackupAction.PrepareToWriteBackupCard(1) + ) + } + is CompletionResult.Failure -> { + } + } + } + } + is BackupAction.WriteBackupCard -> { + backupService.proceedBackup { result -> + when (result) { + is CompletionResult.Success -> { + if (backupService.currentState == BackupService.State.Finished) { + store.dispatchOnMain(BackupAction.FinishBackup) + } else { + store.dispatchOnMain( + BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1) + ) + } + } + is CompletionResult.Failure -> { + + } + } + } + } + is BackupAction.FinishBackup -> { +// store.dispatch(OnboardingWalletAction.Done) + } + is BackupAction.DiscardBackup -> { + backupService.discardSavedBackup() + } + is BackupAction.CheckForUnfinishedBackup -> { + if (backupService.hasIncompletedBackup) { + store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound)) + } + } + is BackupAction.ResumeBackup -> { + store.dispatch(OnboardingWalletAction.ProceedBackup) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + } + is BackupAction.DismissBackup -> { + store.dispatch(BackupAction.FinishBackup) + } + } + + next(action) + } + } + } } \ No newline at end of file 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 427f44048e..4069f04a22 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,20 +1,126 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import com.tangem.tap.backupService import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction import org.rekotlin.Action class OnboardingWalletReducer { companion object { - fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) + fun reduce(action: Action, state: AppState): OnboardingWalletState = + internalReduce(action, state) } } private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState { - var state = appState.onboardingWalletState - - when (action) { + val state = appState.onboardingWalletState + return when (action) { + is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState)) + is GlobalAction.Onboarding.Start -> OnboardingWalletState( + backupState = state.backupState.copy(canSkipBackup = action.fromHomeScreen) + ) + is OnboardingWalletAction.GetToCreateWalletStep -> + OnboardingWalletState(step = OnboardingWalletStep.CreateWallet) + is OnboardingWalletAction.ProceedBackup -> state.copy(step = OnboardingWalletStep.Backup) + is OnboardingWalletAction.SetArtworkUrl -> { + state.copy(artwork = action.artwork) + } + is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done) + else -> state } +} - return state + +class BackupReducer { + companion object { + fun reduce( + action: BackupAction, state: BackupState, + ): BackupState { + + return when (action) { + BackupAction.IntroduceBackup -> state.copy(backupStep = BackupStep.InitBackup) + + BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard) + + BackupAction.StartAddingBackupCards -> { + state.copy(backupStep = BackupStep.AddBackupCards) + } + BackupAction.AddBackupCard.Success -> { + state.copy(backupCardsNumber = state.backupCardsNumber + 1) + } + BackupAction.FinishAddingBackupCards -> { + state + } + BackupAction.ShowAccessCodeInfoScreen -> { + state.copy(backupStep = BackupStep.SetAccessCode) + } + BackupAction.ShowEnterAccessCodeScreen -> { + state.copy(backupStep = BackupStep.EnterAccessCode) + } + is BackupAction.SaveFirstAccessCode -> { + state.copy( + backupStep = BackupStep.ReenterAccessCode, + accessCode = action.accessCode + ) + } + is BackupAction.SetAccessCodeError -> { + state.copy( + accessCodeError = action.error + ) + } + is BackupAction.PrepareToWritePrimaryCard -> { + if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WritePrimaryCard + ) + } else { + state.copy( + backupStep = BackupStep.WritePrimaryCard + ) + } + } + is BackupAction.PrepareToWriteBackupCard -> { + if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WriteBackupCard(action.cardNumber) + ) + } else { + state.copy( + backupStep = BackupStep.WriteBackupCard(action.cardNumber) + ) + } + } + + is BackupAction.FinishBackup -> { + state.copy( + backupStep = BackupStep.Finished + ) + } + + BackupAction.WritePrimaryCard -> state + is BackupAction.WriteBackupCard -> state + is BackupAction.SaveAccessCodeConfirmation -> state + BackupAction.ScanPrimaryCard -> state + BackupAction.ShowEnterAccessCodeScreen -> state + BackupAction.ShowReenterAccessCodeScreen -> state + BackupAction.AddBackupCard -> state + BackupAction.DismissBackup -> state + is BackupAction.LoadBackupCardArtwork -> state + is BackupAction.CheckAccessCode -> state + BackupAction.GoToShop -> state + BackupAction.DetermineBackupStep -> state + BackupAction.CheckForUnfinishedBackup -> state + BackupAction.DiscardBackup -> state + BackupAction.ResumeBackup -> state + } + + } + } } \ No newline at end of file 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 5207a85b3d..6c6ff6d1f7 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 @@ -1,14 +1,81 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import android.graphics.Bitmap +import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType /** [REDACTED_AUTHOR] */ data class OnboardingWalletState( - val any: String? = null -) : StateType + val step: OnboardingWalletStep = OnboardingWalletStep.None, + val backupState: BackupState = BackupState(), + val artwork: Artwork? = null, + val showConfetti: Boolean = false, +) : StateType { + + fun getProgressStep(): Int { + return when { + step == OnboardingWalletStep.CreateWallet -> { + 1 + } + step == OnboardingWalletStep.Backup -> { + when (backupState.backupStep) { + BackupStep.InitBackup -> 2 + BackupStep.ScanOriginCard -> 3 + + BackupStep.AddBackupCards -> 4 + BackupStep.EnterAccessCode -> 4 + BackupStep.ReenterAccessCode -> 4 + BackupStep.SetAccessCode -> 4 + BackupStep.WritePrimaryCard, is BackupStep.WriteBackupCard -> 5 + BackupStep.Finished -> 6 + } + } + step == OnboardingWalletStep.Done -> 6 + else -> 1 + } + } +} enum class OnboardingWalletStep { - None, CreateWallet, TopUpWallet, Backup, Done + None, CreateWallet, Backup, Done +} + +data class BackupState( + val primaryCardId: String? = null, + val backupCardsNumber: Int = 0, + val backupCardIds: List = emptyList(), + val backupCardsArtworks: Map = emptyMap(), + val accessCode: String = "", + val accessCodeError: AccessCodeError? = null, + val backupStep: BackupStep = BackupStep.InitBackup, + val maxBackupCards: Int = 2, + val canSkipBackup: Boolean = true +) + +enum class AccessCodeError { + CodeTooShort, CodesDoNotMatch +} + +typealias CardId = String + +sealed class BackupStep { + object InitBackup : BackupStep() + object ScanOriginCard : BackupStep() + object AddBackupCards : BackupStep() + object SetAccessCode : BackupStep() + object EnterAccessCode : BackupStep() + object ReenterAccessCode : BackupStep() + object WritePrimaryCard : BackupStep() + data class WriteBackupCard(val cardNumber: Int) : BackupStep() + object Finished : BackupStep() +} + +sealed class BackupDialog: StateDialog { + object BuyMoreBackupCards : StateDialog + object BackupInProgress : StateDialog + object UnfinishedBackupFound : StateDialog + object ConfirmDiscardingBackup : StateDialog } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt new file mode 100644 index 0000000000..d5b5065f36 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt @@ -0,0 +1,234 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder +import android.view.View +import android.widget.ImageView +import androidx.core.animation.doOnEnd +import com.tangem.tap.common.leapfrogWidget.LeapView +import com.tangem.tap.common.leapfrogWidget.LeapViewState +import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget + +class BackupCardsWidget( + val leapfrogWidget: LeapfrogWidget, + val getTopOfAnchorViewForActivateState: () -> Float, +) { + + fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(BackupCardType.ORIGIN, createWelcomeProperties(BackupCardType.ORIGIN)), + createAnimator(BackupCardType.FIRST_BACKUP, createWelcomeProperties(BackupCardType.FIRST_BACKUP)), + createAnimator(BackupCardType.SECOND_BACKUP, createWelcomeProperties(BackupCardType.SECOND_BACKUP)) + ) + leapfrogWidget.fold { animator.start() } + } + + fun toFolded(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), + createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), + createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)) + ) + leapfrogWidget.fold { animator.start() } + } + + fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(BackupCardType.ORIGIN, createFanProperties(BackupCardType.ORIGIN)), + createAnimator(BackupCardType.FIRST_BACKUP, createFanProperties(BackupCardType.FIRST_BACKUP)), + createAnimator(BackupCardType.SECOND_BACKUP, createFanProperties(BackupCardType.SECOND_BACKUP)) + ) + leapfrogWidget.fold { animator.start() } + } + + fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), + createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), + createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)) + ) + leapfrogWidget.fold { + animator.doOnEnd { + leapfrogWidget.initViews() + leapfrogWidget.unfold() + } + animator.start() + } + } +// +// fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) { +// val animator = createAnimator(animate, onEnd) +// animator.playTogether( +// createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), +// createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) +// ) +// animator.start() +// } + + private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { + return AnimatorSet().apply { + duration = if (animate) 400 else 0 + doOnEnd { onEnd() } + } + } + + private fun createAnimator( + cardType: BackupCardType, + properties: CardProperties, + ): ObjectAnimator { + val view = getLeapViewByCardNumber(cardType).view + val animator = ObjectAnimator.ofPropertyValuesHolder(view, + *properties.createValuesHolders().toTypedArray()) + view.elevation = properties.elevation + return animator + } + + private fun createWelcomeProperties(cardType: BackupCardType): CardProperties { + return when (cardType) { + BackupCardType.ORIGIN -> CardProperties( + xTranslation = 440f, + yTranslation = 0f, + rotation = 75f, + elevation = 2f, + scale = .9f, + ) + BackupCardType.FIRST_BACKUP -> CardProperties( + xTranslation = 10f, + yTranslation = -100f, + rotation = 100f, + elevation = 1f, + scale = .9f, +) + BackupCardType.SECOND_BACKUP -> CardProperties( + xTranslation = -440f, + yTranslation = -100f, + rotation = 70f, + elevation = 0f, + scale = .9f, + ) + } + } + + private fun createFanProperties(cardType: BackupCardType): CardProperties { + return when (cardType) { + BackupCardType.ORIGIN -> CardProperties( + xTranslation = 0f, + yTranslation = 0f, + rotation = 5f, + elevation = 2f, + scale = 1f, + ) + BackupCardType.FIRST_BACKUP -> CardProperties( + xTranslation = 0f, + yTranslation = -80f, + rotation = -5f, + elevation = 1f, + scale = 0.9f, + ) + BackupCardType.SECOND_BACKUP -> CardProperties( + xTranslation = 10f, + yTranslation = -170f, + rotation = -20f, + elevation = 0f, + scale = 0.8f, + ) + } + } + + private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties { + return when (cardType) { + BackupCardType.ORIGIN -> CardProperties( + xTranslation = 0f, + yTranslation = 0f, + rotation = 0f, + elevation = 2f, + scale = 1f, + ) + BackupCardType.FIRST_BACKUP -> CardProperties( + xTranslation = 0f, + yTranslation = 0f, + rotation = 0f, + elevation = 1f, + scale = 0.9f, + ) + BackupCardType.SECOND_BACKUP -> CardProperties( + xTranslation = 0f, + yTranslation = 0f, + rotation = 0f, + elevation = 0f, + scale = 0.8f, + ) + } + } + + private fun createActivateProperties(cardType: BackupCardType): CardProperties { + val topOfAnchorView = getTopOfAnchorViewForActivateState() + val twinProperties = CardProperties.from(getLeapViewByCardNumber(cardType).state) + return twinProperties.copy( + xTranslation = twinProperties.xTranslation, + yTranslation = twinProperties.yTranslation - topOfAnchorView, + rotation = 0f, + scale = twinProperties.scale - 0.4f, + ) + } + + private fun getLeapViewByCardNumber(cardType: BackupCardType): LeapView { + return when (cardType) { + BackupCardType.ORIGIN -> leapfrogWidget.getViewByPosition(0) + BackupCardType.FIRST_BACKUP -> leapfrogWidget.getViewByPosition(1) + BackupCardType.SECOND_BACKUP -> leapfrogWidget.getViewByPosition(2) + } + } + + fun getOriginCardView(): ImageView { + return getLeapViewByCardNumber(BackupCardType.ORIGIN).view as ImageView + } + + fun getFirstBackupCardView(): ImageView { + return getLeapViewByCardNumber(BackupCardType.FIRST_BACKUP).view as ImageView + } + + fun getSecondBackupCardView(): ImageView { + return getLeapViewByCardNumber(BackupCardType.SECOND_BACKUP).view as ImageView + } +} + + +enum class BackupCardType { ORIGIN, FIRST_BACKUP, SECOND_BACKUP } + + +private data class CardProperties( + val xTranslation: Float = 0f, + val yTranslation: Float = 0f, + val rotation: Float = 0f, + val elevation: Float = 0f, + val scale: Float = 1.1f, +) { + + fun createValuesHolders(): List { + return listOf( + PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation), + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation), + PropertyValuesHolder.ofFloat(View.ROTATION, rotation), + PropertyValuesHolder.ofFloat(View.SCALE_X, scale), + PropertyValuesHolder.ofFloat(View.SCALE_Y, scale), + ) + } + + companion object { + fun from(leapViewState: LeapViewState): CardProperties { + val leapViewProperties = leapViewState.properties + + return CardProperties( + yTranslation = leapViewProperties.yTranslation, + elevation = leapViewProperties.elevationEnd, + scale = leapViewProperties.scale, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt new file mode 100644 index 0000000000..f7044f9986 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt @@ -0,0 +1,54 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tap.common.extensions.getString +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.item_backup_info_adapter.view.* + +class BackupInfoAdapter : RecyclerView.Adapter() { + + private val data = backupInfoSnippets + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder = + BackupInfoViewHolder(LayoutInflater.from(parent.context) + .inflate(R.layout.item_backup_info_adapter, parent, false)) + + override fun getItemCount(): Int = data.size + + override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) = + holder.itemView.run { + tv_header.text = getString(data[position].header) + tv_body.text = getString(data[position].body) + } +} + +private class BackupInfo( + @StringRes val header: Int, + @StringRes val body: Int, +) + +private val backupInfoSnippets = listOf( + BackupInfo( + R.string.onboarding_wallet_info_title_first, + R.string.onboarding_wallet_info_subtitle_first + ), + BackupInfo( + R.string.onboarding_wallet_info_title_second, + R.string.onboarding_wallet_info_subtitle_second + ), + BackupInfo( + R.string.onboarding_wallet_info_title_third, + R.string.onboarding_wallet_info_subtitle_third + ), + BackupInfo( + R.string.onboarding_wallet_info_title_fourth, + R.string.onboarding_wallet_info_subtitle_fourth + ), + + ) + +class BackupInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ 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 new file mode 100644 index 0000000000..52adf8a47d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -0,0 +1,384 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui + +import android.os.Bundle +import android.view.* +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import androidx.transition.TransitionManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.tabs.TabLayoutMediator +import com.tangem.common.CardIdFormatter +import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.tap.common.extensions.addOnBackPressedDispatcher +import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget +import com.tangem.tap.common.postUi +import com.tangem.tap.features.home.redux.HomeMiddleware +import com.tangem.tap.features.onboarding.products.wallet.redux.* +import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_onboarding_wallet.* +import kotlinx.android.synthetic.main.fragment_onboarding_wallet.toolbar +import kotlinx.android.synthetic.main.fragment_wallet_details.* +import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.* +import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.* +import kotlinx.android.synthetic.main.view_confetti.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* +import org.rekotlin.StoreSubscriber + +class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), + StoreSubscriber { + + private var accessCodeDialog: AccessCodeDialog? = null + private lateinit var cardsWidget: BackupCardsWidget + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + postponeEnterTransition() + setHasOptionsMenu(true) + + + activity?.addOnBackPressedDispatcher { + store.dispatch(OnboardingWalletAction.OnBackPressed) + } + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val leapfrog = LeapfrogWidget(fl_cards_container) + cardsWidget = BackupCardsWidget(leapfrog) { 200f } + startPostponedEnterTransition() + + view_pager_backup_info.adapter = BackupInfoAdapter() + TabLayoutMediator(tab_layout_backup_info, view_pager_backup_info) { tab, position -> + //Some implementation + }.attach() + + (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) + + store.dispatch(OnboardingWalletAction.Init) + toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + +// store.dispatch(OnboardingWalletAction.LoadArtwork) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.onboardingWalletState == newState.onboardingWalletState + }.select { it.onboardingWalletState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun newState(state: OnboardingWalletState) { + if (activity == null) return + + requireActivity().invalidateOptionsMenu() + + state.artwork?.artwork?.let { imv_front_card.setImageBitmap(it) } + pb_state.max = 6 + pb_state.progress = state.getProgressStep() + + when (state.step) { + OnboardingWalletStep.None -> { + } + OnboardingWalletStep.CreateWallet -> setupCreateWalletState() + OnboardingWalletStep.Backup -> setBackupState(state.backupState) + OnboardingWalletStep.Done -> { + + } + } + } + + private fun setupCreateWalletState() { + btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) + btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) } + btn_alternative_action.hide() + + toolbar.title = getText(R.string.onboarding_getting_started) + + tv_header.setText(R.string.onboarding_create_wallet_header) + tv_body.setText(R.string.onboarding_create_wallet_body) + + cardsWidget.toFolded() + startPostponedEnterTransition() + } + + + private fun setBackupState(state: BackupState) { + when (state.backupStep) { + BackupStep.InitBackup -> showBackupIntro(state) + BackupStep.ScanOriginCard -> showScanOriginCard(state) + BackupStep.AddBackupCards -> showAddBackupCards(state) + BackupStep.SetAccessCode -> showSetAccessCode() + BackupStep.EnterAccessCode -> showEnterAccessCode(state) + BackupStep.ReenterAccessCode -> showReenterAccessCode(state) + is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state) + is BackupStep.WriteBackupCard -> showWriteBackupCard(state) + BackupStep.Finished -> showSuccess(state) + } + } + + private fun showBackupIntro(state: BackupState) { + imv_first_backup_card.show() + imv_second_backup_card.show() + + cardsWidget.toWelcome() + + tv_header.hide() + tv_body.hide() + view_pager_backup_info.show() + tab_layout_backup_info.show() + + btn_main_action.text = getText(R.string.onboarding_button_backup_now) + btn_alternative_action.text = getText(R.string.onboarding_button_skip_backup) + btn_alternative_action.show(state.canSkipBackup) + btn_main_action.setOnClickListener { store.dispatch(BackupAction.StartBackup) } + btn_alternative_action.setOnClickListener { store.dispatch(BackupAction.DismissBackup) } + startPostponedEnterTransition() + } + + private fun showScanOriginCard(state: BackupState) { + + toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup) + + cardsWidget.toFolded() + + tv_header.show() + tv_body.show() + view_pager_backup_info.hide() + tab_layout_backup_info.hide() + + imv_first_backup_card.show() + imv_second_backup_card.show() + + tv_header.text = getText(R.string.onboarding_title_scan_origin_card) + tv_body.text = getString( + R.string.onboarding_subtitle_scan_origin_card, + ) + + btn_main_action.text = getString(R.string.onboarding_button_scan_origin_card) + btn_alternative_action.hide() + btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) } + } + + private fun showAddBackupCards(state: BackupState) { + imv_first_backup_card.show() + imv_second_backup_card.show() + + layout_buttons_add_cards.show() + layout_buttons_common.hide() + if (state.backupCardsNumber < state.maxBackupCards) { + btn_add_card.text = getText(R.string.onboarding_button_add_backup_card) + btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) } + } else { + btn_add_card.text = "You hit the maximum" + btn_add_card.isEnabled = false + } + + when (state.backupCardsNumber) { + 0 -> { + cardsWidget.toFan() + tv_header.text = getText(R.string.onboarding_title_no_backup_cards) + tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards) + cardsWidget.getFirstBackupCardView().alpha = 0.6f + cardsWidget.getSecondBackupCardView().alpha = 0.2f + } + 1 -> { + tv_header.text = getText(R.string.onboarding_title_one_backup_card) + tv_body.text = getText(R.string.onboarding_subtitle_one_backup_card) + + cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400) + cardsWidget.getSecondBackupCardView().alpha = 0.2f + } + 2 -> { + tv_header.text = getText(R.string.onboarding_title_two_backup_cards) + tv_body.text = getText(R.string.onboarding_subtitle_two_backup_cards) + cardsWidget.getFirstBackupCardView().alpha = 1f + cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400) + } + } + + btn_continue.text = getText(R.string.onboarding_button_finalize_backup) + btn_continue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) } + btn_continue.isEnabled = state.backupCardsNumber != 0 + } + + private fun showSetAccessCode() { + accessCodeDialog = AccessCodeDialog(requireContext()).apply { + dismissWithAnimation = true + create() + setOnCancelListener { + + } + show() + showInfoScreen() + val view = + delegate.findViewById(com.google.android.material.R.id.design_bottom_sheet) + (view?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) } + behavior.state = BottomSheetBehavior.STATE_EXPANDED + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + view?.let { it.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT } + } + } + + private fun showEnterAccessCode(state: BackupState) { + accessCodeDialog?.showEnterAccessCode() + accessCodeDialog?.showError(state.accessCodeError) + postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) } + + } + + private fun showReenterAccessCode(state: BackupState) { + accessCodeDialog?.showReenterAccessCode() + accessCodeDialog?.showError(state.accessCodeError) + postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) } + + } + + private fun showWritePrimaryCard(state: BackupState) { + accessCodeDialog?.dismiss() + + prepareViewForFinalizeStep() + cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2) + + cardsWidget.toLeapfrog() { + cardsWidget.getFirstBackupCardView().alpha = 0.6f + cardsWidget.getSecondBackupCardView().alpha = 0.2f + } + + val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) + tv_header.text = getText(R.string.onboarding_title_prepare_origin) + tv_body.text = getString( + R.string.onboarding_subtitle_scan_primary_card_format, + state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) } + ) + btn_main_action.text = getText(R.string.onboarding_button_backup_origin) + btn_main_action.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } + + } + + private fun prepareViewForFinalizeStep() { + layout_buttons_add_cards.hide() + layout_buttons_common.show() + + imv_first_backup_card.show() + imv_second_backup_card.show() + + toolbar.title = getText(R.string.onboarding_button_finalize_backup) + + imv_card_background.hide() + + cardsWidget.toLeapfrog() + +// imv_front_card.alpha = 1f +// imv_first_backup_card.alpha = 1f +// imv_second_backup_card.alpha = 1f + + btn_alternative_action.hide() + } + + private fun showWriteBackupCard(state: BackupState) { + prepareViewForFinalizeStep() + + cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2) + + val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 + when (cardNumber) { + 1 -> cardsWidget.leapfrogWidget.leap() { + cardsWidget.getOriginCardView().alpha = 0.4f + cardsWidget.getFirstBackupCardView().alpha = 0.2f + cardsWidget.getSecondBackupCardView().alpha = 1f + } + 2 -> cardsWidget.leapfrogWidget.leap() { + cardsWidget.getOriginCardView().alpha = 0.4f + cardsWidget.getFirstBackupCardView().alpha = 0.2f + cardsWidget.getSecondBackupCardView().alpha = 1f + } + } + + val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) + tv_header.text = + getString(R.string.onboarding_title_backup_card_format, cardNumber) + tv_body.text = getString( + R.string.onboarding_subtitle_scan_primary_card_format, + cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]) + ) + btn_main_action.text = getString( + R.string.onboarding_button_backup_card_format, + cardNumber + ) + + btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) } + } + + private fun showSuccess(state: BackupState) { + tv_header.text = getText(R.string.onboarding_done_header) + val text = when (state.backupCardsNumber) { + 1 -> getText(R.string.onboarding_subtitle_success_backup_one_card) + 2 -> getText(R.string.onboarding_subtitle_success_backup) + else -> getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding) + } + + tv_header.show() + tv_body.show() + view_pager_backup_info.hide() + tab_layout_backup_info.hide() + + tv_body.text = text + btn_main_action.text = getText(R.string.common_continue) + btn_alternative_action.hide() + btn_main_action.setOnClickListener { + lav_confetti.cancelAnimation() + lav_confetti.hide() + store.dispatch(OnboardingWalletAction.FinishOnboarding) + } + + cardsWidget.leapfrogWidget.fold { + fl_cards_container?.hide() + imv_card_background?.hide() + lav_confetti?.show() + lav_confetti?.playAnimation() + imv_success?.alpha = 0f + imv_success?.show() + + imv_success?.animate() + ?.alpha(1f) + ?.setDuration(400) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.shop_menu -> { + store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.shop, menu) + + val backupStep = store.state.onboardingWalletState.backupState.backupStep + val shopMenuShouldBeVisible = + backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards + menu.getItem(0).isVisible = shopMenuShouldBeVisible + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt new file mode 100644 index 0000000000..afd4521ac1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt @@ -0,0 +1,71 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.layout_backup_access_code.* +import kotlinx.android.synthetic.main.layout_backup_access_code_info.* +import kotlinx.android.synthetic.main.layout_backup_access_code_submit.* + +class AccessCodeDialog(context: Context) : BottomSheetDialog(context) { + + + init { + behavior.state = BottomSheetBehavior.STATE_EXPANDED + val dialogView = LayoutInflater.from(context).inflate(R.layout.layout_backup_access_code, null) + setContentView(dialogView) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + override fun setContentView(view: View) { + super.setContentView(view) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + + fun showInfoScreen() { + behavior.state = BottomSheetBehavior.STATE_EXPANDED + layout_backup_access_code_info.show() + layout_backup_access_code_submit.hide() + btn_access_code_create.setOnClickListener { + store.dispatch(BackupAction.ShowEnterAccessCodeScreen) + } + } + + fun showEnterAccessCode() { + layout_backup_access_code_info.hide() + layout_backup_access_code_submit.show() + btn_access_code_submit.text = context.getText(R.string.common_continue) + btn_access_code_submit.setOnClickListener { + store.dispatch(BackupAction.CheckAccessCode(et_access_code.text.toString())) + } + } + + fun showReenterAccessCode() { + layout_backup_access_code_info.hide() + layout_backup_access_code_submit.show() + et_access_code.setText("") + btn_access_code_submit.text = context.getText(R.string.common_submit) + btn_access_code_submit.setOnClickListener { + store.dispatch(BackupAction.SaveAccessCodeConfirmation(et_access_code.text.toString())) + } + } + + fun showError(error: AccessCodeError?) { + when (error) { + AccessCodeError.CodeTooShort -> + til_access_code.error = context.getString(R.string.onboarding_access_code_too_short) + AccessCodeError.CodesDoNotMatch -> + til_access_code.error = context.getString(R.string.onboarding_access_codes_doesnt_match) + null -> til_access_code.error = null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt new file mode 100644 index 0000000000..9b619eef7d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +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 + +class BackupInProgressDialog { + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_title) + setMessage(R.string.onboarding_backup_exit_warning) + setPositiveButton(R.string.warning_button_ok) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt new file mode 100644 index 0000000000..06f4c1565d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt @@ -0,0 +1,28 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction +import com.tangem.tap.store +import com.tangem.wallet.R + +class BuyMoreBackupCardsDialog { + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_title) + setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) + setPositiveButton(R.string.common_continue) { _, _ -> + store.dispatch(BackupAction.ShowAccessCodeInfoScreen) + } + setNegativeButton(R.string.onboarding_button_buy_more_cards) { _, _ -> + store.dispatch(BackupAction.GoToShop) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt new file mode 100644 index 0000000000..07900822ba --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction +import com.tangem.tap.store +import com.tangem.wallet.R + +class ConfirmDiscardingBackupDialog { + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.welcome_interrupted_backup_discard_title) + setMessage(R.string.welcome_interrupted_backup_discard_message) + setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ -> + store.dispatch(BackupAction.ResumeBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> + store.dispatch(BackupAction.DiscardBackup) + } + 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/ui/dialogs/UnfinishedBackupFoundDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt new file mode 100644 index 0000000000..41ccf7083b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog +import com.tangem.tap.store +import com.tangem.wallet.R + +class UnfinishedBackupFoundDialog { + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_title) + setMessage(R.string.welcome_interrupted_backup_alert_message) + setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(BackupAction.ResumeBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup)) + } + setCancelable(false) + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingWalletService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingWalletService.kt new file mode 100644 index 0000000000..db7f77b99c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingWalletService.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.features.onboarding.service + +//class OnboardingWalletService( +// override val fromScreen: AppScreen, +// override var scanResponse: ScanResponse, +// cardInfoStorage: UsedCardsPrefStorage, +// walletManagerFactory: WalletManagerFactory, +//) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { +// +// override fun navigateToScreen(): AppScreen { +// return AppScreen.OnboardingWallet +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index aa0aaee36c..6fc58ea550 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.ui.wallet import android.app.Dialog import androidx.recyclerview.widget.LinearLayoutManager +import com.tangem.common.card.Card import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.StateDialog @@ -49,8 +50,22 @@ class MultiWalletView : WalletView { btn_scan_multiwallet?.show() rv_multiwallet.show() btn_add_token.show() + setupWalletCardNumber(fragment) } + private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) { + val card = store.state.globalState.scanResponse?.card + if (card?.backupStatus?.isActive == true) { + val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1 + tv_twin_card_number.show() + tv_twin_card_number.text = + getString(R.string.wallet_twins_chip_format, 1, cardCount) + } else { + tv_twin_card_number.hide() + } + } + + private fun setupButtons(fragment: WalletFragment) = with(fragment) { btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index cb58832ebb..d8ab4644df 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -96,11 +96,11 @@ class SingleWalletView : WalletView { twinCardsState?.cardNumber?.let { cardNumber -> tv_twin_card_number.show() val number = when (cardNumber) { - TwinCardNumber.First -> "1" - TwinCardNumber.Second -> "2" + TwinCardNumber.First -> 1 + TwinCardNumber.Second -> 2 } tv_twin_card_number.text = - this.getString(R.string.wallet_twins_chip_format, number) + this.getString(R.string.wallet_twins_chip_format, number, 2) } if (twinCardsState?.cardNumber == null) { tv_twin_card_number.hide() diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index 17238689f5..a016e233f3 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -45,7 +45,7 @@ class UsedCardsPrefStorage( } fun activationIsStarted(cardId: String): Boolean { - return findCardInfo(cardId)?.isActivationStarted ?: false + return findCardInfo(cardId)?.isActivationStarted ?: true } fun activationFinished(cardId: String) { diff --git a/app/src/main/res/color/selector_btn_gray_text_color.xml b/app/src/main/res/color/selector_btn_gray_text_color.xml new file mode 100644 index 0000000000..8e5c0ab355 --- /dev/null +++ b/app/src/main/res/color/selector_btn_gray_text_color.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_edit_text.xml b/app/src/main/res/color/selector_edit_text.xml new file mode 100644 index 0000000000..b6c0697011 --- /dev/null +++ b/app/src/main/res/color/selector_edit_text.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/card_placeholder_new.png b/app/src/main/res/drawable-hdpi/card_placeholder_new.png new file mode 100644 index 0000000000..4c9cff9062 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable-ldpi/card_placeholder_new.png b/app/src/main/res/drawable-ldpi/card_placeholder_new.png new file mode 100644 index 0000000000..e9986ae27f Binary files /dev/null and b/app/src/main/res/drawable-ldpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable-mdpi/card_placeholder_new.png b/app/src/main/res/drawable-mdpi/card_placeholder_new.png new file mode 100644 index 0000000000..b15e1cf3bb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable-xhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xhdpi/card_placeholder_new.png new file mode 100644 index 0000000000..a9f3c750e0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png new file mode 100644 index 0000000000..0caf7a0d35 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png new file mode 100644 index 0000000000..56a6e580f0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png differ diff --git a/app/src/main/res/drawable/ic_access_code.xml b/app/src/main/res/drawable/ic_access_code.xml new file mode 100644 index 0000000000..d24768aa15 --- /dev/null +++ b/app/src/main/res/drawable/ic_access_code.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_dot.xml b/app/src/main/res/drawable/ic_dot.xml new file mode 100644 index 0000000000..321894f58a --- /dev/null +++ b/app/src/main/res/drawable/ic_dot.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dot_not_selected.xml b/app/src/main/res/drawable/ic_dot_not_selected.xml new file mode 100644 index 0000000000..a6d0f14af2 --- /dev/null +++ b/app/src/main/res/drawable/ic_dot_not_selected.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_feature_1.xml b/app/src/main/res/drawable/ic_feature_1.xml new file mode 100644 index 0000000000..ed4eba060f --- /dev/null +++ b/app/src/main/res/drawable/ic_feature_1.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_feature_2.xml b/app/src/main/res/drawable/ic_feature_2.xml new file mode 100644 index 0000000000..6c1b54564b --- /dev/null +++ b/app/src/main/res/drawable/ic_feature_2.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_feature_3.xml b/app/src/main/res/drawable/ic_feature_3.xml new file mode 100644 index 0000000000..cf8a08f7d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_feature_3.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_hide_password.xml b/app/src/main/res/drawable/ic_hide_password.xml new file mode 100644 index 0000000000..e0ace3f008 --- /dev/null +++ b/app/src/main/res/drawable/ic_hide_password.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_selected_dot.xml b/app/src/main/res/drawable/ic_selected_dot.xml new file mode 100644 index 0000000000..e847725fc2 --- /dev/null +++ b/app/src/main/res/drawable/ic_selected_dot.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_show_password.xml b/app/src/main/res/drawable/ic_show_password.xml new file mode 100644 index 0000000000..4bbf87adb5 --- /dev/null +++ b/app/src/main/res/drawable/ic_show_password.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/selector_password_toggle.xml b/app/src/main/res/drawable/selector_password_toggle.xml new file mode 100644 index 0000000000..bdff307019 --- /dev/null +++ b/app/src/main/res/drawable/selector_password_toggle.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_tabs_dots.xml b/app/src/main/res/drawable/selector_tabs_dots.xml new file mode 100644 index 0000000000..8b1678434a --- /dev/null +++ b/app/src/main/res/drawable/selector_tabs_dots.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 90df722e29..b6475a8ac3 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -160,15 +160,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes" /> - + + + app:layout_constraintTop_toBottomOf="@id/tv_reset_to_factory" /> - + android:layout_height="match_parent" + android:background="@color/backgroundLightGray" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_backup_info_adapter.xml b/app/src/main/res/layout/item_backup_info_adapter.xml new file mode 100644 index 0000000000..ecbfdbdb1b --- /dev/null +++ b/app/src/main/res/layout/item_backup_info_adapter.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_backup_access_code.xml b/app/src/main/res/layout/layout_backup_access_code.xml new file mode 100644 index 0000000000..de45562ba3 --- /dev/null +++ b/app/src/main/res/layout/layout_backup_access_code.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_backup_access_code_info.xml b/app/src/main/res/layout/layout_backup_access_code_info.xml new file mode 100644 index 0000000000..fab2ddf46d --- /dev/null +++ b/app/src/main/res/layout/layout_backup_access_code_info.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_backup_access_code_submit.xml b/app/src/main/res/layout/layout_backup_access_code_submit.xml new file mode 100644 index 0000000000..eca2c05bbd --- /dev/null +++ b/app/src/main/res/layout/layout_backup_access_code_submit.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml b/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml new file mode 100644 index 0000000000..1de5055e97 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_buttons_common.xml b/app/src/main/res/layout/layout_onboarding_buttons_common.xml new file mode 100644 index 0000000000..ff0472d121 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_buttons_common.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/test_leapfrog_widget.xml b/app/src/main/res/layout/test_leapfrog_widget.xml index c4fddb3183..63834a3de2 100644 --- a/app/src/main/res/layout/test_leapfrog_widget.xml +++ b/app/src/main/res/layout/test_leapfrog_widget.xml @@ -19,4 +19,10 @@ android:layout_height="wrap_content" android:src="@drawable/card_placeholder_black" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_progress.xml b/app/src/main/res/layout/view_onboarding_progress.xml index 6dbac9141f..1c7c3b53fc 100644 --- a/app/src/main/res/layout/view_onboarding_progress.xml +++ b/app/src/main/res/layout/view_onboarding_progress.xml @@ -1,6 +1,5 @@ - + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5d0fe290b1..3bf6353760 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -55,4 +55,7 @@ #FFB71B #CA0F03 + #EFEFF0 + + #18C077 \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index c28a51e88b..3a6508fd00 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -16,7 +16,7 @@ One wallet. Two cards. This one that you are holding in your hands and the other one with number #%s. - Card %s of 2 + Card %1d of %2d Re-create twin wallet Card terms of use @@ -208,4 +208,89 @@ Ok, Got it! + Success! + Getting started + + Backup now + Get a backup card + Skip for later + Finalize the backup process + + Add backup card + Scan the card #%d + Scan primary card + Buy more cards + + Backup your wallet + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + + You can backup your keys up to two other blank wallet cards + To start the backup process add up to two cards + Prepare the primary card with number %s + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process + If the process of backup gets interrupted in any way you’ll have to start over + + You’ve added one backup card. When backup process is finished you can’t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process? + + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to setup a single access code to protect all yor wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don’t keep all cards at one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match initial access code + + Create + Submit + You have created 2 backup cards, and now these cards are ready for use. + Creating a backup + The backup process is partly complete. You can’t exit it now. + + Backup wallet + Identical cards + Access code + Access code restore + + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functoinal with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + + Your Tangem Wallet card is configured and ready for use. + You have created 1 backup card, and now this card are ready for use. + Primary Card + I understand + + Backup status + Active. Backup cards count: %d + Linked. Backup cards count: %d + No backup + Create backup + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Primary card + Scan the primary card + Continue to my wallet + Card %1s of %2s + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Reset to factory settings + This action is irreversible. If, after resetting the card, someone sends funds to it, then you will not be able to withdraw them. + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 286cddae59..ba05fd093a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -9,6 +9,8 @@ true true @null + @color/menu_accent_color + + + + + + + + + + + + + + + +