diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..9dd60618bd --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,184 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.4) + rexml + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.519.0) + aws-sdk-core (3.121.3) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.50.0) + aws-sdk-core (~> 3, >= 3.121.2) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.104.0) + aws-sdk-core (~> 3, >= 3.121.2) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.4.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.20) + declarative-option (0.1.0) + digest-crc (0.6.4) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (1.0.1) + excon (0.87.0) + faraday (0.17.4) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.2.5) + fastlane (2.146.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.13.1) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.29.2, < 0.37.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-firebase_app_distribution (0.3.1) + gh_inspector (1.1.3) + google-api-client (0.32.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.5, < 0.10.0) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.10) + google-cloud-core (1.3.2) + google-cloud-env (~> 1.0) + google-cloud-env (1.2.1) + faraday (~> 0.11) + google-cloud-storage (1.21.1) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.26) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + mini_mime (~> 1.0) + googleauth (0.9.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.7) + highline (1.7.10) + http-cookie (1.0.4) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.4.0) + json (2.6.1) + jwt (2.1.0) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.1.2) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.1) + os (1.1.1) + plist (3.6.0) + public_suffix (2.0.5) + rake (13.0.6) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.5) + rouge (2.0.7) + rubyzip (1.3.0) + security (0.1.3) + signet (0.11.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.4.0) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8) + unicode-display_width (1.8.0) + word_wrap (1.0.0) + xcodeproj (1.21.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + universal-darwin-18 + +DEPENDENCIES + fastlane + fastlane-plugin-firebase_app_distribution + +BUNDLED WITH + 2.2.16 diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 6f99403572..a7307e4e74 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -13,6 +13,8 @@ import com.tangem.tap.common.DialogManager import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.redux.NotificationsHandler +import com.tangem.tap.common.redux.global.AndroidResources +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.TangemSdkManager @@ -66,9 +68,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { tangemSdk = TangemSdk.init(this, TangemSdkManager.config) tangemSdkManager = TangemSdkManager(tangemSdk, this) + store.dispatch(GlobalAction.SetResources(getAndroidResources())) store.dispatch(WalletConnectAction.RestoreSessions) } + private fun getAndroidResources(): AndroidResources { + return AndroidResources( + AndroidResources.RString( + R.string.copy_toast_msg, + R.string.details_notification_erase_wallet_not_possible + ) + ) + } + private fun systemActions() { // makes the status bar text dark window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 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 114c17c7cd..b3a0c345e2 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -7,6 +7,8 @@ import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog 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.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -43,24 +45,25 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) + is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, - context = context + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, + context = context ) is WalletConnectDialog.OpeningSessionRejected -> { SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_same_wcuri, - context = context + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_same_wcuri, + context = context ) } is WalletConnectDialog.SessionTimeout -> { SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_error_timeout, - context = context + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_error_timeout, + context = context ) } is WalletConnectDialog.ApproveWcSession -> 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 02c794329c..f96f16f5ee 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 @@ -10,26 +10,24 @@ import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.DetailsConfirmFragment import com.tangem.tap.features.details.ui.DetailsFragment import com.tangem.tap.features.details.ui.DetailsSecurityFragment -import com.tangem.tap.features.details.ui.twins.CreateTwinWalletFragment -import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment 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.send.ui.SendFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment -import com.tangem.tap.features.wallet.ui.OnboardingTwinsFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.R fun FragmentActivity.openFragment( - screen: AppScreen, - addToBackstack: Boolean, - fgShareTransition: FragmentShareTransition? = null + screen: AppScreen, + addToBackstack: Boolean, + fgShareTransition: FragmentShareTransition? = null ) { val transaction = this.supportFragmentManager.beginTransaction() val fragment = fragmentFactory(screen) @@ -39,7 +37,7 @@ fun FragmentActivity.openFragment( transaction.setReorderingAllowed(true) shareElements.forEach { shareElement -> shareElement.wView.get()?.let { view -> - transaction.addSharedElement(view, shareElement.name) + transaction.addSharedElement(view, shareElement.elementName) } } } @@ -60,8 +58,8 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { } fun FragmentActivity.addOnBackPressedDispatcher( - isEnabled: Boolean = true, - onBackPressed: VoidCallback + isEnabled: Boolean = true, + onBackPressed: VoidCallback ): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) { override fun handleOnBackPressed() { onBackPressed() @@ -70,10 +68,11 @@ fun FragmentActivity.addOnBackPressedDispatcher( private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { +// AppScreen.Home -> TestLeapfrogFragment() AppScreen.Home -> HomeFragment() AppScreen.OnboardingNote -> OnboardingNoteFragment() AppScreen.OnboardingWallet -> OnboardingWalletFragment() - AppScreen.OnboardingTwins -> OnboardingTwinsFragment() + AppScreen.OnboardingTwins -> TwinsCardsFragment() AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() @@ -81,8 +80,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.DetailsConfirm -> DetailsConfirmFragment() AppScreen.DetailsSecurity -> DetailsSecurityFragment() AppScreen.Disclaimer -> DisclaimerFragment() - AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment() - AppScreen.CreateTwinWallet -> CreateTwinWalletFragment() AppScreen.AddTokens -> AddTokensFragment() AppScreen.WalletDetails -> WalletDetailsFragment() AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment() diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index f5fd832f2b..17db11e102 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -4,6 +4,7 @@ import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -17,6 +18,10 @@ fun Store<*>.dispatchOnMain(action: Action) { } } +suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { + store.state.globalState.tapWalletManager.onCardScanned(scanResponse) +} + fun Store<*>.dispatchOpenUrl(url: String) { store.dispatch(NavigationAction.OpenUrl(url)) } diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt index 884cef3f94..e83f7b8b92 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt @@ -6,6 +6,7 @@ import android.view.animation.AccelerateDecelerateInterpolator import android.widget.FrameLayout import androidx.core.animation.doOnEnd import androidx.core.view.children +import com.tangem.common.extensions.VoidCallback import com.tangem.tangem_sdk_new.extensions.dpToPx /** @@ -15,9 +16,9 @@ class LeapfrogWidget( private val parentContainer: FrameLayout, private val calculator: PropertyCalculator = PropertyCalculator(), ) { - private val foldAnimationDuration = 500L - private val leapAnimationDuration = 800L - private val leapBackAnimationDuration = 800L + private val foldAnimationDuration = 300L + private val leapAnimationDuration = 500L + private val leapBackAnimationDuration = 500L private val leapViews = mutableListOf() @@ -25,9 +26,6 @@ class LeapfrogWidget( private var leapProgress: Int = 100 private var leapBackProgress: Int = 100 - var isFolded: Boolean = true - private set - init { calculator.setTranslationConverter { parentContainer.dpToPx(it) } if (viewIsFullFledged()) { @@ -43,12 +41,11 @@ class LeapfrogWidget( * Apply initial properties to the views */ fun initViews() { - isFolded = true leapViews.forEach { it.initView() } } - fun fold(onEnd: () -> Unit = {}) { - val animator = foldAnimator() + fun fold(animate: Boolean = true, onEnd: VoidCallback = {}) { + val animator = foldAnimator(animate) if (animator == null) { onEnd() } else { @@ -57,8 +54,8 @@ class LeapfrogWidget( } } - fun unfold(onEnd: () -> Unit = {}) { - val animator = unfoldAnimator() + fun unfold(animate: Boolean = true, onEnd: VoidCallback = {}) { + val animator = unfoldAnimator(animate) if (animator == null) { onEnd() } else { @@ -67,33 +64,31 @@ class LeapfrogWidget( } } - fun foldAnimator(): Animator? { - if (!canFoldUnfold() || isFolded) return null + fun foldAnimator(animate: Boolean = true): Animator? { + if (!canFoldUnfold()) return null - return createFoldUnfoldAnimator(true, foldAnimationDuration) + return createFoldUnfoldAnimator(true, animate, foldAnimationDuration) } - fun unfoldAnimator(): Animator? { - if (!canFoldUnfold() || !isFolded) return null + fun unfoldAnimator(animate: Boolean = true): Animator? { + if (!canFoldUnfold()) return null - return createFoldUnfoldAnimator(false, foldAnimationDuration) + return createFoldUnfoldAnimator(false, animate, foldAnimationDuration) } - private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, duration: Long): Animator { + private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, animate: Boolean, duration: Long): Animator { val animatorsList = mutableListOf() + val animateDuration = if (animate) duration else 0 leapViews.forEach { if (isFoldAnimation) { it.fold() - animatorsList.add(it.view.foldAnimation(duration, it.state.properties)) + animatorsList.add(it.view.foldAnimation(animateDuration, it.state.properties)) } else { it.unfold() - animatorsList.add(it.view.unfoldAnimation(duration, it.state.properties)) + animatorsList.add(it.view.unfoldAnimation(animateDuration, it.state.properties)) } } - animatorsList.add(createProgressListener(duration) { - foldUnfoldProgress = it - if (it == 100) isFolded = isFoldAnimation - }) + animatorsList.add(createProgressListener(animateDuration) { foldUnfoldProgress = it }) val animator = AnimatorSet() animator.interpolator = AccelerateDecelerateInterpolator() @@ -101,13 +96,13 @@ class LeapfrogWidget( return animator } - fun leap(onEnd: () -> Unit = {}) { + fun leap(animate: Boolean = true, onEnd: VoidCallback = {}) { if (!canLeap()) { onEnd() return } - val duration = leapAnimationDuration + val duration = if (animate) leapAnimationDuration else 0 val animatorsList = mutableListOf() leapViews.forEach { when (it.leap()) { @@ -128,13 +123,13 @@ class LeapfrogWidget( animator.start() } - fun leapBack(onEnd: () -> Unit = {}) { + fun leapBack(animate: Boolean = true, onEnd: VoidCallback = {}) { if (!canLeapBack()) { onEnd() return } - val duration = leapBackAnimationDuration + val duration = if (animate) leapBackAnimationDuration else 0 val animatorsList = mutableListOf() leapViews.forEach { when (it.leapBack()) { @@ -160,12 +155,11 @@ class LeapfrogWidget( fun getViewByPosition(position: Int): LeapView = leapViews.first { it.state.currentPosition == position } - fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(isFolded, leapViews.map { it.state }) + fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(leapViews.map { it.state }) fun applyState(state: LeapfrogWidgetState) { - isFolded = state.isFolded - state.leapViewStates.forEach { state -> - leapViews.firstOrNull { it.index == state.index }?.applyState(state) + state.leapViewStates.forEach { viewState -> + leapViews.firstOrNull { it.index == viewState.index }?.applyState(viewState) } } @@ -183,7 +177,6 @@ class LeapfrogWidget( private fun canLeap(): Boolean { return when { !viewIsFullFledged() -> false - isFolded -> false leapBackInProgress() -> false leapProgress < 70 -> false else -> true @@ -193,7 +186,6 @@ class LeapfrogWidget( private fun canLeapBack(): Boolean { return when { !viewIsFullFledged() -> false - isFolded -> false leapInProgress() -> false leapBackProgress < 70 -> false else -> true @@ -206,14 +198,13 @@ class LeapfrogWidget( } data class LeapfrogWidgetState( - val isFolded: Boolean, val leapViewStates: List ) class PropertyCalculator( val elevationFactor: Float = 1f, - val decreaseScaleFactor: Float = 0.1f, - val yTranslationFactor: Float = 20f, + val decreaseScaleFactor: Float = 0.15f, + val yTranslationFactor: Float = 35f, val decreaseOverLiftingFactor: Float = 0.85f, ) { // 0 view it is view on the top of stack, but it is last in parent.children diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 38c76819b3..d797ff9010 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -8,6 +8,7 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer import com.tangem.tap.features.home.redux.HomeReducer import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.tokens.redux.TokensReducer @@ -19,18 +20,19 @@ fun appReducer(action: Action, state: AppState?): AppState { if (action is AppAction.RestoreState) return action.state return AppState( - navigationState = NavigationReducer.reduce(action, state), - globalState = globalReducer(action, state), - homeState = HomeReducer.reduce(action, state), - onboardingNoteState = OnboardingNoteReducer.reduce(action, state), - onboardingWalletState = OnboardingWalletReducer.reduce(action, state), - onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state), - walletState = WalletReducer.reduce(action, state), - sendState = SendScreenReducer.reduce(action, state.sendState), - detailsState = DetailsReducer.reduce(action, state), - disclaimerState = DisclaimerReducer.reduce(action, state), - tokensState = TokensReducer.reduce(action, state), - walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState) + navigationState = NavigationReducer.reduce(action, state), + globalState = globalReducer(action, state), + homeState = HomeReducer.reduce(action, state), + onboardingNoteState = OnboardingNoteReducer.reduce(action, state), + onboardingWalletState = OnboardingWalletReducer.reduce(action, state), + onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state), + walletState = WalletReducer.reduce(action, state), + twinCardsState = TwinCardsReducer.reduce(action, state), + sendState = SendScreenReducer.reduce(action, state.sendState), + detailsState = DetailsReducer.reduce(action, state), + disclaimerState = DisclaimerReducer.reduce(action, state), + tokensState = TokensReducer.reduce(action, state), + walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState) ) } 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 40e0c80077..39b221f517 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 @@ -16,6 +16,8 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMidd import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsMiddleware 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.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware @@ -35,6 +37,7 @@ data class AppState( val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(), val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(), val walletState: WalletState = WalletState(), + val twinCardsState: TwinCardsState = TwinCardsState(), val sendState: SendState = SendState(), val detailsState: DetailsState = DetailsState(), val disclaimerState: DisclaimerState = DisclaimerState(), @@ -45,18 +48,19 @@ data class AppState( companion object { fun getMiddleware(): List> { return listOf( - logMiddleware, navigationMiddleware, notificationsMiddleware, - GlobalMiddleware.handler, - HomeMiddleware.handler, - OnboardingNoteMiddleware.handler, - OnboardingWalletMiddleware.handler, - OnboardingOtherCardsMiddleware.handler, - WalletMiddleware().walletMiddleware, - SendMiddleware().sendMiddleware, - DetailsMiddleware().detailsMiddleware, - DisclaimerMiddleware().disclaimerMiddleware, - TokensMiddleware().tokensMiddleware, - WalletConnectMiddleware().walletConnectMiddleware + logMiddleware, navigationMiddleware, notificationsMiddleware, + GlobalMiddleware.handler, + HomeMiddleware.handler, + OnboardingNoteMiddleware.handler, + OnboardingWalletMiddleware.handler, + OnboardingOtherCardsMiddleware.handler, + WalletMiddleware().walletMiddleware, + TwinCardsMiddleware.handler, + SendMiddleware().sendMiddleware, + DetailsMiddleware().detailsMiddleware, + DisclaimerMiddleware().disclaimerMiddleware, + TokensMiddleware().tokensMiddleware, + WalletConnectMiddleware().walletConnectMiddleware ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index 36e1a749a5..4334d55f59 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux +import com.tangem.common.extensions.VoidCallback import com.tangem.tap.features.wallet.redux.AddressData /** @@ -11,7 +12,7 @@ sealed class AppDialog : StateDialog { object ScanFailsDialog : AppDialog() data class AddressInfoDialog( val addressData: AddressData, - val onCopyAddress: () -> Unit, - val onExploreAddress: () -> Unit + val onCopyAddress: VoidCallback, + val onExploreAddress: VoidCallback ) : AppDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 297ba9fd40..278672bff1 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 @@ -23,6 +23,9 @@ sealed class GlobalAction : Action { data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction + // android resources + data class SetResources(val resources: AndroidResources) : GlobalAction() + // dialogs data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() object HideDialog : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index ea80ab463c..10aa0f1533 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -47,7 +47,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.RestoreAppCurrency -> { store.dispatch(GlobalAction.RestoreAppCurrency.Success( - preferencesStorage.getAppCurrency() + preferencesStorage.getAppCurrency() )) } is GlobalAction.HideWarningMessage -> { @@ -81,7 +81,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState val userStatusResponse = MoonpayService().getUserStatus(apiKey) if (userStatusResponse is Result.Success) { store.dispatchOnMain( - GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) + GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) ) } } @@ -94,7 +94,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState withMainContext { when (result) { is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) + tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data) action.onSuccess?.invoke(result.data) } is CompletionResult.Failure -> action.onFailure?.invoke(result.error) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 29ec84876e..f5ce207390 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -12,6 +12,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { + is GlobalAction.SetResources -> { + globalState.copy(resources = action.resources) + } is GlobalAction.Onboarding.Start -> { val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage val onboardingState = OnboardingManager(action.scanResponse, usedCardsPrefStorage) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index e27641b854..e0166e90e8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -27,7 +27,17 @@ data class GlobalState( val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, val moonPayUserStatus: MoonPayUserStatus? = null, + val resources: AndroidResources = AndroidResources(), ) : StateType + +data class AndroidResources( + val strings: RString = RString() +) { + data class RString( + val addressWasCopied: Int = -1, + val walletIsNotEmpty: Int = -1 + ) +} typealias CryptoCurrencyName = String typealias FiatCurrencyName = String \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt new file mode 100644 index 0000000000..69547d020f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt @@ -0,0 +1,35 @@ +package com.tangem.tap.common.redux.navigation + +import android.view.View +import androidx.transition.TransitionSet +import java.lang.ref.WeakReference + +/** +[REDACTED_AUTHOR] + */ +data class FragmentShareTransition( + val shareElements: List, + val enterTransitionSet: TransitionSet, + val exitTransitionSet: TransitionSet +) + +/** + * For ease of use, the name is used as transitionName\name into the FragmentTransaction.addSharedElement + */ +class ShareElement(view: View, name: String? = null) { + + val wView: WeakReference + val elementName: String + + init { + name?.let { view.transitionName = it } + elementName = view.transitionName + ?: throw UnsupportedOperationException("ShareElement require the name") + wView = WeakReference(view) + } + + companion object { + val imvFrontCard = "imv_front_card" + val imvBackCard = "imv_back_card" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index 331dc07e1a..d2147ad3c6 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -1,8 +1,6 @@ package com.tangem.tap.common.redux.navigation -import android.view.View import androidx.fragment.app.FragmentActivity -import androidx.transition.TransitionSet import org.rekotlin.Action import java.lang.ref.WeakReference @@ -19,12 +17,4 @@ sealed class NavigationAction : Action { data class ActivityCreated(val activity: WeakReference) : NavigationAction() object ActivityDestroyed : NavigationAction() -} - -data class FragmentShareTransition( - val shareElements: List, - val enterTransitionSet: TransitionSet, - val exitTransitionSet: TransitionSet -) - -data class ShareElement(val wView: WeakReference, val name: String) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index 80cd5e3260..e742aa2e6b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -20,8 +20,7 @@ private fun internalReduce(action: Action, state: AppState): NavigationState { navState.copy(backStack = navState.backStack + navigationAction.screen) } is NavigationAction.PopBackTo -> { - val screen = - navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() + val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() val index = navState.backStack.lastIndexOf(screen) + 1 state.navigationState.copy(backStack = navState.backStack.subList(0, index)) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 4fa02150f0..61e3d263da 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -5,8 +5,8 @@ import org.rekotlin.StateType import java.lang.ref.WeakReference data class NavigationState( - val backStack: List = listOf(AppScreen.Home), - val activity: WeakReference? = null + val backStack: List = listOf(AppScreen.Home), + val activity: WeakReference? = null ) : StateType enum class AppScreen { @@ -16,7 +16,6 @@ enum class AppScreen { Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, - CreateTwinWalletWarning, CreateTwinWallet, AddTokens, WalletConnectSessions, QrScan diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt index e7ef08e026..277f839c05 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -41,30 +41,31 @@ class RefreshBalanceWidget( val progressState = state as? ProgressState ?: return val currentStateByView = getState() - - if (progressState == currentStateByView) return + when { + currentStateByView == progressState -> return + currentStateByView == ProgressState.Done -> if (progressState == ProgressState.Error) return + } animateState(progressState) viewSwitcher.showNext() - } private fun animateState(state: ProgressState) { when (state) { ProgressState.Done, ProgressState.Error -> { progressViewAnimation?.cancel() + progressViewAnimation = null } ProgressState.Loading -> { - val animation = RotateAnimation( - 0f, 360f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f + progressViewAnimation = RotateAnimation( + 0f, 360f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f ) - animation.duration = 700 - animation.interpolator = AccelerateInterpolator() - animation.repeatCount = -1 - arrowView.startAnimation(animation) - progressViewAnimation = animation + progressViewAnimation?.duration = 700 + progressViewAnimation?.interpolator = AccelerateInterpolator() + progressViewAnimation?.repeatCount = -1 + progressViewAnimation?.let { arrowView.startAnimation(it) } } } } @@ -88,10 +89,10 @@ class RefreshBalanceWidget( class ShowAnimation : AnimationSet(true) { init { addAnimation(ScaleAnimation( - 0f, 1f, - 0f, 1f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f) + 0f, 1f, + 0f, 1f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f) ) addAnimation(AlphaAnimation(0f, 1f)) duration = 300 diff --git a/app/src/main/java/com/tangem/tap/domain/ProductType.kt b/app/src/main/java/com/tangem/tap/domain/ProductType.kt index 02f0246c82..07cbea8905 100644 --- a/app/src/main/java/com/tangem/tap/domain/ProductType.kt +++ b/app/src/main/java/com/tangem/tap/domain/ProductType.kt @@ -4,5 +4,5 @@ package com.tangem.tap.domain [REDACTED_AUTHOR] */ enum class ProductType { - Note, Wallet, Twin, Other + Note, Wallet, Twins, Other } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 5253a9f91c..3a3acc1a87 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -23,7 +23,6 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -45,9 +44,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult { return runTaskAsync( - CreateProductWalletAndRescanTask(scanResponse.productType), - scanResponse.card.cardId, - Message(context.getString(R.string.initial_message_create_wallet_body)) + CreateProductWalletAndRescanTask(scanResponse.productType), + scanResponse.card.cardId, + Message(context.getString(R.string.initial_message_create_wallet_body)) ) } @@ -105,13 +104,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult = - withContext(Dispatchers.Main) { - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> - continuation.resume(result) + withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) + } } } - } private suspend fun runTaskAsyncReturnOnMain( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, @@ -120,8 +119,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return withContext(Dispatchers.Main) { result } } - fun changeDisplayedCardIdNumbersCount(card: Card) { - tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTangemTwin()) 4 else null + fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) { + tangemSdk.config.cardIdDisplayedNumbersCount = if (scanResponse.isTangemTwins()) 4 else null } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 9133792893..14f7b50b6b 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -16,8 +16,6 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.* import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.domain.twins.getTwinCardNumber -import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction @@ -59,8 +57,8 @@ class TapWalletManager { suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) { Timber.d(wallet.getTokens().toString()) val currencies = wallet.getTokens() - .map { Currency.Token(it) } - .plus(Currency.Blockchain(wallet.blockchain)) + .map { Currency.Token(it) } + .plus(Currency.Blockchain(wallet.blockchain)) loadFiatRate(fiatCurrency, currencies) } @@ -89,11 +87,6 @@ class TapWalletManager { store.dispatch(GlobalAction.SaveScanNoteResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) - if (data.card.isTangemTwin()) { - data.card.getTwinCardNumber()?.let { - store.dispatch(WalletAction.TwinsAction.SetTwinCard(it, true)) - } - } val blockchain = data.getBlockchain() if (blockchain == Blockchain.Ethereum || @@ -122,56 +115,47 @@ class TapWalletManager { suspend fun loadData(data: ScanResponse) { withContext(Dispatchers.Main) { - store.dispatch(WalletAction.LoadCardInfo(data.card)) + getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { + store.dispatch(it) + return@withContext + } - when { - data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { - store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) + val config = store.state.globalState.configManager?.config ?: return@withContext + val blockchain = data.getBlockchain() + val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) + + if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { + val primaryToken = data.getPrimaryToken() + + store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) + if (primaryToken != null) { + primaryWalletManager.addToken(primaryToken) + store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) } - data.card.wallets.isEmpty() || - (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { - store.dispatch(WalletAction.EmptyWallet) + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data.card, blockchain, primaryWalletManager) + } else { + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) + store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) } - else -> { - val config = store.state.globalState.configManager?.config ?: return@withContext - val blockchain = data.getBlockchain() - val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) - - if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { - val primaryToken = data.getPrimaryToken() - - store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) - if (primaryToken != null) { - primaryWalletManager.addToken(primaryToken) - store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) - } - if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, primaryWalletManager) - } else { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) - } - - } else { - if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, null) - } - } - val moonPayUserStatus = store.state.globalState.moonPayUserStatus - store.dispatch(WalletAction.LoadWallet( - allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true, - allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true, - )) - store.dispatch(WalletAction.LoadFiatRate()) + } else { + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data.card, blockchain, null) } } + val moonPayUserStatus = store.state.globalState.moonPayUserStatus + store.dispatch(WalletAction.LoadWallet( + allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true, + allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true, + )) + store.dispatch(WalletAction.LoadFiatRate()) } } private fun loadMultiWalletData( - card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? + card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { val primaryTokens = primaryWalletManager?.cardTokens ?: emptySet() val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) @@ -179,9 +163,9 @@ class TapWalletManager { if (savedCurrencies == null) { if (primaryBlockchain != null && primaryWalletManager != null) { store.dispatch(WalletAction.MultiWallet.SaveCurrencies( - CardCurrencies( - blockchains = setOf(primaryBlockchain), tokens = primaryTokens - ))) + CardCurrencies( + blockchains = setOf(primaryBlockchain), tokens = primaryTokens + ))) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) store.dispatch(WalletAction.MultiWallet.AddTokens(primaryTokens.toList())) @@ -191,7 +175,7 @@ class TapWalletManager { CardCurrencies(blockchains = blockchains, tokens = emptySet()) )) val walletManagers = - walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList()) + walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList()) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList())) } @@ -200,12 +184,12 @@ class TapWalletManager { } else { val blockchains = savedCurrencies.blockchains.toList() val walletManagers = if ( - primaryTokens.isNotEmpty() && - primaryWalletManager != null && primaryBlockchain != null + primaryTokens.isNotEmpty() && + primaryWalletManager != null && primaryBlockchain != null ) { val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain } walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary) - .plus(primaryWalletManager) + .plus(primaryWalletManager) } else { walletManagerFactory.makeWalletManagersForApp(card, blockchains) } @@ -218,28 +202,38 @@ class TapWalletManager { suspend fun reloadData(data: ScanResponse) { withContext(Dispatchers.Main) { - when { - data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { - store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) - } - data.card.wallets.isEmpty() || - (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { - store.dispatch(WalletAction.EmptyWallet) - } - else -> { - if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) - return@withContext - } - val config = store.state.globalState.configManager?.config ?: return@withContext - val moonpayUserStatus = store.state.globalState.moonPayUserStatus - store.dispatch(WalletAction.LoadWallet( - allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true, - allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true, - )) - store.dispatch(WalletAction.LoadFiatRate()) - } + getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { + store.dispatch(it) + return@withContext } + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) + return@withContext + } + + val config = store.state.globalState.configManager?.config ?: return@withContext + val moonpayUserStatus = store.state.globalState.moonPayUserStatus + store.dispatch(WalletAction.LoadWallet( + allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true, + allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true, + )) + store.dispatch(WalletAction.LoadFiatRate()) + } + } + + private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? { + return when { + // check order is important + data.isTangemTwins() && !data.twinsIsTwinned() -> { + WalletAction.EmptyWallet + } + data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + WalletAction.LoadData.Failure(TapError.UnknownBlockchain) + } + data.card.wallets.isEmpty() -> { + WalletAction.EmptyWallet + } + else -> null } } 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 9003c9f4fc..d734698fd5 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -28,10 +28,13 @@ object TapWorkarounds { return excludedBatch || excludedIssuerName } - fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) - fun Card.isTangemWallet(): Boolean = tangemWalletBatches.contains(batchId) + @Deprecated("Use ScanResponse.isTangemNote") + fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId) - fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId] + @Deprecated("Use ScanResponse.isTangemWallet") + fun isTangemWallet(card: Card): Boolean = tangemWalletBatches.contains(card.batchId) + + fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId] private const val START_2_COIN_ISSUER = "start2coin" private const val TEST_CARD_BATCH = "99FF" @@ -55,7 +58,7 @@ object TapWorkarounds { "AB02" to Blockchain.Ethereum, "AB03" to Blockchain.CardanoShelley, "AB04" to Blockchain.Dogecoin, - "AB05" to Blockchain.Binance, + "AB05" to Blockchain.BSC, "AB06" to Blockchain.XRP, ) } @@ -64,7 +67,7 @@ val DELAY_SDK_DIALOG_CLOSE = 1400L val Card.isMultiwalletAllowed: Boolean get() { - return !isTangemTwin() && !isStart2Coin && !isTangemNote() + return !isTangemTwin() && !isStart2Coin && !isTangemNote(this) && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || getSingleWallet()?.curve == EllipticCurve.Secp256k1) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index e08ba924b5..33aa1e1b4d 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -14,6 +14,7 @@ data class Config( val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), val isSendingToPayIdEnabled: Boolean = true, val isTopUpEnabled: Boolean = false, + @Deprecated("Not relevant since version 3.23") val isCreatingTwinCardsAllowed: Boolean = false ) @@ -50,10 +51,10 @@ class ConfigManager( fun resetToDefault(name: String) { when (name) { isSendingToPayIdEnabled -> config = - config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled) + config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled) isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled) isCreatingTwinCardsAllowed -> config = - config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) + config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index d658847574..4be4dda58f 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -13,10 +13,10 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean { } } -fun Blockchain.amountToCreateAccount(token: Token? = null): Double? { +fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { return when (this) { - Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble() - Blockchain.XRP -> 10.toDouble() + Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ZERO + Blockchain.XRP -> BigDecimal(10) else -> null } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 99ed8231c4..aa85331ab2 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -9,7 +9,6 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.hexToBytes import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.domain.twins.isTangemTwin fun WalletManagerFactory.makeWalletManagerForApp( card: Card, @@ -65,7 +64,7 @@ fun WalletManagerFactory.makePrimaryWalletManager( val publicKey = wallet?.publicKey ?: return null val curveToUse = wallet.curve - return if (card.isTangemTwin() && data.secondTwinPublicKey != null) { + return if (data.isTangemTwins() && data.secondTwinPublicKey != null) { makeMultisigWalletManager( cardId = card.cardId, walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(), diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt deleted file mode 100644 index 43425eab48..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tangem.tap.domain.tasks - -import com.tangem.* -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.CompletionResult -import com.tangem.common.card.Card -import com.tangem.common.card.FirmwareVersion -import com.tangem.common.core.CardSession -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.TangemError -import com.tangem.common.extensions.toHexString -import com.tangem.operations.ScanTask -import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand -import com.tangem.tap.domain.ProductType -import com.tangem.tap.domain.TapSdkError -import com.tangem.tap.domain.TapWorkarounds.isExcluded -import com.tangem.tap.domain.TapWorkarounds.isTangemNote -import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.domain.twins.isTangemTwin -import com.tangem.tap.store - -class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { - ScanTask().run(session) { result -> - when (result) { - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - - is CompletionResult.Success -> { - val card = this.card ?: result.data - - val error = getErrorIfExcludedCard(card) - if (error != null) { - callback(CompletionResult.Failure(error)) - return@run - } - - if (card.isTangemTwin()) { - dealWithTwinCard(card, session, callback) - } else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) { - createMissingWalletsIfNeeded(card, session, callback) - } else { - callback(CompletionResult.Success( - ScanResponse(card, ProductType.Other, session.environment.walletData))) - } - } - } - } - } - - private fun createMissingWalletsIfNeeded( - card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { - val walletData = session.environment.walletData - if (card.wallets.isEmpty()) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) - return - } - - val curvesPresent = card.wallets.map { it.curve } -// val curvesToCreate = EllipticCurve.values().subtract(curvesPresent) - val curvesToCreate = card.supportedCurves.subtract(curvesPresent) - - if (curvesToCreate.isEmpty()) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) - return - } - - CreateWalletsTask(curvesToCreate.toList()).run(session) { result -> - when (result) { - is CompletionResult.Success -> - callback(CompletionResult.Success(ScanResponse(result.data, ProductType.Other, walletData))) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - - } - - private fun dealWithTwinCard( - card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { - ReadIssuerDataCommand().run(session) { readDataResult -> - when (readDataResult) { - is CompletionResult.Success -> { - val publicKey = card.getSingleWallet()?.publicKey - if (publicKey == null) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) - return@run - } - val verified = TwinCardsManager.verifyTwinPublicKey( - readDataResult.data.issuerData, publicKey - ) - if (verified) { - val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) - callback(CompletionResult.Success( - ScanResponse( - card = card, - ProductType.Other, - walletData = session.environment.walletData, - secondTwinPublicKey = twinPublicKey.toHexString()) - )) - return@run - } else { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) - } - } - is CompletionResult.Failure -> - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) - } - } - } - - private fun getErrorIfExcludedCard(card: Card): TangemError? { - if (card.isExcluded()) return TapSdkError.CardForDifferentApp - // Disable new cards on the old version of the app // TODO: remove when cards are supported - // Disable new multi-currency HD wallet cards on the old version of the app -// if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard() - return null - } - - private fun getWalletManagerFactory(): WalletManagerFactory { - val blockchainSdkConfig = store.state.globalState.configManager?.config - ?.blockchainSdkConfig ?: BlockchainSdkConfig() - return WalletManagerFactory(blockchainSdkConfig) - } - -} \ No newline at end of file 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 3b3c6ea2ae..f1024cdf11 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 @@ -45,7 +45,7 @@ private class CreateProductWalletTask( override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() - ProductType.Twin -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") + ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") ProductType.Wallet -> CreateWalletTangemWallet() else -> CreateWalletOtherCards() } @@ -64,7 +64,7 @@ private class CreateWalletTangemNote : ProductCommandProcessor { @@ -70,9 +75,9 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable ScanNoteProcessor() - card.isTangemTwin() -> ScanTwinProcessor() - card.isTangemWallet() -> ScanWalletProcessor() + TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor() + card.isTangemTwins() -> ScanTwinProcessor() + TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor() else -> ScanOtherCardsProcessor() } commandProcessor.proceed(card, session, callback) @@ -89,6 +94,8 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable { override fun proceed( card: Card, @@ -120,21 +127,21 @@ private class ScanTwinProcessor : ProductCommandProcessor { is CompletionResult.Success -> { val publicKey = card.getSingleWallet()?.publicKey if (publicKey == null) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null))) return@run } - val verified = TwinCardsManager.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) + val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) val walletData = session.environment.walletData - val response = ScanResponse(card, ProductType.Twin, walletData, twinPublicKey.toHexString()) + val response = ScanResponse(card, ProductType.Twins, walletData, twinPublicKey.toHexString()) callback(CompletionResult.Success(response)) } else { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null))) } } is CompletionResult.Failure -> - callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null))) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index faa839cbe6..861f3db801 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -1,63 +1,52 @@ package com.tangem.tap.domain.twins -import android.content.Context import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Types import com.tangem.Message import com.tangem.blockchain.extensions.Result -import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.CompletionResult import com.tangem.common.KeyPair +import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.crypto.CryptoUtils +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler -import com.tangem.tap.common.extensions.readAssetAsString import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.network.createMoshi import com.tangem.tap.tangemSdkManager -class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) { +class TwinCardsManager(private val card: Card, assetReader: AssetReader) { - private val currentCardId: String = scanResponse.card.cardId + private val currentCardId: String = card.cardId private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null - private val issuerKeyPair: KeyPair = getIssuerKeys( - context, scanResponse.card.issuer.publicKey.toHexString() - ) + private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) - suspend fun createFirstWallet(message: Message): SimpleResult { - val response = tangemSdkManager.runTaskAsync( - CreateFirstTwinWalletTask(), currentCardId, message - ) + suspend fun createFirstWallet(message: Message): CompletionResult { + val response = tangemSdkManager.runTaskAsync(CreateFirstTwinWalletTask(), currentCardId, message) when (response) { - is CompletionResult.Success -> { - currentCardPublicKey = response.data.wallet.publicKey.toHexString() - return SimpleResult.Success - } + is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = scanResponse.card + card = card ) } - return SimpleResult.failure(response.error) } } - + return response } - suspend fun createSecondWallet( initialMessage: Message, preparingMessage: Message, creatingWalletMessage: Message, - ): SimpleResult { + ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, firstCardId = currentCardId, @@ -69,20 +58,18 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() - return SimpleResult.Success } is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = scanResponse.card + card = card ) } - return SimpleResult.failure(response.error) } } - + return response } suspend fun complete(message: Message): Result { @@ -97,7 +84,7 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData, - card = scanResponse.card + card = card ) } Result.failure(response.error) @@ -106,16 +93,8 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) } companion object { - fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { - if (issuerData.size < 65 || cardWalletPublicKey == null) return false - - val publicKey = issuerData.sliceArray(0 until 65) - val signedKey = issuerData.sliceArray(65 until issuerData.size) - return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) - } - - private fun getIssuerKeys(context: Context, publicKey: String): KeyPair { - val issuer = getIssuers(context).first { it.publicKey == publicKey } + private fun getIssuerKeys(reader: AssetReader, publicKey: String): KeyPair { + val issuer = getIssuers(reader).first { it.publicKey == publicKey } return KeyPair( publicKey = issuer.publicKey.hexToBytes(), privateKey = issuer.privateKey.hexToBytes() @@ -128,13 +107,17 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) ) } - private fun getIssuers(context: Context): List { - val file = context.readAssetAsString("tangem-app-config/issuers") + private fun getIssuers(reader: AssetReader): List { + val file = reader.readAssetAsString("tangem-app-config/issuers") return getAdapter().fromJson(file)!! } } } +interface AssetReader { + fun readAssetAsString(name: String): String +} + private class Issuer( val id: String, val privateKey: String, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt similarity index 74% rename from app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt rename to app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt index 63f891eb4d..c8f3c02952 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt @@ -5,6 +5,7 @@ import android.animation.ObjectAnimator import android.animation.PropertyValuesHolder import android.view.View import androidx.core.animation.doOnEnd +import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.leapfrogWidget.LeapView import com.tangem.tap.common.leapfrogWidget.LeapViewState import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget @@ -17,47 +18,60 @@ class TwinsCardWidget( val getTopOfAnchorViewForActivateState: () -> Float ) { + var state: TwinCardsWidgetState? = null + private set + + private val animationDuration = 300L + init { if (leapfrogWidget.getViewsCount() != 2) throw UnsupportedOperationException() } - fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { - val animator = createAnimator(animate, onEnd) + fun toWelcome(animate: Boolean = true, onEnd: VoidCallback = {}) { + if (state == TwinCardsWidgetState.Welcome) return + + state = TwinCardsWidgetState.Welcome + val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)) ) - leapfrogWidget.fold { animator.start() } + animator.doOnEnd { onEnd() } + leapfrogWidget.fold(animate) { animator.start() } } - fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { - val animator = createAnimator(animate, onEnd) + fun toLeapfrog(animate: Boolean = true, onEnd: VoidCallback = {}) { + if (state == TwinCardsWidgetState.Leapfrog) return + + state = TwinCardsWidgetState.Leapfrog + val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)) ) - leapfrogWidget.fold { - animator.doOnEnd { - leapfrogWidget.initViews() - leapfrogWidget.unfold() - } - animator.start() + animator.doOnEnd { + leapfrogWidget.initViews() + leapfrogWidget.unfold(animate, onEnd) } + animator.start() } - fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) { - val animator = createAnimator(animate, onEnd) + fun toActivate(animate: Boolean = true, onEnd: VoidCallback = {}) { + if (state == TwinCardsWidgetState.Activate) return + + state = TwinCardsWidgetState.Activate + val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) ) + animator.doOnEnd { onEnd() } animator.start() } - private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { + private fun createAnimator(animate: Boolean): AnimatorSet { return AnimatorSet().apply { - duration = if (animate) 400 else 0 - doOnEnd { onEnd() } + duration = if (animate) animationDuration else 0 } } @@ -72,20 +86,20 @@ class TwinsCardWidget( return when (cardNumber) { TwinCardNumber.First -> { TwinsCardProperties( - xTranslation = -180f, - yTranslation = -350f, + xTranslation = -120f, + yTranslation = -220f, rotation = -3f, elevation = 1f, - scale = 1.1f, + scale = 1f, ) } TwinCardNumber.Second -> { TwinsCardProperties( - xTranslation = 180f, - yTranslation = 10f, + xTranslation = 170f, + yTranslation = 170f, rotation = -3f, elevation = 0f, - scale = 1.1f, + scale = 1f, ) } } @@ -108,7 +122,7 @@ class TwinsCardWidget( xTranslation = twinProperties.xTranslation, yTranslation = twinProperties.yTranslation - topOfAnchorView, rotation = 0f, - scale = twinProperties.scale - 0.4f, + scale = twinProperties.scale - 0.5f, ) } @@ -120,6 +134,10 @@ class TwinsCardWidget( } } +enum class TwinCardsWidgetState { + Welcome, Leapfrog, Activate +} + private data class TwinsCardProperties( val xTranslation: Float = 0f, val yTranslation: Float = 0f, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt index ebcbb5ff2b..8196f39b22 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt @@ -1,11 +1,20 @@ package com.tangem.tap.domain.twins import com.tangem.common.card.Card +import com.tangem.crypto.CryptoUtils class TwinsHelper { companion object { const val TWIN_FILE_NAME = "TwinPublicKey" + fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { + if (issuerData.size < 65 || cardWalletPublicKey == null) return false + + val publicKey = issuerData.sliceArray(0 until 65) + val signedKey = issuerData.sliceArray(65 until issuerData.size) + return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) + } + fun getTwinCardNumber(cardId: String): TwinCardNumber? { return when { firstCardSeries.map { cardId.startsWith(it) }.contains(true) -> { @@ -55,6 +64,7 @@ enum class TwinCardNumber(val number: Int) { } } +@Deprecated("Use ScanResponse.isTangemTwin") fun Card.isTangemTwin(): Boolean { return TwinsHelper.getTwinCardNumber(cardId) != null } diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt similarity index 54% rename from app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt rename to app/src/main/java/com/tangem/tap/features/BaseFragment.kt index bb3046db5e..8b4d0d3f70 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt @@ -1,26 +1,26 @@ -package com.tangem.tap.features.send +package com.tangem.tap.features +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar +import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.store import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_wallet.* -import org.rekotlin.StoreSubscriber /** [REDACTED_AUTHOR] */ -abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { - - abstract fun subscribeToStore() +abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler { protected lateinit var mainView: View - protected val storeSubscribersList = mutableListOf>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,28 +38,29 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { return mainView } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - toolbar?.setNavigationOnClickListener { activity?.onBackPressed() } + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) } - override fun onStart() { - super.onStart() - subscribeToStore() - } - - - override fun onStop() { - storeSubscribersList.forEach { store.unsubscribe(it) } - storeSubscribersList.clear() - super.onStop() - } - - fun showRetrySnackbar(message: String, action: () -> Unit) { + fun showRetrySnackbar(message: String, action: VoidCallback) { val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE) snackbar.setAction(getString(R.string.common_retry)) { snackbar.dismiss() action() }.show() } +} + +interface FragmentOnBackPressedHandler { + fun handleOnBackPressed() +} + +@SuppressLint("FragmentBackPressedCallback") +fun Fragment.addBackPressHandler(handler: FragmentOnBackPressedHandler) { + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + handler.handleOnBackPressed() + } + }) + view?.findViewById(R.id.toolbar)?.setNavigationOnClickListener { activity?.onBackPressed() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt new file mode 100644 index 0000000000..b4f486a1a2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features + +import com.tangem.tap.store +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) { + + abstract fun subscribeToStore() + protected val storeSubscribersList = mutableListOf>() + + override fun onStart() { + super.onStart() + subscribeToStore() + } + + override fun onStop() { + storeSubscribersList.forEach { store.unsubscribe(it) } + storeSubscribersList.clear() + super.onStop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 74b37f90f4..29143f79b9 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 @@ -1,16 +1,12 @@ package com.tangem.tap.features.details.redux -import android.content.Context -import com.tangem.Message import com.tangem.blockchain.common.Wallet -import com.tangem.common.card.Card import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.termsOfUse.CardTou import com.tangem.tap.domain.twins.TwinCardNumber -import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.wallet.R import org.rekotlin.Action @@ -18,17 +14,15 @@ import org.rekotlin.Action sealed class DetailsAction : Action { data class PrepareScreen( - val card: Card, val scanResponse: ScanResponse, val wallets: List, - val isCreatingTwinWalletAllowed: Boolean?, val cardTou: CardTou, val fiatCurrencyName: FiatCurrencyName, val fiatCurrencies: List? = null, ) : DetailsAction() object ShowDisclaimer : DetailsAction() - + data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction() sealed class EraseWallet : DetailsAction() { object Check : EraseWallet() @@ -48,44 +42,6 @@ sealed class DetailsAction : Action { object Success : EraseWallet() } - sealed class CreateTwinWalletAction : DetailsAction() { - data class ShowWarning( - val twinCardNumber: TwinCardNumber?, - val createTwinWallet: CreateTwinWallet = CreateTwinWallet.RecreateWallet - ) : CreateTwinWalletAction() - object NotEmpty : CreateTwinWalletAction(), NotificationAction { - override val messageResource = R.string.details_notification_erase_wallet_not_possible - } - object ShowAlert : CreateTwinWalletAction() - object HideAlert : CreateTwinWalletAction() - object Proceed: CreateTwinWalletAction() - - object Cancel : CreateTwinWalletAction() { - object Confirm : CreateTwinWalletAction() - } - - data class LaunchFirstStep( - val message: Message, val context: Context - ) : CreateTwinWalletAction() { - object Success : CreateTwinWalletAction() - object Failure : CreateTwinWalletAction() - } - - data class LaunchSecondStep( - val initialMessage: Message, - val preparingMessage: Message, - val creatingWalletMessage: Message, - ) : CreateTwinWalletAction() { - object Success : CreateTwinWalletAction() - object Failure : CreateTwinWalletAction() - } - - data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() { - data class Success(val scanResponse: ScanResponse) : CreateTwinWalletAction() - object Failure : CreateTwinWalletAction() - } - } - 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 15eca00858..22bbd11511 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,13 +4,16 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain 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.features.details.redux.twins.CreateTwinWalletMiddleware import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.preferencesStorage @@ -26,7 +29,6 @@ class DetailsMiddleware { private val eraseWalletMiddleware = EraseWalletMiddleware() private val appCurrencyMiddleware = AppCurrencyMiddleware() private val manageSecurityMiddleware = ManageSecurityMiddleware() - private val twinWalletMiddleware = CreateTwinWalletMiddleware() val detailsMiddleware: Middleware = { dispatch, state -> { next -> { action -> @@ -35,11 +37,25 @@ class DetailsMiddleware { is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action) is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) - is DetailsAction.CreateTwinWalletAction -> twinWalletMiddleware.handle(action) is DetailsAction.ShowDisclaimer -> { store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } + is DetailsAction.ReCreateTwinsWallet -> { + val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull() + if (wallet == null) { + store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) + } else { + if (wallet.hasSendableAmountsOrPendingTransactions()) { + val walletIsNotEmpty = store.state.globalState.resources.strings.walletIsNotEmpty + store.dispatchNotification(walletIsNotEmpty) + } else { + store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) + } + } + } } next(action) } @@ -84,7 +100,7 @@ class DetailsMiddleware { store.dispatch(NavigationAction.PopBackTo()) } is DetailsAction.EraseWallet.Confirm -> { - val card = store.state.detailsState.card ?: return + val card = store.state.detailsState.card ?: return scope.launch { val result = tangemSdkManager.eraseWallet(card) withContext(Dispatchers.Main) { 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 d2bb330e40..d2622417f7 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 @@ -7,10 +7,7 @@ import com.tangem.tap.domain.TapWorkarounds.isStart2Coin import com.tangem.tap.domain.extensions.isWalletDataSupported import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.domain.extensions.toSendableAmounts -import com.tangem.tap.domain.twins.getTwinCardNumber import com.tangem.tap.domain.twins.isTangemTwin -import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer -import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState import com.tangem.tap.features.wallet.models.hasPendingTransactions import org.rekotlin.Action import java.util.* @@ -38,34 +35,17 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { is DetailsAction.ManageSecurity -> { handleSecurityAction(action, detailsState) } - is DetailsAction.CreateTwinWalletAction -> { - CreateTwinWalletReducer.handle(action, detailsState) - } else -> detailsState } } private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { - val twinsState = if (action.card.isTangemTwin()) { - CreateTwinWalletState( - scanResponse = action.scanResponse, - twinCardNumber = action.card.getTwinCardNumber(), - createTwinWallet = null, - showAlert = false, - allowRecreatingWallet = action.isCreatingTwinWalletAllowed - ) - } else { - null - } - return DetailsState( - card = action.card, wallets = action.wallets, - cardInfo = action.card.toCardInfo(), - appCurrencyState = AppCurrencyState( - action.fiatCurrencyName - ), - createTwinWalletState = twinsState, - cardTermsOfUseUrl = action.cardTou.getUrl(action.card) + card = action.scanResponse.card, + wallets = action.wallets, + cardInfo = action.scanResponse.card.toCardInfo(), + appCurrencyState = AppCurrencyState(action.fiatCurrencyName), + cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card) ) } @@ -99,7 +79,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS } private fun handleAppCurrencyAction( - action: DetailsAction.AppCurrencyAction, state: DetailsState + action: DetailsAction.AppCurrencyAction, state: DetailsState ): DetailsState { return when (action) { is DetailsAction.AppCurrencyAction.SetCurrencies -> { @@ -113,9 +93,9 @@ private fun handleAppCurrencyAction( } is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { state.copy( - appCurrencyState = state.appCurrencyState.copy( - fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false - ) + appCurrencyState = state.appCurrencyState.copy( + fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false + ) ) } else -> state @@ -123,7 +103,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 -> { @@ -143,8 +123,8 @@ private fun handleSecurityAction( is DetailsAction.ManageSecurity.OpenSecurity -> { if (state.card?.isStart2Coin == true) { return state.copy(securityScreenState = state.securityScreenState?.copy( - allowedOptions = EnumSet.of(SecurityOption.LongTap), - selectedOption = state.securityScreenState.currentOption + allowedOptions = EnumSet.of(SecurityOption.LongTap), + selectedOption = state.securityScreenState.currentOption )) } @@ -152,13 +132,13 @@ private fun handleSecurityAction( state.card, state.securityScreenState?.currentOption ) state.copy(securityScreenState = state.securityScreenState?.copy( - allowedOptions = allowedSecurityOptions, - selectedOption = state.securityScreenState.currentOption + allowedOptions = allowedSecurityOptions, + selectedOption = state.securityScreenState.currentOption )) } is DetailsAction.ManageSecurity.SelectOption -> { state.copy(securityScreenState = state.securityScreenState?.copy( - selectedOption = action.option + selectedOption = action.option )) } is DetailsAction.ManageSecurity.ConfirmSelection -> { @@ -213,4 +193,5 @@ private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name val signedHashes = this.signedHashesCount() - return CardInfo(cardId, issuer, signedHashes)} \ No newline at end of file + return CardInfo(cardId, issuer, signedHashes) +} \ No newline at end of file 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 bf16dd3674..40f409c680 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 @@ -6,41 +6,52 @@ import com.tangem.common.card.Card import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.global.FiatCurrencyName -import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.network.coinmarketcap.FiatCurrency +import com.tangem.tap.store import org.rekotlin.StateType import java.util.* +import kotlin.properties.ReadOnlyProperty data class DetailsState( - val card: Card? = null, - val wallets: List = emptyList(), - val cardInfo: CardInfo? = null, - val appCurrencyState: AppCurrencyState = AppCurrencyState(), - val eraseWalletState: EraseWalletState? = null, - val confirmScreenState: ConfirmScreenState? = null, - val securityScreenState: SecurityScreenState? = null, - val createTwinWalletState: CreateTwinWalletState? = null, - val cardTermsOfUseUrl: Uri? = null, -) : StateType + val card: Card? = null, + val wallets: List = emptyList(), + val cardInfo: CardInfo? = null, + val appCurrencyState: AppCurrencyState = AppCurrencyState(), + val eraseWalletState: EraseWalletState? = null, + val confirmScreenState: ConfirmScreenState? = null, + val securityScreenState: SecurityScreenState? = null, + val cardTermsOfUseUrl: Uri? = null, +) : StateType { + + // if you do not delegate - the application crashes on startup, + // because twinCardsState has not been created yet + val twinCardsState: TwinCardsState by ReadOnlyProperty { thisRef, property -> + store.state.twinCardsState + } + + val isTangemTwins: Boolean + get() = store.state.globalState.scanResponse?.isTangemTwins() == true +} data class CardInfo( - val cardId: String, - val issuer: String, - val signedHashes: Int, + val cardId: String, + val issuer: String, + val signedHashes: Int, ) enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode } data class SecurityScreenState( - val currentOption: SecurityOption = SecurityOption.LongTap, - val selectedOption: SecurityOption = currentOption, - val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), - val buttonProceed: Button = Button(true), + val currentOption: SecurityOption = SecurityOption.LongTap, + val selectedOption: SecurityOption = currentOption, + val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), + val buttonProceed: Button = Button(true), ) enum class SecurityOption { LongTap, PassCode, AccessCode } data class AppCurrencyState( - val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, - val showAppCurrencyDialog: Boolean = false, - val fiatCurrencies: List? = null, + val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, + val showAppCurrencyDialog: Boolean = false, + val fiatCurrencies: List? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt deleted file mode 100644 index 1a6c05c2b1..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.tangem.tap.features.details.redux.twins - -import com.tangem.blockchain.extensions.Result -import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.toSendableAmounts -import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.scope -import com.tangem.tap.store -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -class CreateTwinWalletMiddleware { - var twinsManager: TwinCardsManager? = null - fun handle(action: DetailsAction.CreateTwinWalletAction) { - when (action) { - is DetailsAction.CreateTwinWalletAction.ShowWarning -> { - val wallet = store.state.detailsState.wallets.firstOrNull() - if (wallet == null) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) - return - } - val notEmpty = wallet.recentTransactions.isNotEmpty() || wallet.amounts.toSendableAmounts().isNotEmpty() - if (notEmpty) { - store.dispatch(DetailsAction.CreateTwinWalletAction.NotEmpty) - } else { - store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) - } - } - is DetailsAction.CreateTwinWalletAction.Proceed -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWallet)) - is DetailsAction.CreateTwinWalletAction.Cancel -> { - - val step = store.state.detailsState.createTwinWalletState?.step - if (step != null && step != CreateTwinWalletStep.FirstStep - ) { - store.dispatch(DetailsAction.CreateTwinWalletAction.ShowAlert) - } else { - twinsManager = null - store.dispatch(NavigationAction.PopBackTo()) - } - } - is DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> { - twinsManager = null - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> { - store.state.globalState.scanResponse?.let { - twinsManager = TwinCardsManager(it, action.context) - } - scope.launch { - val result = twinsManager?.createFirstWallet(action.message) - withContext(Dispatchers.Main) { - when (result) { - SimpleResult.Success -> - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success) - is SimpleResult.Failure -> - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure) - } - } - } - } - DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> { - - } - DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> { - - } - is DetailsAction.CreateTwinWalletAction.LaunchSecondStep -> - scope.launch { - val result = twinsManager?.createSecondWallet(action.initialMessage, - action.preparingMessage, action.creatingWalletMessage) - withContext(Dispatchers.Main) { - when (result) { - SimpleResult.Success -> - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success) - is SimpleResult.Failure -> - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure) - } - } - } - DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success -> { - - } - DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> { - - } - is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> { - scope.launch { - val result = twinsManager?.complete(action.message) - withContext(Dispatchers.Main) { - when (result) { - is Result.Success -> - store.dispatch( - DetailsAction.CreateTwinWalletAction - .LaunchThirdStep.Success(result.data) - ) - is Result.Failure -> - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure) - } - } - } - } - is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> { - scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(action.scanResponse) - } - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> { - - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt deleted file mode 100644 index 7de266d076..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.tangem.tap.features.details.redux.twins - -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.DetailsState - -class CreateTwinWalletReducer { - companion object { - fun handle( - action: DetailsAction.CreateTwinWalletAction, state: DetailsState - ): DetailsState { - return when (action) { - is DetailsAction.CreateTwinWalletAction.ShowWarning -> { - state.copy(createTwinWalletState = CreateTwinWalletState( - scanResponse = null, - twinCardNumber = action.twinCardNumber - ?: state.createTwinWalletState?.twinCardNumber, - createTwinWallet = action.createTwinWallet, - showAlert = false, - allowRecreatingWallet = state.createTwinWalletState?.allowRecreatingWallet - )) - } - DetailsAction.CreateTwinWalletAction.NotEmpty -> state - DetailsAction.CreateTwinWalletAction.ShowAlert -> { - state.copy(createTwinWalletState = state.createTwinWalletState?.copy( - showAlert = true - )) - } - DetailsAction.CreateTwinWalletAction.HideAlert -> { - state.copy(createTwinWalletState = state.createTwinWalletState?.copy( - showAlert = false - )) - } - is DetailsAction.CreateTwinWalletAction.Proceed -> { - state - } - DetailsAction.CreateTwinWalletAction.Cancel -> state - DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> state - is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> state - - DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> { - state.copy(createTwinWalletState = state.createTwinWalletState?.copy( - step = CreateTwinWalletStep.SecondStep - )) - } - DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> state - - is DetailsAction.CreateTwinWalletAction.LaunchSecondStep -> state - DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success -> - state.copy(createTwinWalletState = state.createTwinWalletState?.copy( - step = CreateTwinWalletStep.ThirdStep - )) - DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> { - state - } - is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> { - state - } - is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> { - state - } - DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> { - state - } - } - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt deleted file mode 100644 index 22c96dd0bf..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tap.features.details.redux.twins - -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.domain.twins.TwinCardNumber - -data class CreateTwinWalletState( - val scanResponse: ScanResponse?, - val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep, - val twinCardNumber: TwinCardNumber?, - val createTwinWallet: CreateTwinWallet?, - val showAlert: Boolean, - val allowRecreatingWallet: Boolean? = null -) - -enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep } - -enum class CreateTwinWallet { CreateWallet, RecreateWallet } 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 640cc47cf0..cb51e326dd 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 @@ -6,17 +6,14 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show 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.twins.getTwinCardIdForUser -import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.SecurityOption -import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.features.feedback.FeedbackEmail import com.tangem.tap.store import com.tangem.wallet.R @@ -68,8 +65,8 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber { - - private var dialog: Dialog? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.detailsState == newState.detailsState - }.select { it.detailsState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - toolbar.setNavigationOnClickListener { - store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel) - } - - Picasso.get() - .load(Artwork.TWIN_CARD_1) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_card_1) - - Picasso.get() - .load(Artwork.TWIN_CARD_2) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_card_2) - } - - - override fun newState(state: DetailsState) { - if (activity == null) return - - toolbar.title = when (state.createTwinWalletState?.createTwinWallet) { - CreateTwinWallet.CreateWallet -> getText(R.string.wallet_button_create_wallet) - CreateTwinWallet.RecreateWallet, null -> getText(R.string.details_twins_recreate_toolbar) - - } - - val selectedColor = getColor(requireContext(), R.color.colorSecondary) - val defaultColor = getColor(requireContext(), R.color.blue_pale) - - val twinCardNumber = state.createTwinWalletState?.twinCardNumber ?: TwinCardNumber.First - - val cardNumber = when (state.createTwinWalletState?.step) { - CreateTwinWalletStep.FirstStep -> { - val twinCardNumberString = twinCardNumber.number.toString() - tv_step_number.text = - getString(R.string.details_twins_recreate_step_format, "1") - v_step_1.setBackgroundColor(selectedColor) - v_step_2.setBackgroundColor(defaultColor) - v_step_3.setBackgroundColor(defaultColor) - btn_tap.setOnClickListener { - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep( - Message(getString( - R.string.details_twins_recreate_title_format, - twinCardNumberString - )), - requireContext() - )) - } - btn_tap.text = getString(R.string.details_twins_recreate_button_format, - twinCardNumber.number.toString()) - twinCardNumberString - } - CreateTwinWalletStep.SecondStep -> { - val twinCardNumberString = twinCardNumber.pairNumber().number.toString() - - tv_step_number.text = - getString(R.string.details_twins_recreate_step_format, "2") - v_step_1.setBackgroundColor(selectedColor) - v_step_2.setBackgroundColor(selectedColor) - v_step_3.setBackgroundColor(defaultColor) - btn_tap.setOnClickListener { - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep( - Message(getString(R.string.details_twins_recreate_title_format, twinCardNumberString)), - Message(getString(R.string.details_twins_recreate_title_preparing)), - Message(getString(R.string.details_twins_recreate_title_creating_wallet)), - )) - } - btn_tap.text = getString(R.string.details_twins_recreate_button_format, "2") - twinCardNumberString - } - CreateTwinWalletStep.ThirdStep -> { - val twinCardNumberString = twinCardNumber.number.toString() - - tv_step_number.text = - getString(R.string.details_twins_recreate_step_format, "3") - v_step_1.setBackgroundColor(selectedColor) - v_step_2.setBackgroundColor(selectedColor) - v_step_3.setBackgroundColor(selectedColor) - btn_tap.setOnClickListener { - store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep( - Message(getString( - R.string.details_twins_recreate_title_format, twinCardNumberString) - ) - )) - } - btn_tap.text = getString(R.string.details_twins_recreate_button_format, twinCardNumberString) - twinCardNumberString - } - else -> null - } - btn_tap.text = getString(R.string.details_twins_recreate_button_format, cardNumber) - tv_twin_title.text = getString(R.string.details_twins_recreate_title_format, cardNumber) - - if (state.createTwinWalletState?.showAlert == true) { - if (dialog == null) { - dialog = MaterialAlertDialogBuilder(requireContext()) - .setMessage(R.string.details_twins_recreate_alert) - .setPositiveButton(R.string.common_ok) { _, _ -> - store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel.Confirm) - } - .setNegativeButton(R.string.common_cancel) { _, _ -> - dialog?.cancel() - } - .setOnCancelListener { - store.dispatch(DetailsAction.CreateTwinWalletAction.HideAlert) - } - .create() - dialog?.show() - } - } else { - dialog?.cancel() - dialog = null - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt deleted file mode 100644 index 678dddf2bb..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.tap.features.details.ui.twins - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.transition.TransitionInflater -import com.squareup.picasso.Picasso -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.twins.CreateTwinWallet -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.store -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_details_twin_cards_warning.* -import kotlinx.android.synthetic.main.layout_twin_cards_orange.* - -class TwinWalletWarningFragment : Fragment(R.layout.fragment_details_twin_cards_warning) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val createTwinWallet = store.state.detailsState.createTwinWalletState?.createTwinWallet - if (createTwinWallet == CreateTwinWallet.CreateWallet) { - tv_twin_cards_description.text = getText(R.string.details_twins_recreate_subtitle) - } else if (createTwinWallet == CreateTwinWallet.RecreateWallet) { - tv_twin_cards_description.text = getText(R.string.details_twins_recreate_warning) - } - - btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } - btn_start.setOnClickListener { - store.dispatch( - DetailsAction.CreateTwinWalletAction.Proceed) - } - Picasso.get() - .load(Artwork.TWIN_CARD_1) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_twin_card_1) - - Picasso.get() - .load(Artwork.TWIN_CARD_2) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_twin_card_2) - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index d7b0c6d941..0d337c6da9 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -16,15 +16,14 @@ import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* -import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* -import kotlinx.android.synthetic.main.layout_onboarding_home.* -import java.lang.ref.WeakReference +import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.* +import kotlinx.android.synthetic.main.layout_onboarding_home_top.* class HomeFragment : BaseOnboardingFragment() { private lateinit var btnScanCard: ViewStateWidget - override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home_top override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,7 +37,10 @@ class HomeFragment : BaseOnboardingFragment() { toolbar.hide() val shareTransition = FragmentShareTransition( - listOf(ShareElement(WeakReference(imv_front_card), imv_front_card.transitionName)), + listOf( + ShareElement(imv_front_card, ShareElement.imvFrontCard), + ShareElement(imv_back_card, ShareElement.imvBackCard) + ), FrontCardEnterTransition(), FrontCardExitTransition() ) @@ -62,6 +64,7 @@ class HomeFragment : BaseOnboardingFragment() { oldState.homeState == newState.homeState }.select { it.homeState } } + storeSubscribersList.add(this) } override fun newState(state: HomeState) { diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 46e097943a..bd5cdcffff 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -2,6 +2,8 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.post import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppState @@ -11,6 +13,8 @@ import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.features.onboarding.OnboardingHelper +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -56,13 +60,24 @@ private fun handleReadCard() { } else { changeButtonState(ButtonState.PROGRESS) store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + if (OnboardingHelper.isOnboardingCase(scanResponse)) { val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) store.dispatch(GlobalAction.Onboarding.Start(scanResponse)) navigateTo(navigateTo, store.state.homeState.shareTransition) } else { - scope.launch { store.state.globalState.tapWalletManager.onCardScanned(scanResponse) } - navigateTo(AppScreen.Wallet, null) + scope.launch { + store.onCardScanned(scanResponse) + withMainContext { + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) + navigateTo(AppScreen.OnboardingTwins, store.state.homeState.shareTransition) + } else { + navigateTo(AppScreen.Wallet, null) + } + } + } } }, { changeButtonState(ButtonState.ENABLED) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 38b2517ad1..6a097f6511 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -12,14 +12,28 @@ import com.tangem.tap.preferencesStorage class OnboardingHelper { companion object { - fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) - preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true + fun isOnboardingCase(response: ScanResponse): Boolean { + val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + return when { + response.productType == ProductType.Twins -> { + if (!response.twinsIsTwinned()) { + true + } else { + cardInfoStorage.activationIsStarted(response.card.cardId) + } + } + response.card.hasWallets() -> { + cardInfoStorage.activationIsStarted(response.card.cardId) + } + else -> true + } + } fun whereToNavigate(scanResponse: ScanResponse): AppScreen { return when (scanResponse.productType) { ProductType.Note -> AppScreen.OnboardingNote ProductType.Wallet -> AppScreen.OnboardingWallet - ProductType.Twin -> AppScreen.OnboardingTwins + ProductType.Twins -> AppScreen.OnboardingTwins ProductType.Other -> AppScreen.OnboardingOther } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index 2b4894d5b9..984f512d4f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -55,7 +55,7 @@ class OnboardingManager( } suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance { - return when (val result = walletManager.safeUpdate()) { + val balance = when (val result = walletManager.safeUpdate()) { is Result.Success -> { val wallet = walletManager.wallet val valueOfAmount = wallet.amounts[AmountType.Coin]?.value @@ -63,11 +63,10 @@ class OnboardingManager( val customError = TapError.CustomError("Amount is NULL") OnboardingWalletBalance.criticalError(customError) } else { - val currency = Currency.Blockchain(walletManager.wallet.blockchain) val balance = if (valueOfAmount.isZero()) { - OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions(), currency) + OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions()) } else { - OnboardingWalletBalance.done(valueOfAmount, false, currency) + OnboardingWalletBalance.done(valueOfAmount, false) } balance } @@ -81,6 +80,8 @@ class OnboardingManager( } } } + + return balance.copy(currency = Currency.Blockchain(walletManager.wallet.blockchain)) } fun activationStarted(cardId: String) { @@ -122,7 +123,10 @@ data class OnboardingWalletBalance( state = ProgressState.Loading ) - fun done(value: BigDecimal, hasTransactions: Boolean, currency: Currency.Blockchain): OnboardingWalletBalance = - OnboardingWalletBalance(value, currency, hasTransactions, ProgressState.Done) + fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance( + value, + hasIncomingTransaction = hasTransactions, + state = ProgressState.Done + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt index 7cdbff7733..b33b3118f5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt @@ -5,7 +5,7 @@ import android.view.View import com.tangem.tap.common.extensions.inflate import com.tangem.tap.common.extensions.show import com.tangem.tap.common.transitions.HomeToOnboardingTransition -import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.BaseStoreFragment import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* import kotlinx.android.synthetic.main.view_confetti.* @@ -16,11 +16,6 @@ import org.rekotlin.StoreSubscriber */ abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - configureTransitions() - } - override fun configureTransitions() { enterTransition = HomeToOnboardingTransition() exitTransition = HomeToOnboardingTransition() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 6bf7ca96bf..b59e7ea667 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -15,16 +15,19 @@ import com.tangem.tangem_sdk_new.extensions.fadeIn import com.tangem.tangem_sdk_new.extensions.fadeOut import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString -import com.tangem.tap.common.postUi +import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment -import com.tangem.tap.features.onboarding.products.note.redux.* +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* -import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* -import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.* +import kotlinx.android.synthetic.main.layout_onboarding_container_top.* import kotlinx.android.synthetic.main.view_onboarding_progress.* import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* @@ -33,24 +36,28 @@ import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* */ class OnboardingNoteFragment : BaseOnboardingFragment() { - private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget - private var currentStep: OnboardingNoteStep = OnboardingNoteStep.None + private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None - override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note + private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget + + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + postponeEnterTransition() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + addBackPressHandler(this) + + imv_front_card.transitionName = ShareElement.imvFrontCard + startPostponedEnterTransition() toolbar.setTitle(R.string.onboarding_title) btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) - val resources = AndroidResources( - OnboardingStringResources( - R.string.copy_toast_msg - ) - ) store.dispatch(OnboardingNoteAction.LoadCardArtwork) - store.dispatch(OnboardingNoteAction.SetResources(resources)) store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } @@ -78,7 +85,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } setBalance(state) showConfetti(state.showConfetti) - currentStep = state.currentStep + previousStep = state.currentStep } private fun setBalance(state: OnboardingNoteState) { @@ -129,14 +136,22 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } tv_header.setText(R.string.onboarding_top_up_header) - tv_body.setText(R.string.onboarding_top_up_body) + if (state.balanceNonCriticalError == null) { + tv_body.setText(R.string.onboarding_top_up_body) + } else { + state.walletBalance.amountToCreateAccount?.let { amount -> + val tvBodyMessage = getString( + R.string.onboarding_top_up_body_no_account_error, + amount, state.walletBalance.currency.currencySymbol + ) + tv_body.text = tvBodyMessage + } + } btnRefreshBalanceWidget.changeState(state.walletBalance.state) if (btnRefreshBalanceWidget.isShowing != true) { - postUi(300) { - btnRefreshBalanceWidget.mainView.setOnClickListener { - store.dispatch(OnboardingNoteAction.Balance.Update) - } + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(OnboardingNoteAction.Balance.Update) } } imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) @@ -163,7 +178,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) { - if (this.currentStep == currentStep) return + if (this.previousStep == currentStep) return val constraintSet = ConstraintSet() constraintSet.clone(requireContext(), layoutId) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt index 798f8f4136..3a370e6c72 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.domain.TapError import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.Action @@ -10,14 +11,13 @@ import org.rekotlin.Action */ sealed class OnboardingNoteAction : Action { // from user, ui - data class SetResources(val resources: AndroidResources) : OnboardingNoteAction() object LoadCardArtwork : OnboardingNoteAction() object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() object ShowAddressInfoDialog : OnboardingNoteAction() // from redux - class SetArtworkUrl(val artwork: Artwork) : OnboardingNoteAction() + class SetArtwork(val artwork: Artwork) : OnboardingNoteAction() data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() @@ -27,6 +27,7 @@ sealed class OnboardingNoteAction : Action { sealed class Balance { object Update : OnboardingNoteAction() data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction() + data class SetNonCriticalError(val error: TapError?) : OnboardingNoteAction() } sealed class Confetti { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 0a925dc7ee..125ba49fff 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope import com.tangem.tap.store @@ -50,7 +51,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { is OnboardingNoteAction.LoadCardArtwork -> { scope.launch { val artwork = onboardingManager.loadArtwork() - withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artwork)) } + withMainContext { store.dispatch(OnboardingNoteAction.SetArtwork(artwork)) } } } is OnboardingNoteAction.DetermineStepOfScreen -> { @@ -107,6 +108,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading val balanceIsLoading = noteState.walletBalance.copy( + currency = Currency.Blockchain(walletManager.wallet.blockchain), state = ProgressState.Loading, error = null, criticalError = null @@ -117,9 +119,11 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { val loadedBalance = onboardingManager.updateBalance(walletManager) loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } delay(if (isLoadedBefore) 0 else 300) - withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } + withMainContext { + store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) + store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error)) + } } - } is OnboardingNoteAction.Balance.Set -> { if (action.balance.balanceIsToppedUp()) { @@ -128,7 +132,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { } is OnboardingNoteAction.ShowAddressInfoDialog -> { val addressData = noteState.walletManager?.getAddressData() ?: return - val addressWasCopied = noteState.resources.strings.addressWasCopied + val addressWasCopied = globalState.resources.strings.addressWasCopied val appDialog = AppDialog.AddressInfoDialog( addressData, onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, @@ -143,7 +147,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { OnboardingNoteAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - globalState.tapWalletManager.onCardScanned(scanResponse) + store.onCardScanned(scanResponse) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt index 03415eec84..260f41fc20 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -17,18 +17,18 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt is GlobalAction.Onboarding.Start -> { state = OnboardingNoteState() } - is OnboardingNoteAction.SetArtworkUrl -> { + is OnboardingNoteAction.SetArtwork -> { state = state.copy(cardArtwork = action.artwork) } is OnboardingNoteAction.SetWalletManager -> { state = state.copy(walletManager = action.walletManager) } - is OnboardingNoteAction.SetResources -> { - state = state.copy(resources = action.resources) - } is OnboardingNoteAction.Balance.Set -> { state = state.copy(walletBalance = action.balance) } + is OnboardingNoteAction.Balance.SetNonCriticalError -> { + state = state.copy(balanceNonCriticalError = action.error) + } is OnboardingNoteAction.SetStepOfScreen -> { if (action.step != state.currentStep && state.steps.contains(action.step)) { state = state.copy(currentStep = action.step) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt index d4f1c2bcb7..3cf07bea6b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.domain.TapError import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType @@ -10,10 +11,10 @@ import org.rekotlin.StateType */ data class OnboardingNoteState( val walletManager: WalletManager? = null, - val resources: AndroidResources = AndroidResources(), // UI val cardArtwork: Artwork? = null, val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(), + val balanceNonCriticalError: TapError? = null, val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), val showConfetti: Boolean = false, @@ -25,12 +26,4 @@ data class OnboardingNoteState( enum class OnboardingNoteStep { None, CreateWallet, TopUpWallet, Done -} - -data class AndroidResources( - val strings: OnboardingStringResources = OnboardingStringResources() -) - -data class OnboardingStringResources( - val addressWasCopied: Int = -1 -) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index ebdbe7d4c1..cdd2871e00 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -8,7 +8,9 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import com.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment import com.tangem.tap.features.onboarding.products.note.swapToBitmapDrawable import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction @@ -17,8 +19,8 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* -import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* -import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.* +import kotlinx.android.synthetic.main.layout_onboarding_container_top.* import kotlinx.android.synthetic.main.view_onboarding_progress.* /** @@ -26,10 +28,19 @@ import kotlinx.android.synthetic.main.view_onboarding_progress.* */ class OnboardingOtherCardsFragment : BaseOnboardingFragment() { - override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + postponeEnterTransition() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + addBackPressHandler(this) + + imv_front_card.transitionName = ShareElement.imvFrontCard + startPostponedEnterTransition() toolbar.setTitle(R.string.onboarding_title) store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt index 44bdec53c8..b48570b6c9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt @@ -12,7 +12,7 @@ sealed class OnboardingOtherCardsAction : Action { object CreateWallet : OnboardingOtherCardsAction() // from redux - class SetArtworkUrl(val artwork: Artwork) : OnboardingOtherCardsAction() + class SetArtwork(val artwork: Artwork) : OnboardingOtherCardsAction() object DetermineStepOfScreen : OnboardingOtherCardsAction() object Done : OnboardingOtherCardsAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index e775176f10..fd2dbee87d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux import com.tangem.common.CompletionResult +import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppState @@ -44,7 +45,7 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { is OnboardingOtherCardsAction.LoadCardArtwork -> { scope.launch { val artwork = onboardingManager.loadArtwork() - withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artwork)) } + withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtwork(artwork)) } } } is OnboardingOtherCardsAction.DetermineStepOfScreen -> { @@ -83,7 +84,7 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { OnboardingOtherCardsAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - globalState.tapWalletManager.onCardScanned(onboardingManager.scanResponse) + store.onCardScanned(onboardingManager.scanResponse) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt index a6313b9199..129b7ea280 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt @@ -17,7 +17,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingOtherC is GlobalAction.Onboarding.Start -> { state = OnboardingOtherCardsState() } - is OnboardingOtherCardsAction.SetArtworkUrl -> { + is OnboardingOtherCardsAction.SetArtwork -> { state = state.copy(cardArtwork = action.artwork) } is OnboardingOtherCardsAction.SetStepOfScreen -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt new file mode 100644 index 0000000000..7d48937c17 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt @@ -0,0 +1,64 @@ +package com.tangem.tap.features.onboarding.products.twins.redux + +import com.tangem.Message +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.twins.AssetReader +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class TwinCardsAction : Action { + data class IfTwinsPrepareState(val scanResponse: ScanResponse) : TwinCardsAction() + + object Init : TwinCardsAction() + data class SetMode(val mode: CreateTwinWalletMode) : TwinCardsAction() + data class SetStepOfScreen(val step: TwinCardsStep) : TwinCardsAction() + data class SetUserUnderstand(val isUnderstand: Boolean) : TwinCardsAction() + + sealed class CardsManager { + data class Set(val manager: TwinCardsManager) : TwinCardsAction() + object Release : TwinCardsAction() + } + + sealed class Wallet : TwinCardsAction() { + data class HandleOnBackPressed( + // this is necessary for the correct animation of the return of cards to the Home screen + val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit + ) : TwinCardsAction() + + data class ShowInterruptDialog(val onOk: VoidCallback) : TwinCardsAction(), StateDialog + + data class LaunchFirstStep(val initialMessage: Message, val reader: AssetReader) : TwinCardsAction() + data class LaunchSecondStep( + val initialMessage: Message, + val preparingMessage: Message, + val creatingWalletMessage: Message, + ) : TwinCardsAction() + + data class LaunchThirdStep(val message: Message) : TwinCardsAction() + } + + + // for the onboarding + data class SetPairCardId(val cardId: String) : TwinCardsAction() + object TopUp : TwinCardsAction() + object ShowAddressInfoDialog : TwinCardsAction() + data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction() + object Done : TwinCardsAction() + + sealed class Balance { + object Update : TwinCardsAction() + data class Set(val balance: OnboardingWalletBalance) : TwinCardsAction() + } + + sealed class Confetti { + object Show : TwinCardsAction() + object Hide : TwinCardsAction() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt new file mode 100644 index 0000000000..26fbfb104b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -0,0 +1,259 @@ +package com.tangem.tap.features.onboarding.products.twins.redux + +import com.tangem.blockchain.extensions.Result +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.guard +import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.postUi +import com.tangem.tap.common.redux.AppDialog +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.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.rekotlin.Action +import org.rekotlin.DispatchFunction +import org.rekotlin.Middleware + +class TwinCardsMiddleware { + companion object { + val handler = twinsWalletMiddleware + } +} + +private val twinsWalletMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + handle(action, dispatch) + next(action) + } + } +} + +private fun handle(action: Action, dispatch: DispatchFunction) { + val action = action as? TwinCardsAction ?: return + + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager + val twinCardsState = store.state.twinCardsState + + fun getScanResponse(): ScanResponse { + return when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse + CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse + } ?: throw NullPointerException("ScanResponse can't be NULL") + } + + fun updateScanResponse(response: ScanResponse) { + when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response + CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response)) + } + } + + fun startCardActivation(cardId: String) { + if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) { + onboardingManager?.activationStarted(cardId) + } + } + + fun finishCardActivation() { + if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) { + onboardingManager?.activationFinished(getScanResponse().card.cardId) + twinCardsState.pairCardId?.let { onboardingManager?.activationFinished(it) } + } + } + + when (action) { + is TwinCardsAction.Init -> { + if (twinCardsState.currentStep == TwinCardsStep.WelcomeOnly) return + + when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> { + if (preferencesStorage.wasTwinsOnboardingShown()) { + val step = when { + !getScanResponse().twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet + twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done + else -> TwinCardsStep.TopUpWallet + } + store.dispatch((TwinCardsAction.SetStepOfScreen(step))) + } else { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome)) + } + } + CreateTwinWalletMode.RecreateWallet -> { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning)) + } + } + } + is TwinCardsAction.Wallet.HandleOnBackPressed -> { + val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet + && twinCardsState.currentStep != TwinCardsStep.TopUpWallet + && twinCardsState.currentStep != TwinCardsStep.Done + + if (twinCardsState.showAlert) { + val onInterruptPrompt = { + store.dispatch(TwinCardsAction.CardsManager.Release) + action.shouldResetTwinCardsWidget(shouldReturnCardBack) { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + val stateDialog = TwinCardsAction.Wallet.ShowInterruptDialog(onInterruptPrompt) + store.dispatchDialogShow(stateDialog) + } else { + store.dispatch(TwinCardsAction.CardsManager.Release) + action.shouldResetTwinCardsWidget(shouldReturnCardBack) { + store.dispatch(NavigationAction.PopBackTo()) + } + } + } + is TwinCardsAction.SetStepOfScreen -> { + when (action.step) { + TwinCardsStep.WelcomeOnly -> preferencesStorage.saveTwinsOnboardingShown() + TwinCardsStep.Welcome -> preferencesStorage.saveTwinsOnboardingShown() + TwinCardsStep.TopUpWallet -> store.dispatch(TwinCardsAction.Balance.Update) + TwinCardsStep.Done -> { + finishCardActivation() + postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) } + } + } + } + is TwinCardsAction.Wallet.LaunchFirstStep -> { + val manager = TwinCardsManager(getScanResponse().card, action.reader) + store.dispatch(TwinCardsAction.CardsManager.Set(manager)) + + scope.launch { + when (val result = manager.createFirstWallet(action.initialMessage)) { + is CompletionResult.Success -> { + startCardActivation(result.data.cardId) + delay(DELAY_SDK_DIALOG_CLOSE) + withMainContext { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet)) + } + } + is CompletionResult.Failure -> { + } + } + } + } + is TwinCardsAction.Wallet.LaunchSecondStep -> { + val manager = twinCardsState.twinCardsManager ?: return + + scope.launch { + val result = manager.createSecondWallet( + action.initialMessage, + action.preparingMessage, + action.creatingWalletMessage + ) + when (result) { + is CompletionResult.Success -> { + startCardActivation(result.data.cardId) + delay(DELAY_SDK_DIALOG_CLOSE) + withMainContext { + store.dispatch(TwinCardsAction.SetPairCardId(result.data.cardId)) + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet)) + } + } + is CompletionResult.Failure -> { + } + } + } + } + is TwinCardsAction.Wallet.LaunchThirdStep -> { + val manager = twinCardsState.twinCardsManager ?: return + + scope.launch { + when (val result = manager.complete(action.message)) { + is Result.Success -> { + updateScanResponse(result.data) + delay(DELAY_SDK_DIALOG_CLOSE) + withMainContext { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet)) } + } + is Result.Failure -> { + } + } + } + } + is TwinCardsAction.Balance.Update -> { + val walletManager = if (twinCardsState.walletManager != null) { + twinCardsState.walletManager + } else { + val wmFactory = globalState.tapWalletManager.walletManagerFactory + val walletManager = wmFactory.makePrimaryWalletManager(getScanResponse()).guard { + val message = "Loading cancelled. Cause: wallet manager didn't created" + val customError = TapError.CustomError(message) + store.dispatchErrorNotification(customError) + return + } + dispatch(TwinCardsAction.SetWalletManager(walletManager)) + walletManager + } + val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading + val balanceIsLoading = twinCardsState.walletBalance.copy( + currency = Currency.Blockchain(walletManager.wallet.blockchain), + state = ProgressState.Loading, + error = null, + criticalError = null + ) + store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading)) + + scope.launch { + val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch + loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } + delay(if (isLoadedBefore) 0 else 300) + withMainContext { store.dispatch(TwinCardsAction.Balance.Set(loadedBalance)) } + } + + } + is TwinCardsAction.Balance.Set -> { + if (action.balance.balanceIsToppedUp()) { + scope.launch { + withMainContext { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done)) } + } + } + } + is TwinCardsAction.ShowAddressInfoDialog -> { + val addressData = twinCardsState.walletManager?.getAddressData() ?: return + + val addressWasCopied = globalState.resources.strings.addressWasCopied + val appDialog = AppDialog.AddressInfoDialog( + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + ) + store.dispatchDialogShow(appDialog) + } + is TwinCardsAction.TopUp -> { + val topUpUrl = twinCardsState.walletManager?.getToUpUrl() ?: return + store.dispatchOpenUrl(topUpUrl) + } + TwinCardsAction.Done -> { + scope.launch { + val scanResponse = getScanResponse() + store.onCardScanned(scanResponse) + withMainContext { + when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> { + store.dispatch(GlobalAction.Onboarding.Stop) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + CreateTwinWalletMode.RecreateWallet -> { + store.dispatch(NavigationAction.PopBackTo()) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt new file mode 100644 index 0000000000..1d18eb4dec --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt @@ -0,0 +1,64 @@ +package com.tangem.tap.features.onboarding.products.twins.redux + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.twins.getTwinCardNumber +import org.rekotlin.Action + +class TwinCardsReducer { + companion object { + fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): TwinCardsState { + if (action !is TwinCardsAction) return state.twinCardsState + + var state = state.twinCardsState + + when (action) { + is TwinCardsAction.IfTwinsPrepareState -> { + state = if (action.scanResponse.isTangemTwins()) { + val cardNumber = action.scanResponse.card.getTwinCardNumber() ?: return state + + TwinCardsState( + cardNumber = cardNumber, + mode = CreateTwinWalletMode.CreateWallet, + ) + } else { + TwinCardsState() + } + } + is TwinCardsAction.SetMode -> { + state = state.copy(mode = action.mode) + } + is TwinCardsAction.SetStepOfScreen -> { + state = state.copy(currentStep = action.step) + } + is TwinCardsAction.SetUserUnderstand -> { + state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand) + } + is TwinCardsAction.CardsManager.Set -> { + state = state.copy(twinCardsManager = action.manager) + } + TwinCardsAction.CardsManager.Release -> { + state = state.copy(twinCardsManager = null) + } + is TwinCardsAction.SetPairCardId -> { + state = state.copy(pairCardId = action.cardId) + } + is TwinCardsAction.SetWalletManager -> { + state = state.copy(walletManager = action.walletManager) + } + is TwinCardsAction.Balance.Set -> { + state = state.copy(walletBalance = action.balance) + } + is TwinCardsAction.Confetti.Show -> { + state = state.copy(showConfetti = true) + } + is TwinCardsAction.Confetti.Hide -> { + state = state.copy(showConfetti = false) + } + } + + return state +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt new file mode 100644 index 0000000000..ac731df22a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -0,0 +1,61 @@ +package com.tangem.tap.features.onboarding.products.twins.redux + +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class TwinCardsState( + // if cardNumber = null -> the scanned card is not Twin + val cardNumber: TwinCardNumber? = null, + val mode: CreateTwinWalletMode = CreateTwinWalletMode.CreateWallet, + val currentStep: TwinCardsStep = TwinCardsStep.Welcome, + val userWasUnderstandIfWalletRecreate: Boolean = false, + // available only when creating or recreating wallets + val twinCardsManager: TwinCardsManager? = null, + + // for the onboarding + val pairCardId: String? = null, // available after create\recreate wallets + val walletManager: WalletManager? = null, + val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(), + val showConfetti: Boolean = false, +) : StateType { + + val steps: List + get() = when (mode) { + CreateTwinWalletMode.CreateWallet -> listOf( + TwinCardsStep.None, + TwinCardsStep.CreateFirstWallet, + TwinCardsStep.CreateSecondWallet, + TwinCardsStep.CreateThirdWallet, + TwinCardsStep.TopUpWallet, + TwinCardsStep.Done + ) + CreateTwinWalletMode.RecreateWallet -> listOf( + TwinCardsStep.None, + TwinCardsStep.CreateFirstWallet, + TwinCardsStep.CreateSecondWallet, + TwinCardsStep.CreateThirdWallet, + TwinCardsStep.Done + ) + } + + val progress: Int + get() = steps.indexOf(currentStep) + + val showAlert: Boolean + get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet +} + +enum class CreateTwinWalletMode { CreateWallet, RecreateWallet } + +enum class TwinCardsStep { + None, WelcomeOnly, Welcome, Warning, CreateFirstWallet, CreateSecondWallet, CreateThirdWallet, + + // for the onboarding + TopUpWallet, Done +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt new file mode 100644 index 0000000000..dee8230f9f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -0,0 +1,368 @@ +package com.tangem.tap.features.onboarding.products.twins.ui + +import android.os.Bundle +import android.view.View +import android.view.animation.OvershootInterpolator +import androidx.annotation.LayoutRes +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.view.isVisible +import androidx.transition.TransitionInflater +import androidx.transition.TransitionManager +import com.squareup.picasso.Picasso +import com.tangem.Message +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget +import com.tangem.tap.common.postUi +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.common.redux.navigation.ShareElement +import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget +import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.domain.twins.AssetReader +import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.domain.twins.TwinsCardWidget +import com.tangem.tap.features.addBackPressHandler +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_onboarding_main.* +import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.* +import kotlinx.android.synthetic.main.layout_onboarding_container_top.* +import kotlinx.android.synthetic.main.view_bg_twins_welcome.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* +import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* +import org.rekotlin.Action + +class TwinsCardsFragment : BaseOnboardingFragment() { + + private var previousStep = TwinCardsStep.None + + private lateinit var twinsWidget: TwinsCardWidget + private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget + + private val assetReader: AssetReader by lazy { + object : AssetReader { + override fun readAssetAsString(name: String): String = requireContext().readAssetAsString(name) + } + } + + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + postponeEnterTransition() + store.dispatch(TwinCardsAction.Init) + } + + override fun configureTransitions() { + when (store.state.twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> super.configureTransitions() + CreateTwinWalletMode.RecreateWallet -> { + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + addBackPressHandler(this) + + reconfigureLayoutForTwins() + twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container)) { 250f } + btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) + + toolbar.title = getText(R.string.twins_recreate_toolbar) + + Picasso.get() + .load(Artwork.TWIN_CARD_1) + .placeholder(R.drawable.card_placeholder_black) + ?.into(imv_twin_front_card) + + Picasso.get() + .load(Artwork.TWIN_CARD_2) + .placeholder(R.drawable.card_placeholder_white) + ?.into(imv_twin_back_card) + } + + private fun reconfigureLayoutForTwins() { + imv_front_card.hide() + imv_card_background.hide() + cards_container.show() + + imv_twin_front_card.transitionName = ShareElement.imvFrontCard + imv_twin_back_card.transitionName = ShareElement.imvBackCard + } + + override fun subscribeToStore() { + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.twinCardsState == newState.twinCardsState + }.select { it.twinCardsState } + } + storeSubscribersList.add(this) + } + + override fun newState(state: TwinCardsState) { + if (activity == null) return + +// toolbar.title = when (state.mode) { +// CreateTwinWalletMode.CreateWallet -> getText(R.string.wallet_button_create_wallet) +// CreateTwinWalletMode.RecreateWallet -> getText(R.string.twins_recreate_toolbar) +// } + + pb_state.max = state.steps.size - 1 + pb_state.progress = state.progress + + when (state.currentStep) { + TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state) + TwinCardsStep.Welcome -> setupWelcomeState(state) + TwinCardsStep.Warning -> setupWarningState(state) + TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) + TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state) + TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state) + TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state) + TwinCardsStep.Done -> setupDoneState(state) + } + + setBalance(state) + showConfetti(state.showConfetti) + previousStep = state.currentStep + } + + private fun setBalance(state: TwinCardsState) { + if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return + + val balanceValue = state.walletBalance.value.stripZeroPlainString() + val currency = state.walletBalance.currency.currencySymbol + tv_balance_value.text = "$balanceValue $currency" + } + + private fun setupWelcomeOnlyState(state: TwinCardsState) { + setupWelcomeState(state, NavigationAction.NavigateTo(AppScreen.Wallet)) + } + + private fun setupWelcomeState(state: TwinCardsState) { + setupWelcomeState(state, TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) + } + + private fun setupWelcomeState(state: TwinCardsState, mainAction: Action) { + twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + + pb_state.hide() + + tv_header.setText(R.string.twins_onboarding_subtitle) + tv_body.text = getString(R.string.twins_onboarding_description_format, state.cardNumber?.pairIndexNumber()) + + btn_main_action.setText(R.string.common_continue) + btn_main_action.setOnClickListener { store.dispatch(mainAction) } + } + + private fun setupWarningState(state: TwinCardsState) { + twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + pb_state.hide() + chb_understand.show() + + tv_header.setText(R.string.common_warning) + tv_body.setText(R.string.twins_recreate_warning) + + chb_understand.setOnCheckedChangeListener { buttonView, isChecked -> + store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked)) + } + btn_main_action.isEnabled = state.userWasUnderstandIfWalletRecreate + btn_main_action.setText(R.string.common_continue) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) + } + } + + private fun setupCreateFirstWalletState(state: TwinCardsState) { + bg_circle_large.hide() + bg_circle_medium.hide() + bg_circle_min.hide() + chb_understand.hide() + pb_state.show() + + onboarding_main_container.beginDelayedTransition() + + when (previousStep) { + TwinCardsStep.None -> { + twinsWidget.toLeapfrog(false) { + when (state.cardNumber) { + TwinCardNumber.First -> startPostponedEnterTransition() + TwinCardNumber.Second -> { + switchToCard(state.cardNumber, false) { startPostponedEnterTransition() } + } + } + } + } + TwinCardsStep.Welcome, TwinCardsStep.Warning -> { + twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) }) + } + } + + val twinIndexNumber = state.cardNumber?.indexNumber() + tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) + tv_body.setText(R.string.onboarding_twins_interrupt_warning) + + btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.Wallet.LaunchFirstStep( + Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), + assetReader + )) + } + + btn_alternative_action.isVisible = false + btn_alternative_action.isClickable = false + } + + private fun setupCreateSecondWalletState(state: TwinCardsState) { + switchToCard(state.cardNumber?.pairNumber()) + + val twinPairIndexNumber = state.cardNumber?.pairIndexNumber() + tv_header.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber) + tv_body.setText(R.string.onboarding_twins_interrupt_warning) + + btn_main_action.text = getString(R.string.twins_recreate_button_format, twinPairIndexNumber) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.Wallet.LaunchSecondStep( + Message(getString(R.string.twins_recreate_title_format, twinPairIndexNumber)), + Message(getString(R.string.twins_recreate_title_preparing)), + Message(getString(R.string.twins_recreate_title_creating_wallet)), + )) + } + } + + private fun setupCreateThirdWalletState(state: TwinCardsState) { + switchToCard(state.cardNumber) + + val twinIndexNumber = state.cardNumber?.indexNumber() + tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) + tv_body.setText(R.string.onboarding_twins_interrupt_warning) + + btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.Wallet.LaunchThirdStep( + Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)) + )) + } + } + + + private fun setupTopUpWalletState(state: TwinCardsState) { + when (previousStep) { + TwinCardsStep.None -> { + when (state.cardNumber) { + TwinCardNumber.First -> { + twinsWidget.leapfrogWidget.unfold(false) { + twinsWidget.toActivate(false) { + startPostponedEnterTransition() + } + } + } + TwinCardNumber.Second -> { + switchToCard(state.cardNumber, false) { + twinsWidget.toActivate(false) + startPostponedEnterTransition() + } + } + } + } + TwinCardsStep.CreateThirdWallet -> { + twinsWidget.toActivate() + } + } + + btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.TopUp) + } + + btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) + btn_alternative_action.setOnClickListener { + store.dispatch(TwinCardsAction.ShowAddressInfoDialog) + } + + tv_header.setText(R.string.onboarding_top_up_header) + tv_body.setText(R.string.onboarding_top_up_body) + + btnRefreshBalanceWidget.changeState(state.walletBalance.state) + if (btnRefreshBalanceWidget.isShowing != true) { + postUi(300) { + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(TwinCardsAction.Balance.Update) + } + } + } + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins) + } + + private fun setupDoneState(state: TwinCardsState) { + btn_main_action.setText(R.string.onboarding_done_button_continue) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.Confetti.Hide) + store.dispatch(TwinCardsAction.Done) + } + + btn_alternative_action.isVisible = false + btn_alternative_action.isClickable = false + + tv_header.setText(R.string.onboarding_done_header) + tv_body.setText(R.string.onboarding_done_body) + + val layout = when (state.mode) { + CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins + CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done + } + updateConstraints(state.currentStep, layout) + } + + private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) { + if (this.previousStep == currentStep) return + + val constraintSet = ConstraintSet() + constraintSet.clone(requireContext(), layoutId) + constraintSet.applyTo(onboarding_main_container) + val transition = InternalNoteLayoutTransition() + transition.interpolator = OvershootInterpolator() + TransitionManager.beginDelayedTransition(onboarding_main_container, transition) + } + + private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) { + val position = cardNumber?.number ?: return + + if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) { + twinsWidget.leapfrogWidget.leap(animate, onEnd) + } else { + onEnd() + } + } + + override fun handleOnBackPressed() { + store.dispatch(TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> + store.dispatch(TwinCardsAction.Confetti.Hide) + showConfetti(false) + if (should) switchToCard(TwinCardNumber.First, true, popAction) + else popAction() + }) + } +} + +private fun TwinCardNumber?.indexNumber(): String { + return this?.number?.toString() ?: "" +} + +private fun TwinCardNumber?.pairIndexNumber(): String { + return this?.pairNumber()?.number?.toString() ?: "" +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt new file mode 100644 index 0000000000..0020137a82 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.onboarding.products.twins.ui.dialog + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class CreateWalletInterruptDialog { + companion object { + fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog { + return MaterialAlertDialogBuilder(context) + .setMessage(R.string.twins_recreate_alert) + .setPositiveButton(R.string.common_ok) { _, _ -> state.onOk() } + .setNegativeButton(R.string.common_cancel) { _, _ -> } + .setOnDismissListener { store.dispatchDialogHide() } + .create() + } + } +} \ No newline at end of file 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 index d0463040bd..69c66581a6 100644 --- 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 @@ -3,8 +3,8 @@ 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.features.send.BaseStoreFragment import com.tangem.tap.store import com.tangem.wallet.R import org.rekotlin.StoreSubscriber diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index bb675cf3fe..4d9528605a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -7,7 +7,6 @@ import android.text.method.DigitsKeyListener import android.view.View import android.view.inputmethod.EditorInfo import android.widget.EditText -import androidx.activity.OnBackPressedCallback import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.recyclerview.widget.LinearLayoutManager @@ -25,7 +24,8 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.snackBar.MaxAmountSnackbar import com.tangem.tap.common.text.truncateMiddleWith import com.tangem.tap.common.toggleWidget.* -import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.BaseStoreFragment +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* import com.tangem.tap.features.send.redux.AmountActionUi.* @@ -63,25 +63,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private val sendSubscriber = SendStateSubscriber(this) private lateinit var keyboardObserver: KeyboardObserver - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val externalTransactionData = store.state.sendState.externalTransactionData - if (externalTransactionData == null) { - store.dispatch(NavigationAction.PopBackTo()) - } else { - store.dispatch( - WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId) - ) - } - } - }) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + addBackPressHandler(this) etAmountToSend = view.findViewById(R.id.etAmountToSend) @@ -97,7 +81,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private fun initSendButtonStates() { btnSend.setOnClickListener { store.dispatch(SendActionUi.SendAmountToRecipient( - Message(getString(R.string.initial_message_sign_header)) + Message(getString(R.string.initial_message_sign_header)) )) } sendBtn = IndeterminateProgressButtonWidget(btnSend, progress) @@ -124,8 +108,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } imvQrCode.setOnClickListener { startActivityForResult( - Intent(requireContext(), ScanQrCodeActivity::class.java), - ScanQrCodeActivity.SCAN_QR_REQUEST_CODE + Intent(requireContext(), ScanQrCodeActivity::class.java), + ScanQrCodeActivity.SCAN_QR_REQUEST_CODE ) } } @@ -281,6 +265,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { sp.edit().putString("mainCurrency", type.name).apply() } + override fun handleOnBackPressed() { + val externalTransactionData = store.state.sendState.externalTransactionData + if (externalTransactionData == null) { + store.dispatch(NavigationAction.PopBackTo()) + } else { + store.dispatch( + WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId) + ) + } + } + override fun onDestroy() { keyboardObserver.unregisterListener() store.dispatch(ReleaseSendState) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt index aa129ebbb5..17dfb7a5fc 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.send.ui.stateSubscribers -import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.BaseStoreFragment import org.rekotlin.StateType import org.rekotlin.StoreSubscriber import java.lang.ref.WeakReference diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 2471b257de..a8987dcd0e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -12,7 +12,7 @@ import com.tangem.tap.common.redux.getMessageString import com.tangem.tap.common.text.DecimalDigitsInputFilter import com.tangem.tap.domain.MultiMessageError import com.tangem.tap.domain.assembleErrors -import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.SendAction @@ -188,15 +188,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber (fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type) val balanceText = fg.getString(R.string.send_balance_subtitle_format, - state.mainCurrency.currencySymbol, - state.viewBalanceValue) + state.mainCurrency.currencySymbol, + state.viewBalanceValue) fg.tvBalance.update(balanceText) fg.tilAmountToSend.isEnabled = state.inputIsEnabled val imageRes = if (state.inputIsEnabled) R.drawable.ic_arrows_up_down else 0 fg.tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0) - val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray + val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray fg.tvAmountCurrency.setTextColor(fg.getColor(textColor)) } @@ -245,7 +245,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}") val willSent = getString(R.string.send_total_subtitle_format, - receipt.willSentCrypto, receipt.symbols.crypto) + receipt.willSentCrypto, receipt.symbols.crypto) totalLayout.tvWillBeSentValue.update(willSent) } @@ -277,9 +277,9 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}") val willSent = getString( - R.string.send_total_subtitle_asset_format, - receipt.symbols.token ?: "", receipt.willSentToken, - receipt.symbols.crypto, receipt.willSentFeeCoin + R.string.send_total_subtitle_asset_format, + receipt.symbols.token ?: "", receipt.willSentToken, + receipt.symbols.crypto, receipt.willSentFeeCoin ) totalLayout.tvWillBeSentValue.update(willSent) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index e3d8eb80fb..4b88b06ca5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -18,7 +18,6 @@ import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_add_tokens.* -import kotlinx.android.synthetic.main.fragment_details_twin_cards.toolbar import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.* import org.rekotlin.StoreSubscriber diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt index 492513e02c..a449bb17ea 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt @@ -5,12 +5,13 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionStatus import com.tangem.blockchain.common.Wallet import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.domain.extensions.toSendableAmounts data class PendingTransaction( - val address: String?, - val amount: String?, - val currency: String, - val type: PendingTransactionType + val address: String?, + val amount: String?, + val currency: String, + val type: PendingTransactionType ) enum class PendingTransactionType { Incoming, Outgoing, Unknown } @@ -36,10 +37,10 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact } return PendingTransaction( - if (address == "unknown") null else address, - this.amount.value?.toFormattedString(amount.decimals), - this.amount.currencySymbol, - type + if (address == "unknown") null else address, + this.amount.value?.toFormattedString(amount.decimals), + this.amount.currencySymbol, + type ) } @@ -65,5 +66,9 @@ fun Wallet.getPendingTransactions(): List { } fun Wallet.hasPendingTransactions(): Boolean { - return recentTransactions.toPendingTransactions(address).isNotEmpty() + return getPendingTransactions().isNotEmpty() +} + +fun Wallet.hasSendableAmountsOrPendingTransactions(): Boolean { + return hasPendingTransactions() || amounts.toSendableAmounts().isNotEmpty() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 6fd84ec7cd..ae0a273c14 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -9,7 +9,6 @@ import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -147,12 +146,4 @@ sealed class WalletAction : Action { data class ChangeSelectedAddress(val type: AddressType) : WalletAction() - sealed class TwinsAction : WalletAction() { - object ShowOnboarding : TwinsAction() - object SetOnboardingShown : TwinsAction() - data class SetTwinCard( - val number: TwinCardNumber, - val isCreatingTwinCardsAllowed: Boolean, - ) : TwinsAction() - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index d078cd0526..54e7f12586 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -12,39 +12,49 @@ import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.domain.topup.TradeCryptoHelper -import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken +import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.store import org.rekotlin.StateType import java.math.BigDecimal +import kotlin.properties.ReadOnlyProperty data class WalletState( - val state: ProgressState = ProgressState.Done, - val error: ErrorType? = null, - val cardImage: Artwork? = null, - val hashesCountVerified: Boolean? = null, - val walletDialog: StateDialog? = null, - val twinCardsState: TwinCardsState? = null, - val mainWarningsList: List = mutableListOf(), - val wallets: List = emptyList(), - val walletManagers: List = emptyList(), - val isMultiwalletAllowed: Boolean = false, - val cardCurrency: CryptoCurrencyName? = null, - val selectedWallet: Currency? = null, - val primaryBlockchain: Blockchain? = null, - val primaryToken: Token? = null, - val isTestnet: Boolean = false, - val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability() + val state: ProgressState = ProgressState.Done, + val error: ErrorType? = null, + val cardImage: Artwork? = null, + val hashesCountVerified: Boolean? = null, + val walletDialog: StateDialog? = null, + val mainWarningsList: List = mutableListOf(), + val wallets: List = emptyList(), + val walletManagers: List = emptyList(), + val isMultiwalletAllowed: Boolean = false, + val cardCurrency: CryptoCurrencyName? = null, + val selectedWallet: Currency? = null, + val primaryBlockchain: Blockchain? = null, + val primaryToken: Token? = null, + val isTestnet: Boolean = false, + val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability() ) : StateType { + // if you do not delegate - the application crashes on startup, + // because twinCardsState has not been created yet + val twinCardsState: TwinCardsState by ReadOnlyProperty { thisRef, property -> + store.state.twinCardsState + } + + val isTangemTwins: Boolean + get() = store.state.globalState.scanResponse?.isTangemTwins() == true + val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null val shouldShowDetails: Boolean = - primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && - primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain + primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && + primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain val blockchains: List get() = walletManagers.map { it.wallet.blockchain } @@ -57,16 +67,16 @@ data class WalletState( return walletManagers.find { it.wallet.blockchain == token.blockchain } } - fun getWalletManager(currency: Currency?) : WalletManager? { + fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null return walletManagers.find { it.wallet.blockchain == currency.blockchain } } - fun getWalletManager(blockchain: Blockchain) : WalletManager? { + fun getWalletManager(blockchain: Blockchain): WalletManager? { return walletManagers.find { it.wallet.blockchain == blockchain } } - fun getWalletData(currency: Currency?) : WalletData? { + fun getWalletData(currency: Currency?): WalletData? { if (currency == null) return null return wallets.find { it.currency == currency } } @@ -93,7 +103,7 @@ data class WalletState( ?: return true if (walletData.currency is Currency.Blockchain && - walletManager.cardTokens.isNotEmpty() + walletManager.cardTokens.isNotEmpty() ) { return false } @@ -105,9 +115,9 @@ data class WalletState( wallet.amounts.toSendableAmounts().isEmpty() } else if (walletData.currency is Currency.Token) ( return wallet.recentTransactions.toPendingTransactionsForToken( - walletData.currency.token, wallet.address).isEmpty() + walletData.currency.token, wallet.address).isEmpty() && wallet.amounts[AmountType.Token(token = walletData.currency.token)] - ?.isAboveZero() != true + ?.isAboveZero() != true ) } return false @@ -137,9 +147,9 @@ data class WalletState( } } -sealed class WalletDialog: StateDialog { +sealed class WalletDialog : StateDialog { data class QrDialog( - val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? + val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? ) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() @@ -147,7 +157,7 @@ sealed class WalletDialog: StateDialog { object ChooseTradeActionDialog : WalletDialog() } -enum class ProgressState: WidgetState { Loading, Done, Error } +enum class ProgressState : WidgetState { Loading, Done, Error } enum class ErrorType { NoInternetConnection } @@ -157,22 +167,22 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { } data class WalletAddresses( - val selectedAddress: AddressData, - val list: List + val selectedAddress: AddressData, + val list: List ) data class AddressData( - val address: String, - val type: AddressType, - val shareUrl: String, - val exploreUrl: String, + val address: String, + val type: AddressType, + val shareUrl: String, + val exploreUrl: String, ) { val qrCode: Bitmap by lazy { shareUrl.toQrCode() } } data class Artwork( - val artworkId: String, - val artwork: Bitmap? = null + val artworkId: String, + val artwork: Bitmap? = null ) { companion object { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" @@ -198,12 +208,6 @@ data class TradeCryptoAvailability( val availableToSell: Set = TradeCryptoHelper.AVAILABLE_TO_SELL, ) -data class TwinCardsState( - val cardNumber: TwinCardNumber?, - val showTwinOnboarding: Boolean, - val isCreatingTwinCardsAllowed: Boolean -) - data class WalletData( val pendingTransactions: List = emptyList(), val hashesCountVerified: Boolean? = null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt deleted file mode 100644 index 88ba60d1d1..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.tap.features.wallet.redux.middlewares - -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.store - -class TwinsMiddleware { - fun handle(action: WalletAction.TwinsAction) { - when (action) { - is WalletAction.TwinsAction.SetTwinCard -> { - val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() - if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding) - } - WalletAction.TwinsAction.SetOnboardingShown -> { - preferencesStorage.saveTwinsOnboardingShown() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 3b3a4f31c1..513627f250 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -11,209 +11,185 @@ import com.tangem.common.services.Result import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.* import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler -import com.tangem.tap.common.extensions.copyToClipboard -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.shareText +import com.tangem.tap.common.extensions.* 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.toSendableAmounts -import com.tangem.tap.domain.twins.TwinsHelper -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.network.NetworkStateChanged import kotlinx.coroutines.launch import org.rekotlin.Action +import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class WalletMiddleware { private val tradeCryptoMiddleware = TradeCryptoMiddleware() - private val twinsMiddleware = TwinsMiddleware() private val warningsMiddleware = WarningsMiddleware() private val multiWalletMiddleware = MultiWalletMiddleware() val walletMiddleware: Middleware = { dispatch, state -> { next -> { action -> - val globalState = state()?.globalState - val walletState = state()?.walletState - when (action) { - is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action) - is WalletAction.TwinsAction -> twinsMiddleware.handle(action) - is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> - multiWalletMiddleware.handle(action, walletState, globalState) - is WalletAction.LoadWallet -> { - scope.launch { - if (action.blockchain == null) { - walletState?.walletManagers?.map { walletManager -> - globalState?.tapWalletManager?.loadWalletData(walletManager) - } - } else { - val walletManager = walletState?.getWalletManager(action.blockchain) - walletManager?.let { globalState?.tapWalletManager?.loadWalletData(it) } - } - } - } - is WalletAction.LoadWallet.Success -> { - val coinAmount = action.wallet.amounts[AmountType.Coin]?.value - if (coinAmount != null && !coinAmount.isZero()) { - if (walletState?.getWalletData(action.wallet.blockchain) == null) { - store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain)) - store.dispatch(WalletAction.LoadWallet.Success(action.wallet)) - } - } - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - warningsMiddleware.tryToShowAppRatingWarning(action.wallet) - } - is WalletAction.LoadFiatRate -> { - scope.launch { - when { - action.wallet != null -> { - globalState?.tapWalletManager?.loadFiatRate( - globalState.appCurrency, action.wallet - ) - } - action.currency != null -> { - globalState?.tapWalletManager?.loadFiatRate( - globalState.appCurrency, action.currency - ) - } - else -> { - globalState?.tapWalletManager?.loadFiatRate( - fiatCurrency = globalState.appCurrency, - currencies = walletState?.wallets?.mapNotNull { it.currency } - ?: emptyList() - ) - } - } - } - } - is WalletAction.CreateWallet -> { - if (walletState?.twinCardsState != null) { - store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning( - globalState?.scanResponse?.card?.cardId?.let { - TwinsHelper.getTwinCardNumber(it) - }, - CreateTwinWallet.CreateWallet - )) - } else { - scope.launch { - val result = tangemSdkManager.createWallet( - globalState?.scanResponse?.card?.cardId - ) - when (result) { - is CompletionResult.Success -> { - val scanNoteResponse = - globalState?.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { - globalState.tapWalletManager.onCardScanned(scanNoteResponse) - } - } - is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( - error, - FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = store.state.detailsState.card - ) - } - } - } - } - } - } - is WalletAction.UpdateWallet -> { - if (action.blockchain != null) { - scope.launch { - val walletManager = walletState?.getWalletManager(action.blockchain) - walletManager?.let { globalState?.tapWalletManager?.updateWallet(it) } - } - } else { - scope.launch { - if (walletState?.state == ProgressState.Done) { - walletState.walletManagers.map { walletManager -> - globalState?.tapWalletManager?.updateWallet(walletManager) - } - } - } - } + handleAction(action, dispatch) + next(action) + } + } + } + private fun handleAction(action: Action, dispatch: DispatchFunction) { + val globalState = store.state.globalState + val walletState = store.state.walletState + + when (action) { + is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action) + is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) + is WalletAction.LoadWallet -> { + scope.launch { + if (action.blockchain == null) { + walletState.walletManagers.map { walletManager -> + globalState.tapWalletManager.loadWalletData(walletManager) + } + } else { + val walletManager = walletState.getWalletManager(action.blockchain) + walletManager?.let { globalState.tapWalletManager.loadWalletData(it) } } - is WalletAction.Scan -> { - store.dispatch(HomeAction.ShouldScanCardOnResume(true)) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + is WalletAction.LoadWallet.Success -> { + val coinAmount = action.wallet.amounts[AmountType.Coin]?.value + if (coinAmount != null && !coinAmount.isZero()) { + if (walletState.getWalletData(action.wallet.blockchain) == null) { + store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain)) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet)) } - is WalletAction.LoadCardInfo -> { - scope.launch { - val response = OnlineCardVerifier().getCardInfo( - action.card.cardId, action.card.cardPublicKey + } + store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + warningsMiddleware.tryToShowAppRatingWarning(action.wallet) + } + is WalletAction.LoadFiatRate -> { + scope.launch { + when { + action.wallet != null -> { + globalState.tapWalletManager.loadFiatRate( + globalState.appCurrency, action.wallet ) - when (response) { - is Result.Success -> { - store.dispatchOnMain( - WalletAction.SetArtworkId(response.data.artwork?.id) - ) - store.dispatchOnMain( - WalletAction.LoadArtwork(action.card, response.data.artwork?.id) - ) - store.dispatchOnMain( - GlobalAction.SetIfCardVerifiedOnline(response.data.passed) - ) - } - is Result.Failure -> { - store.dispatchOnMain( - GlobalAction.SetIfCardVerifiedOnline(false) - ) - } - } - store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded) } - } - is WalletAction.LoadData -> { - scope.launch { - val scanNoteResponse = globalState?.scanResponse - ?: return@launch - if (!walletState?.wallets.isNullOrEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) - } else { - globalState.tapWalletManager.loadData(scanNoteResponse) - } + action.currency != null -> { + globalState.tapWalletManager.loadFiatRate( + globalState.appCurrency, action.currency + ) } - } - is NetworkStateChanged -> { - globalState?.scanResponse?.let { scanNoteResponse -> - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - scope.launch { - globalState.tapWalletManager.loadData(scanNoteResponse) - } - } - } - is WalletAction.CopyAddress -> { - action.context.copyToClipboard(action.address) - store.dispatch(WalletAction.CopyAddress.Success) - } - is WalletAction.ShareAddress -> { - action.context.shareText(action.address) - } - is WalletAction.ExploreAddress -> { - val uri = Uri.parse(action.exploreUrl) - val intent = Intent(Intent.ACTION_VIEW, uri) - ContextCompat.startActivity(action.context, intent, null) - } - is WalletAction.Send -> { - val newAction = prepareSendAction(action.amount, state()?.walletState) - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + else -> { + globalState.tapWalletManager.loadFiatRate( + fiatCurrency = globalState.appCurrency, + currencies = walletState.wallets.mapNotNull { it.currency } + ) } } } - next(action) + } + is WalletAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createWallet( + globalState.scanResponse?.card?.cardId + ) + when (result) { + is CompletionResult.Success -> { + val scanNoteResponse = globalState.scanResponse?.copy(card = result.data) + scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } + } + is CompletionResult.Failure -> { + (result.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + card = store.state.detailsState.card + ) + } + } + } + } + } + is WalletAction.UpdateWallet -> { + if (action.blockchain != null) { + scope.launch { + val walletManager = walletState.getWalletManager(action.blockchain) + walletManager?.let { globalState.tapWalletManager.updateWallet(it) } + } + } else { + scope.launch { + if (walletState.state == ProgressState.Done) { + walletState.walletManagers.map { walletManager -> + globalState.tapWalletManager.updateWallet(walletManager) + } + } + } + } + } + is WalletAction.Scan -> { + store.dispatch(HomeAction.ShouldScanCardOnResume(true)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + is WalletAction.LoadCardInfo -> { + scope.launch { + val response = OnlineCardVerifier().getCardInfo( + action.card.cardId, action.card.cardPublicKey + ) + when (response) { + is Result.Success -> { + val actionList = listOf( + WalletAction.SetArtworkId(response.data.artwork?.id), + WalletAction.LoadArtwork(action.card, response.data.artwork?.id), + GlobalAction.SetIfCardVerifiedOnline(response.data.passed) + ) + actionList.forEach { store.dispatch(it) } + } + is Result.Failure -> { + store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(false)) + } + } + store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded) + } + } + is WalletAction.LoadData -> { + scope.launch { + val scanNoteResponse = globalState.scanResponse ?: return@launch + if (!walletState.wallets.isEmpty()) { + globalState.tapWalletManager.reloadData(scanNoteResponse) + } else { + globalState.tapWalletManager.loadData(scanNoteResponse) + } + } + } + is NetworkStateChanged -> { + globalState.scanResponse?.let { scanNoteResponse -> + store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } + } + } + is WalletAction.CopyAddress -> { + action.context.copyToClipboard(action.address) + store.dispatch(WalletAction.CopyAddress.Success) + } + is WalletAction.ShareAddress -> { + action.context.shareText(action.address) + } + is WalletAction.ExploreAddress -> { + val uri = Uri.parse(action.exploreUrl) + val intent = Intent(Intent.ACTION_VIEW, uri) + ContextCompat.startActivity(action.context, intent, null) + } + is WalletAction.Send -> { + val newAction = prepareSendAction(action.amount, store.state.walletState) + store.dispatch(newAction) + if (newAction is PrepareSendScreen) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + } } } } @@ -236,12 +212,12 @@ class WalletMiddleware { when (currency) { is Currency.Blockchain -> { val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.Send.ChooseCurrency(amounts) + ?: return WalletAction.Send.ChooseCurrency(amounts) PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager) } is Currency.Token -> { val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.Send.ChooseCurrency(amounts) + ?: return WalletAction.Send.ChooseCurrency(amounts) prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager) } } @@ -257,8 +233,8 @@ class WalletMiddleware { } private fun prepareSendActionForToken( - amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?, - walletManager: WalletManager? + amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?, + walletManager: WalletManager? ): PrepareSendScreen { val coinRate = state?.getWalletData(wallet?.blockchain)?.fiatRate val tokenRate = if (state?.isMultiwalletAllowed == true) { @@ -267,7 +243,7 @@ class WalletMiddleware { selectedWalletData?.currencyData?.token?.fiatRate } return PrepareSendScreen( - wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager, - amount, tokenRate) + wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager, + amount, tokenRate) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 39f84c1b09..7dacae94b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -17,7 +17,7 @@ import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.extensions.hasSignedHashes import com.tangem.tap.domain.extensions.remainingSignatures import com.tangem.tap.domain.isMultiwalletAllowed -import com.tangem.tap.domain.twins.isTangemTwin +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.preferencesStorage @@ -51,12 +51,12 @@ class WarningsMiddleware { } is WalletAction.Warnings.CheckRemainingSignatures -> { if (action.remainingSignatures != null && - action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { store.state.globalState.warningManager - ?.removeWarnings( - messageRes = R.string.warning_low_signatures_format - ) + ?.removeWarnings( + messageRes = R.string.warning_low_signatures_format + ) addWarningMessage( warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), @@ -81,7 +81,8 @@ class WarningsMiddleware { } private fun showCardWarningsIfNeeded(globalState: GlobalState?) { - globalState?.scanResponse?.card?.let { card -> + globalState?.scanResponse?.let { scanResponse -> + val card = scanResponse.card globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) if (card.isTestCard) { addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true) @@ -92,7 +93,7 @@ class WarningsMiddleware { if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) { addWarningMessage(WarningMessagesManager.devCardWarning()) } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { - checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) } + checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) } } if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { if (!globalState.cardVerifiedOnline) { @@ -106,7 +107,7 @@ class WarningsMiddleware { private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { val remainingSignatures = card.remainingSignatures if (remainingSignatures != null && - remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { addWarningMessage( WarningMessagesManager.remainingSignaturesNotEnough( @@ -117,12 +118,12 @@ class WarningsMiddleware { } private fun checkIfWarningNeeded( - card: Card, + scanResponse: ScanResponse, ): WarningMessage? { - if (card.isTangemTwin()) return null + if (scanResponse.isTangemTwins()) return null - if (card.isMultiwalletAllowed) { - return if (card.hasSignedHashes()) { + if (scanResponse.card.isMultiwalletAllowed) { + return if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.signedHashesMultiWalletWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -133,7 +134,7 @@ class WarningsMiddleware { val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator return if (validator == null) { - if (card.hasSignedHashes()) { + if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -149,10 +150,11 @@ class WarningsMiddleware { if (store.state.walletState.hashesCountVerified != false) return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return - val card = store.state.globalState.scanResponse?.card + val scanResponse = store.state.globalState.scanResponse + val card = scanResponse?.card if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return - if (card.isTangemTwin() || card.isMultiwalletAllowed) return + if (scanResponse.isTangemTwins() || card.isMultiwalletAllowed) return val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt deleted file mode 100644 index ad83caca20..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.tap.features.wallet.redux.reducers - -import com.tangem.tap.features.wallet.redux.TwinCardsState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletState - -class TwinsReducer { - fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState { - return when (action) { - is WalletAction.TwinsAction.SetTwinCard -> { - state.copy( - twinCardsState = TwinCardsState( - cardNumber = action.number, - showTwinOnboarding = state.twinCardsState?.showTwinOnboarding - ?: false, - isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed - ) - ) - } - is WalletAction.TwinsAction.ShowOnboarding -> { - state.copy( - twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true) - ?: TwinCardsState(cardNumber = null, - showTwinOnboarding = true, - isCreatingTwinCardsAllowed = false) - ) - } - is WalletAction.TwinsAction.SetOnboardingShown -> { - state.copy( - twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index d999216bba..ba5d6c7d56 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -25,7 +25,6 @@ class WalletReducer { private fun internalReduce(action: Action, state: AppState): WalletState { val multiWalletReducer = MultiWalletReducer() - val twinsReducer = TwinsReducer() val onWalletLoadedReducer = OnWalletLoadedReducer() if (action !is WalletAction) return state.walletState @@ -34,21 +33,17 @@ private fun internalReduce(action: Action, state: AppState): WalletState { when (action) { is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState) - is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState) is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState) is WalletAction.ResetState -> newState = WalletState() is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet) is WalletAction.EmptyWallet -> { - val creatingWalletAllowed = !(newState.twinCardsState != null && - newState.twinCardsState?.isCreatingTwinCardsAllowed != true) - newState = newState.copy( state = ProgressState.Done, wallets = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), - mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed), + mainButton = WalletMainButton.CreateWalletButton(true), ) ) ) @@ -215,7 +210,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState) is WalletAction.LoadArtwork -> { val artworkUrl = action.card.getArtworkUrl(action.artworkId) - ?: when (newState.twinCardsState?.cardNumber) { + ?: when (state.twinCardsState.cardNumber) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 else -> Artwork.DEFAULT_IMG_URL diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt deleted file mode 100644 index 7ecd724f9e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.tap.features.wallet.ui - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.transition.TransitionInflater -import com.squareup.picasso.Picasso -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_twin_cards.* -import kotlinx.android.synthetic.main.layout_twin_cards.* - -class OnboardingTwinsFragment : Fragment(R.layout.fragment_twin_cards) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - store.dispatch(WalletAction.TwinsAction.SetOnboardingShown) - val secondTwinNumber = - store.state.walletState.twinCardsState?.cardNumber?.pairNumber()?.number ?: "" - val text = getString(R.string.twins_onboarding_description_format, secondTwinNumber) - tv_twin_cards_description_1.text = text - - setOnClickListeners() - - Picasso.get() - .load(Artwork.TWIN_CARD_1) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_twin_card_1) - - Picasso.get() - .load(Artwork.TWIN_CARD_2) - .placeholder(R.drawable.card_placeholder) - ?.error(R.drawable.card_placeholder) - ?.into(iv_twin_card_2) - } - - private fun setOnClickListeners() { - btn_continue.setOnClickListener { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 1592ae4eb2..f2072f7cf3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -20,9 +20,7 @@ import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog import com.tangem.tap.store import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_details_twin_cards.* import kotlinx.android.synthetic.main.fragment_wallet_details.* -import kotlinx.android.synthetic.main.fragment_wallet_details.toolbar import kotlinx.android.synthetic.main.item_currency_wallet.view.* import kotlinx.android.synthetic.main.item_popular_token.view.* import kotlinx.android.synthetic.main.layout_balance_error.* @@ -98,12 +96,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS tv_currency_title.text = selectedWallet.currencyData.currency val currency = selectedWallet.currency - if (currency is Currency.Token) { - tv_currency_subtitle.text = currency.blockchain.tokenDisplayName() - tv_currency_subtitle.show() + if (currency is Currency.Token) { + tv_currency_subtitle.text = currency.blockchain.tokenDisplayName() + tv_currency_subtitle.show() } else { - tv_currency_subtitle.hide() - } + tv_currency_subtitle.hide() + } showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) @@ -129,9 +127,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS srl_wallet_details.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { store.dispatch(WalletAction.LoadWallet( - allowToBuy = selectedWallet.tradeCryptoState.buyingAllowed, - allowToSell = selectedWallet.tradeCryptoState.sellingAllowed, - blockchain = selectedWallet.currency?.blockchain + allowToBuy = selectedWallet.tradeCryptoState.buyingAllowed, + allowToSell = selectedWallet.tradeCryptoState.sellingAllowed, + blockchain = selectedWallet.currency?.blockchain )) } } @@ -180,8 +178,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS tv_address.text = state.walletAddresses.selectedAddress.address tv_explore?.setOnClickListener { store.dispatch(WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, - requireContext())) + state.walletAddresses.selectedAddress.exploreUrl, + requireContext())) } iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) } @@ -192,8 +190,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS if (state.error == ErrorType.NoInternetConnection) { srl_wallet_details?.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( - text = R.string.wallet_notification_no_internet, - buttonTitle = R.string.common_retry + text = R.string.wallet_notification_no_internet, + buttonTitle = R.string.common_retry ) { store.dispatch(WalletAction.LoadData) } } } else { @@ -249,8 +247,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS tv_error_title.text = getText(R.string.wallet_error_no_account) tv_error_descriptions.text = getString( - R.string.wallet_error_no_account_subtitle_format, - data.amountToCreateAccount, data.currencySymbol + R.string.wallet_error_no_account_subtitle_format, + data.amountToCreateAccount, data.currencySymbol ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index dbd285226f..f7978e35d8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -138,8 +138,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber store.dispatch(DetailsAction.PrepareScreen( - scanNoteResponse.card, scanNoteResponse, - store.state.walletState.walletManagers.map { it.wallet }, - store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed, - CardTou(), - store.state.globalState.appCurrency + scanNoteResponse, + store.state.walletState.walletManagers.map { it.wallet }, + CardTou(), + store.state.globalState.appCurrency )) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) true 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 d2e4e77cc5..2522ba7135 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 @@ -7,9 +7,8 @@ import android.widget.Button import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceWidget @@ -71,7 +70,7 @@ class SingleWalletView : WalletView { state.primaryWallet ?: return setupTwinCards(state.twinCardsState, fragment) - setupButtons(state.primaryWallet, state.twinCardsState != null, fragment) + setupButtons(state.primaryWallet, state.isTangemTwins, fragment) setupAddressCard(state.primaryWallet, fragment) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) @@ -87,12 +86,12 @@ class SingleWalletView : WalletView { private fun setupBalance(state: WalletState, primaryWallet: WalletData) { fragment?.apply { this.l_balance.show() - BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup() + BalanceWidget(this, primaryWallet.currencyData, state.isTangemTwins).setup() } } private fun setupTwinCards( - twinCardsState: TwinCardsState?, fragment: WalletFragment + twinCardsState: TwinCardsState?, fragment: WalletFragment ) = with(fragment) { twinCardsState?.cardNumber?.let { cardNumber -> tv_twin_card_number.show() @@ -106,11 +105,6 @@ class SingleWalletView : WalletView { if (twinCardsState?.cardNumber == null) { tv_twin_card_number.hide() } - if (twinCardsState?.showTwinOnboarding == true) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) - } - - } private fun setupButtons( diff --git a/app/src/main/res/drawable/card_placeholder.xml b/app/src/main/res/drawable/card_placeholder.xml deleted file mode 100644 index 1cfef76e78..0000000000 --- a/app/src/main/res/drawable/card_placeholder.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/card_placeholder_black.xml b/app/src/main/res/drawable/card_placeholder_black.xml new file mode 100644 index 0000000000..0fef87ee39 --- /dev/null +++ b/app/src/main/res/drawable/card_placeholder_black.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/card_placeholder_white.xml b/app/src/main/res/drawable/card_placeholder_white.xml new file mode 100644 index 0000000000..d980dc5599 --- /dev/null +++ b/app/src/main/res/drawable/card_placeholder_white.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_home_back_card.xml b/app/src/main/res/drawable/ic_home_back_card.xml deleted file mode 100644 index f15c47ba3f..0000000000 --- a/app/src/main/res/drawable/ic_home_back_card.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_home_front_card.xml b/app/src/main/res/drawable/ic_home_front_card.xml deleted file mode 100644 index 19969e806e..0000000000 --- a/app/src/main/res/drawable/ic_home_front_card.xml +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_bottom_action_views.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml similarity index 83% rename from app/src/main/res/layout-h680dp/layout_onboarding_bottom_action_views.xml rename to app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml index a603013d47..e635e3ee6f 100644 --- a/app/src/main/res/layout-h680dp/layout_onboarding_bottom_action_views.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml @@ -7,6 +7,7 @@ android:clipChildren="false"> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml b/app/src/main/res/layout/fragment_details_twin_cards_warning.xml deleted file mode 100644 index 1343fbe116..0000000000 --- a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml index 86c9138e69..40df33aeb5 100644 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -41,7 +41,7 @@ diff --git a/app/src/main/res/layout/fragment_twin_cards.xml b/app/src/main/res/layout/fragment_twin_cards.xml deleted file mode 100644 index 0bd670bcc3..0000000000 --- a/app/src/main/res/layout/fragment_twin_cards.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 3733c23089..bc98ff9e41 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -58,7 +58,7 @@ android:elevation="3dp" android:maxHeight="215dp" android:scaleType="fitCenter" - android:src="@drawable/card_placeholder" + android:src="@drawable/card_placeholder_black" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_twin_cards_orange.xml b/app/src/main/res/layout/layout_twin_cards_orange.xml deleted file mode 100644 index f524c5dd49..0000000000 --- a/app/src/main/res/layout/layout_twin_cards_orange.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_create_wallet.xml b/app/src/main/res/layout/lp_onboarding_create_wallet.xml index fa4505f353..3c5cd91655 100644 --- a/app/src/main/res/layout/lp_onboarding_create_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -6,6 +6,11 @@ android:layout_height="match_parent" android:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml index 65705f3fce..6b924ea771 100644 --- a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -6,6 +6,11 @@ android:layout_height="match_parent" android:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 3fbf992180..c4fddb3183 100644 --- a/app/src/main/res/layout/test_leapfrog_widget.xml +++ b/app/src/main/res/layout/test_leapfrog_widget.xml @@ -11,13 +11,12 @@ + android:src="@drawable/card_placeholder_black" + android:tint="@color/colorPrimary" /> + android:src="@drawable/card_placeholder_black" /> \ No newline at end of file diff --git a/app/src/main/res/layout/view_bg_twins_welcome.xml b/app/src/main/res/layout/view_bg_twins_welcome.xml new file mode 100644 index 0000000000..5202b1851b --- /dev/null +++ b/app/src/main/res/layout/view_bg_twins_welcome.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ 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 415f8bd19c..8b2e151d1a 100644 --- a/app/src/main/res/layout/view_onboarding_progress.xml +++ b/app/src/main/res/layout/view_onboarding_progress.xml @@ -1,10 +1,13 @@ - + + + + diff --git a/app/src/main/res/layout/view_onboarding_tv_balance.xml b/app/src/main/res/layout/view_onboarding_tv_balance.xml index 5dd54d7067..6577933837 100644 --- a/app/src/main/res/layout/view_onboarding_tv_balance.xml +++ b/app/src/main/res/layout/view_onboarding_tv_balance.xml @@ -12,6 +12,7 @@ android:id="@+id/tv_balance" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:elevation="0dp" android:fontFamily="sans-serif-medium" android:text="@string/onboarding_balance_title" android:textAllCaps="true" diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 17edb1b769..cb9cdc7224 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -14,25 +14,22 @@ Tangem Twin One wallet. Two cards. - This one that you are holding in your hands -and the other one with number %s.\n\nBoth cards can be used to extract funds from -this wallet. - + This one that you are holding in your handsand the other one with number #%s. Card %s of 2 Re-create twin wallet - This action is irreversible. You will not have access to the old wallet.\nThe wallet re-creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. - Re-create wallet - Step %s - Tap the #%s twin card - Preparing card - Creating wallet - The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. - Tap the card #%s - You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the #%s twin card + Preparing card + Creating wallet + The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. + Tap the card #%s + You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. + The twin address was successfully created //Common @@ -184,15 +181,18 @@ this wallet. Top up your wallet To get started, simply top up the card with any amount + To get started, simply top up the wallet with more than %s %s. Buy crypto Show the wallet’s address Scan the address to top up your wallet - Done! + Success! Your crypto card is activated and ready to be used Continue Balance + If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over. + Internal error: can\'t create wallet manager diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f008a00608..fe7f02a527 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,8 +5,7 @@ @color/colorPrimary @color/colorSecondary @color/darkGray6 - @color/backgroundLightGray - false + true true true @null