From 0382c454f5c386c9c2d52ed7c53cd756e0056982 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Dec 2020 16:35:04 +0300 Subject: [PATCH 1/5] Updated on 2026-08-14 --- app/src/main/res/drawable/ic_group_1258.xml | 26 +++ .../ic_twin_cards_background_warning.xml | 25 +++ .../drawable/rectangle_twin_background.xml | 13 ++ app/src/main/res/drawable/shape_chip.xml | 10 ++ .../layout/fragment_details_twin_cards.xml | 160 ++++++++++++++++++ .../fragment_details_twin_cards_warning.xml | 95 +++++++++++ .../main/res/layout/fragment_disclaimer.xml | 4 +- .../main/res/layout/fragment_twin_cards.xml | 90 ++++++++++ app/src/main/res/layout/fragment_wallet.xml | 37 +++- app/src/main/res/layout/layout_twin_cards.xml | 38 +++++ .../res/layout/layout_twin_cards_orange.xml | 38 +++++ app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/strings.xml | 4 - .../main/res/values/strings_untranslated.xml | 24 +++ 14 files changed, 560 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/ic_group_1258.xml create mode 100644 app/src/main/res/drawable/ic_twin_cards_background_warning.xml create mode 100644 app/src/main/res/drawable/rectangle_twin_background.xml create mode 100644 app/src/main/res/drawable/shape_chip.xml create mode 100644 app/src/main/res/layout/fragment_details_twin_cards.xml create mode 100644 app/src/main/res/layout/fragment_details_twin_cards_warning.xml create mode 100644 app/src/main/res/layout/fragment_twin_cards.xml create mode 100644 app/src/main/res/layout/layout_twin_cards.xml create mode 100644 app/src/main/res/layout/layout_twin_cards_orange.xml diff --git a/app/src/main/res/drawable/ic_group_1258.xml b/app/src/main/res/drawable/ic_group_1258.xml new file mode 100644 index 0000000000..b15674c9de --- /dev/null +++ b/app/src/main/res/drawable/ic_group_1258.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_twin_cards_background_warning.xml b/app/src/main/res/drawable/ic_twin_cards_background_warning.xml new file mode 100644 index 0000000000..54cccb6fa8 --- /dev/null +++ b/app/src/main/res/drawable/ic_twin_cards_background_warning.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/app/src/main/res/drawable/rectangle_twin_background.xml b/app/src/main/res/drawable/rectangle_twin_background.xml new file mode 100644 index 0000000000..73ca08b417 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_twin_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chip.xml b/app/src/main/res/drawable/shape_chip.xml new file mode 100644 index 0000000000..9544d9916d --- /dev/null +++ b/app/src/main/res/drawable/shape_chip.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_twin_cards.xml b/app/src/main/res/layout/fragment_details_twin_cards.xml new file mode 100644 index 0000000000..02b5a83991 --- /dev/null +++ b/app/src/main/res/layout/fragment_details_twin_cards.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 0000000000..35a4fdcf5a --- /dev/null +++ b/app/src/main/res/layout/fragment_details_twin_cards_warning.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml index 3efa0e31fa..0c771b9995 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -28,7 +28,6 @@ android:id="@+id/cl_details_confirm" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" android:layout_marginBottom="33dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> @@ -36,7 +35,7 @@ android:id="@+id/sv_wallet" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="8dp" android:fillViewport="true" android:overScrollMode="never" app:layout_constraintBottom_toTopOf="@id/btn_accept"> @@ -67,7 +66,6 @@ android:id="@+id/btn_accept" style="@style/TapButtonWithIcon" android:layout_width="0dp" - android:layout_height="48dp" android:layout_marginStart="7dp" android:layout_marginTop="30dp" android:layout_marginEnd="16dp" diff --git a/app/src/main/res/layout/fragment_twin_cards.xml b/app/src/main/res/layout/fragment_twin_cards.xml new file mode 100644 index 0000000000..690e785606 --- /dev/null +++ b/app/src/main/res/layout/fragment_twin_cards.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + \ 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 1f139adb02..9ed6acedb3 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -1,6 +1,7 @@ + + + + + + + + app:layout_constraintTop_toBottomOf="@id/barrier" /> + + + + + + + + + \ 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 new file mode 100644 index 0000000000..f524c5dd49 --- /dev/null +++ b/app/src/main/res/layout/layout_twin_cards_orange.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1883882991..2b8a0270b6 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -26,6 +26,8 @@ #F4F5F6 #DE000000 + #14181D + #F8F8FB #C7C7CC @@ -39,5 +41,7 @@ #0029FF #1F50FF #CBE4FF + #E0E6FA + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 574aa2d17c..c90c4534da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -204,8 +204,4 @@ To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ Reduce by %s XTZ No, send all - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index fa4aa42ee7..dd823ac37c 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -3,4 +3,28 @@ Default Compatibility Legacy + + 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. + + + Card %s of 2 + + Re-create twin wallet + This action is irreversible. If, after deleting the old wallet, someone sends funds to it, then you will not be able to withdraw them. + Re-create wallet + Step %s + Tap the #%s twin card + If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over. + 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 + + Start + Back + From 78e1021a76935429b5282b6b97204744d68ff85b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Dec 2020 16:36:43 +0300 Subject: [PATCH 2/5] Updated on 2026-08-14 --- app/build.gradle | 14 ++-- .../main/java/com/tangem/tap/MainActivity.kt | 2 +- .../tap/common/extensions/Navigation.kt | 6 ++ .../com/tangem/tap/common/extensions/UI.kt | 67 ++++++++++--------- .../redux/navigation/NavigationState.kt | 5 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 11 ++- .../tap/features/home/redux/HomeMiddleware.kt | 13 +++- .../send/redux/middlewares/SendMiddleware.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 9 ++- .../features/wallet/redux/WalletMiddleware.kt | 40 ++++++++--- .../features/wallet/redux/WalletReducer.kt | 22 +++++- .../tap/features/wallet/redux/WalletState.kt | 11 ++- .../tap/features/wallet/ui/WalletFragment.kt | 20 +++++- .../ui/dialogs/TwinsOnboardingFragment.kt | 47 +++++++++++++ .../tap/persistence/PreferencesStorage.kt | 10 ++- dependencies.gradle | 4 +- 16 files changed, 221 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt diff --git a/app/build.gradle b/app/build.gradle index ff677eeba7..aab2ec4bb8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,21 +65,21 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin" - implementation 'androidx.core:core-ktx:1.3.1' + implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.google.android.material:material:1.2.1' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' - implementation 'com.tangem:blockchain:1.117.0' - implementation 'com.tangem:core:1.83.0' - implementation 'com.tangem:sdk:1.83.0' + implementation 'com.tangem:blockchain:1.119.0' + implementation 'com.tangem:core:1.86.0' + implementation 'com.tangem:sdk:1.86.0' // WebView implementation "androidx.browser:browser:1.2.0" //lifecycle - implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index d8e89fe868..ef059014db 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,7 +10,7 @@ import com.tangem.CardFilter import com.tangem.Config import com.tangem.Log import com.tangem.TangemSdk -import com.tangem.common.extensions.CardType +import com.tangem.commands.common.card.CardType import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.navigation.AppScreen 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 bb69887342..603c004f13 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 @@ -7,10 +7,13 @@ import com.tangem.tap.common.redux.navigation.AppScreen 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.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment import com.tangem.wallet.R fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) { @@ -44,5 +47,8 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.DetailsConfirm -> DetailsConfirmFragment() AppScreen.DetailsSecurity -> DetailsSecurityFragment() AppScreen.Disclaimer -> DisclaimerFragment() + AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment() + AppScreen.CreateTwinWallet -> CreateTwinWalletFragment() + AppScreen.TwinsOnboarding -> TwinsOnboardingFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index 11d5aa6ca4..9491bc253a 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -13,6 +13,8 @@ import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat @@ -28,6 +30,11 @@ fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { return ContextCompat.getDrawable(this, drawableResId) } +@ColorInt +fun Fragment.getColor(@ColorRes colorRes: Int): Int { + return ContextCompat.getColor(requireContext(), colorRes) +} + fun View.getString(@StringRes id: Int): String { return context.getString(id) } @@ -57,22 +64,22 @@ fun View.makeInvisible() { } fun Context.dpToPixels(dp: Int): Int = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics - ).toInt() + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics + ).toInt() fun MaterialCardView.setMargins( - marginLeftDp: Int = 16, - marginTopDp: Int = 8, - marginRightDp: Int = 16, - marginBottomDp: Int = 8 + marginLeftDp: Int = 16, + marginTopDp: Int = 8, + marginRightDp: Int = 16, + marginBottomDp: Int = 8 ) { val params = this.layoutParams (params as ViewGroup.MarginLayoutParams).setMargins( - context.dpToPixels(marginLeftDp), - context.dpToPixels(marginTopDp), - context.dpToPixels(marginRightDp), - context.dpToPixels(marginBottomDp) + context.dpToPixels(marginLeftDp), + context.dpToPixels(marginTopDp), + context.dpToPixels(marginRightDp), + context.dpToPixels(marginBottomDp) ) this.layoutParams = params } @@ -82,31 +89,30 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) { val flags = this.window.decorView.systemUiVisibility // Update the SystemUiVisibility dependening on whether we want a Light or Dark theme. this.window.decorView.systemUiVisibility = - if (setTextDark) { - flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() - } else { - flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - } + if (setTextDark) { + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + } else { + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } } } - fun String.colorSegment( - context: Context, - color: Int, - startIndex: Int = 0, - endIndex: Int = this.length + context: Context, + color: Int, + startIndex: Int = 0, + endIndex: Int = this.length ): Spannable { return this.toSpannable() - .also { spannable -> - spannable.setSpan( - ForegroundColorSpan(ContextCompat.getColor(context, color)), - startIndex, - endIndex, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } + .also { spannable -> + spannable.setSpan( + ForegroundColorSpan(ContextCompat.getColor(context, color)), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } } fun View.hideKeyboard() { @@ -122,7 +128,8 @@ fun Context.copyToClipboard(value: Any, label: String = "") { } fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? { - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return default + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + ?: return default val clipData = clipboard.primaryClip ?: return default if (clipData.itemCount == 0) return default 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 5edfad540b..393a4e7e77 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 @@ -9,4 +9,7 @@ data class NavigationState( val activity: WeakReference? = null ) : StateType -enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer } \ No newline at end of file +enum class AppScreen { + Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer, + CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index a9e8b493d8..066b2b83db 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -14,7 +14,16 @@ class DisclaimerMiddleware { when (action) { is DisclaimerAction.AcceptDisclaimer -> { preferencesStorage.saveDisclaimerAccepted() - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + if (store.state.walletState.twinCardsState != null) { + val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() + if (showOnboarding) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } } next(action) 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 586051a676..7f4f67329c 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 @@ -54,11 +54,18 @@ class HomeMiddleware { } private fun showDisclaimerOrNavigateToWallet() { - if (preferencesStorage.wasDisclaimerAccepted()) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } else { + if (!preferencesStorage.wasDisclaimerAccepted()) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + return } + if (store.state.walletState.twinCardsState != null) { + val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() + if (showOnboarding) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + return + } + } + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } companion object { diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 845373fe34..1c7db4b842 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -4,7 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.Signer -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.stripZeroPlainString 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 4bd5972e2e..b8d37bc479 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 @@ -4,11 +4,12 @@ import android.content.Context import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.TwinCardNumber import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -94,4 +95,10 @@ 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 secondCardId: String, val number: TwinCardNumber) : TwinsAction() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 5aeba06e50..bdd1a724cb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -6,10 +6,10 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.ContextCompat import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.CardType import com.tangem.commands.common.network.Result import com.tangem.common.CompletionResult -import com.tangem.common.extensions.CardType import com.tangem.common.extensions.getType import com.tangem.common.extensions.toHexString import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -20,7 +20,10 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TopUpHelper +import com.tangem.tap.domain.TwinsHelper import com.tangem.tap.domain.extensions.toSendableAmounts +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.network.NetworkConnectivity @@ -60,15 +63,25 @@ class WalletMiddleware { } } is WalletAction.CreateWallet -> { - scope.launch { - val result = tangemSdkManager.createWallet( - store.state.globalState.scanNoteResponse?.card?.cardId - ) - when (result) { - is CompletionResult.Success -> { - store.state.globalState.tapWalletManager.onCardScanned(result.data) - } + if (store.state.walletState.twinCardsState != null) { + store.dispatch(DetailsAction.CreateTwinWalletAction.Proceed( + store.state.globalState.scanNoteResponse?.card?.cardId?.let { + TwinsHelper.getTwinCardNumber(it) + }, + CreateTwinWallet.CreateWallet + )) + } else { + scope.launch { + val result = tangemSdkManager.createWallet( + store.state.globalState.scanNoteResponse?.card?.cardId + ) + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager + .onCardScanned(result.data) + } + } } } } @@ -164,6 +177,13 @@ class WalletMiddleware { val cardId = store.state.globalState.scanNoteResponse?.card?.cardId cardId?.let { preferencesStorage.saveScannedCardId(it) } } + is WalletAction.TwinsAction.SetTwinCard -> { + val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() + if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding) + } + is WalletAction.TwinsAction.SetOnboardingShown -> { + preferencesStorage.saveTwinsOnboardingShown() + } } next(action) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 461d3363d0..92e07f9616 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -214,10 +214,30 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } is WalletAction.ChangeSelectedAddress -> { val walletAddresses = newState.walletAddresses ?: return newState - val address = walletAddresses.list.firstOrNull { it.type == action.type } ?: return newState + val address = walletAddresses.list.firstOrNull { it.type == action.type } + ?: return newState newState = newState.copy(walletAddresses = WalletAddresses(address, walletAddresses.list)) } + is WalletAction.TwinsAction.SetTwinCard -> { + newState = newState.copy( + twinCardsState = TwinCardsState( + secondCardId = action.secondCardId, + cardNumber = action.number, + newState.twinCardsState?.showTwinOnboarding ?: false) + ) + } + is WalletAction.TwinsAction.ShowOnboarding -> { + newState = newState.copy( + twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true) + ?: TwinCardsState(null, null, true) + ) + } + is WalletAction.TwinsAction.SetOnboardingShown -> { + newState = newState.copy( + twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false) + ) + } } return newState } 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 b55c64bbd4..40872e913b 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 @@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.common.entities.Button import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.domain.TwinCardNumber import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.ui.BalanceWidgetData import org.rekotlin.StateType @@ -24,7 +25,8 @@ data class WalletState( val walletDialog: WalletDialog? = null, val updatingWallet: Boolean = false, val mainButton: WalletMainButton = WalletMainButton.SendButton(false), - val topUpState: TopUpState = TopUpState() + val topUpState: TopUpState = TopUpState(), + val twinCardsState: TwinCardsState? = null, ) : StateType { val showDetails: Boolean = currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && @@ -45,6 +47,7 @@ sealed class WalletDialog { data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() data class WarningDialog(val type: WarningType) : WalletDialog() + data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog() } enum class WarningType { CardSignedHashesBefore, DevCard } @@ -97,4 +100,10 @@ data class TopUpState( val allowed: Boolean = true, val url: String? = null, val redirectUrl: String? = null +) + +data class TwinCardsState( + val secondCardId: String?, + val cardNumber: TwinCardNumber?, + val showTwinOnboarding: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 7575838e78..29d5da7833 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 @@ -18,6 +18,7 @@ import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.TwinCardNumber import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog @@ -91,6 +92,20 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber + tv_twin_card_number.show() + iv_twin_card.show() + val number = when (cardNumber) { + TwinCardNumber.First -> "1" + TwinCardNumber.Second -> "2" + } + tv_twin_card_number.text = getString(R.string.wallet_twins_chip_format, number) + } + if (state.twinCardsState?.cardNumber == null){ + tv_twin_card_number.hide() + iv_twin_card.hide() + } + if (!state.showDetails) { toolbar.menu.removeItem(R.id.details_menu) } else if (toolbar.menu.findItem(R.id.details_menu) == null) { @@ -289,9 +304,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { - store.state.globalState.scanNoteResponse?.card?.let { card -> + store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> store.dispatch(DetailsAction.PrepareScreen( - card, store.state.walletState.wallet, + scanNoteResponse.card, scanNoteResponse, + store.state.walletState.wallet, store.state.globalState.appCurrency )) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt new file mode 100644 index 0000000000..799b4e0789 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt @@ -0,0 +1,47 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +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.* + +class TwinsOnboardingFragment : 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 secondCardId = store.state.walletState.twinCardsState?.secondCardId + val text = getString(R.string.twins_onboarding_description_format, secondCardId) + tv_twin_cards_description_1.text = text + + setOnClickListeners() + } + + 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/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 6ce4249585..08346630f3 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -74,6 +74,14 @@ class PreferencesStorage(applicationContext: Application) { return preferences.getBoolean(DISCLAIMER_ACCEPTED_KEY, false) } + fun saveTwinsOnboardingShown() { + preferences.edit().putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true).apply() + } + + fun wasTwinsOnboardingShown(): Boolean { + return preferences.getBoolean(TWINS_ONBOARDING_SHOWN_KEY, false) + } + private fun incrementLaunchCounter() { var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0) preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) } @@ -83,9 +91,9 @@ class PreferencesStorage(applicationContext: Application) { private const val PREFERENCES_NAME = "tapPrefs" private const val APP_CURRENCY_KEY = "appCurrency" private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" - private const val FIRST_LAUNCH_CHECK_KEY = "firstLaunchCheck" private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted" + private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown" private const val APP_LAUNCH_COUNT_KEY = "launchCount" } diff --git a/dependencies.gradle b/dependencies.gradle index 2010dc13b7..90e2ab6cfd 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.4.0', - build_gradle: '4.1.0', + kotlin : '1.4.10', + build_gradle: '4.1.1', ] From ec392c2cc84543b0ddc441b13dcdd24f5a96c963 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Dec 2020 16:38:06 +0300 Subject: [PATCH 3/5] Updated on 2026-08-14 --- .../tap/common/analytics/AnalyticsHandler.kt | 3 +- .../analytics/FirebaseAnalyticsHandler.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 4 +- .../com/tangem/tap/domain/TapWalletManager.kt | 16 +++- .../com/tangem/tap/domain/TapWorkarounds.kt | 2 +- .../tangem/tap/domain/tasks/ScanNoteTask.kt | 87 +++++++++++++++---- .../domain/twins/CreateFirstTwinWalletTask.kt | 41 +++++++++ .../twins/CreateSecondTwinWalletTask.kt | 50 +++++++++++ .../tap/domain/twins/TwinCardsManager.kt | 83 ++++++++++++++++++ .../tangem/tap/domain/twins/TwinsHelper.kt | 65 ++++++++++++++ 10 files changed, 328 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt index 8057bcae9e..9dddeee4f2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.analytics -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card + interface AnalyticsHandler { fun triggerEvent(event: AnalyticsEvent, card: Card?) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt index 6e7f460773..5b468233ea 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.core.os.bundleOf import com.google.firebase.analytics.ktx.analytics import com.google.firebase.ktx.Firebase -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card object FirebaseAnalyticsHandler: AnalyticsHandler { override fun triggerEvent(event: AnalyticsEvent, card: Card?) { @@ -17,7 +17,7 @@ object FirebaseAnalyticsHandler: AnalyticsHandler { return bundleOf( AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName, AnalyticsParam.BATCH_ID.param to card.cardData?.batchId, - AnalyticsParam.FIRMWARE.param to card.firmwareVersion + AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version ) } 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 a1971bad03..e34ddfe8c7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -3,8 +3,8 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* import com.tangem.commands.* +import com.tangem.commands.common.card.CardType import com.tangem.common.CompletionResult -import com.tangem.common.extensions.CardType import com.tangem.common.extensions.calculateSha256 import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.analytics.AnalyticsEvent @@ -64,7 +64,7 @@ class TangemSdkManager(val activity: ComponentActivity) { ), cardId, initialMessage = Message(activity.getString(R.string.initial_message_tap_header))) } - private suspend fun runTaskAsync( + suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null ): CompletionResult = withContext(Dispatchers.IO) { 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 c6e9bae5c8..f9da8875d4 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -3,7 +3,8 @@ package com.tangem.tap.domain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager -import com.tangem.commands.CardStatus +import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.common.card.masks.Product import com.tangem.commands.common.network.Result import com.tangem.common.extensions.toHexString import com.tangem.tap.common.analytics.AnalyticsEvent @@ -93,7 +94,15 @@ class TapWalletManager { withContext(Dispatchers.Main) { store.dispatch(WalletAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) - store.dispatch(AddressPayIdActionUi.ChangePayIdState(configManager?.config?.isWalletPayIdEnabled ?: false)) + store.dispatch(AddressPayIdActionUi.ChangePayIdState(configManager?.config?.isWalletPayIdEnabled + ?: false)) + if (data.card.cardData?.productMask?.contains(Product.TwinCard) == true) { + val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId) + val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId) + if (secondCardId != null && cardNumber != null) { + store.dispatch(WalletAction.TwinsAction.SetTwinCard(secondCardId, cardNumber)) + } + } loadData(data) } } @@ -114,7 +123,8 @@ class TapWalletManager { store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) store.dispatch(WalletAction.LoadFiatRate) store.dispatch(WalletAction.LoadPayId) - } else if (data.card.status == CardStatus.Empty) { + } else if (data.card.status == CardStatus.Empty || + data.card.cardData?.productMask?.contains(Product.TwinCard) == true) { store.dispatch(WalletAction.EmptyWallet) store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } else { 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 bb5e44f015..1e18167a52 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -1,6 +1,6 @@ package com.tangem.tap.domain -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card import java.util.* object TapWorkarounds { 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 index 93a2617258..1f0c9bcd06 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -6,20 +6,28 @@ import com.tangem.TangemError import com.tangem.TangemSdkError import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.commands.Card -import com.tangem.commands.CardStatus import com.tangem.commands.CommandResponse -import com.tangem.commands.Product +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.common.card.masks.Product +import com.tangem.commands.file.ReadFileDataCommand import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvDecoder +import com.tangem.common.tlv.TlvTag import com.tangem.tap.domain.TapSdkError +import com.tangem.tap.domain.TwinsHelper import com.tangem.tasks.ScanTask data class ScanNoteResponse( val walletManager: WalletManager?, val card: Card, - val verifyResponse: VerifyCardResponse? = null + val verifyResponse: VerifyCardResponse? = null, + val secondTwinPublicKey: String? = null ) : CommandResponse class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { @@ -42,31 +50,74 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable - when (verifyResult) { - is CompletionResult.Success -> { - callback(CompletionResult.Success(ScanNoteResponse( - walletManager, card, verifyResult.data - ))) - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - } - } - } + verifyCard(walletManager, card, null, session, callback) } } } } + private fun verifyCard( + walletManager: WalletManager?, card: Card, publicKey: String? = null, + session: CardSession, callback: (result: CompletionResult) -> Unit + ) { + + VerifyCardCommand(true).run(session) { verifyResult -> + when (verifyResult) { + is CompletionResult.Success -> { + callback(CompletionResult.Success(ScanNoteResponse( + walletManager, card, verifyResult.data + ))) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) + } + } + } + } + + private fun dealWithTwinCard( + card: Card, session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + ReadFileDataCommand(readPrivateFiles = true).run(session) { filesResult -> + when (filesResult) { + is CompletionResult.Success -> { + val decoder = Tlv.deserialize(filesResult.data.fileData)?.let { TlvDecoder(it) } + val name = decoder?.decodeOptional(TlvTag.FileName) + if (name != null && name == TwinsHelper.TWIN_FILE_NAME) { + val publicKey = decoder.decodeOptional(TlvTag.FileData)?.toHexString() + val walletManager = try { + WalletManagerFactory.makeMultisigWalletManager(card, publicKey!!.hexToBytes()) + } catch (exception: Exception) { + callback(CompletionResult.Success(ScanNoteResponse(null, card))) + return@run + } + verifyCard(walletManager, card, publicKey, session, callback) + return@run + } else { + callback(CompletionResult.Success(ScanNoteResponse(null, card))) + } + } + is CompletionResult.Failure -> + callback(CompletionResult.Success(ScanNoteResponse(null, card))) + } + + } + } + private fun getErrorIfExcludedCard(card: Card): TangemError? { - if (card.cardData?.productMask != null && - card.cardData?.productMask?.contains(Product.Note) != true) { + val productMask = card.cardData?.productMask ?: return TangemSdkError.CardError() + if (!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard)) { return TapSdkError.CardForDifferentApp } if (excludedBatches.contains(card.cardData?.batchId)) { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt new file mode 100644 index 0000000000..4b008408e6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.domain.twins + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.commands.CreateWalletResponse +import com.tangem.commands.PurgeWalletCommand +import com.tangem.commands.common.card.CardStatus +import com.tangem.common.CompletionResult +import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.file.DeleteFilesTask + +class CreateFirstTwinWalletTask : CardSessionRunnable { + override val requiresPin2 = false + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + if (session.environment.card?.walletPublicKey != null) { + PurgeWalletCommand().run(session) { response -> + when (response) { + is CompletionResult.Success -> { + session.environment.card = session.environment.card?.copy(status = CardStatus.Empty) + finishTask(session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) + } + } + } else { + finishTask(session, callback) + } + } + + private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { + DeleteFilesTask().run(session) { deleteResponse -> + when (deleteResponse) { + is CompletionResult.Success -> + CreateWalletTask().run(session) { callback(it) } + is CompletionResult.Failure -> + callback(CompletionResult.Failure(deleteResponse.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt new file mode 100644 index 0000000000..4885e78f38 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.domain.twins + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.commands.CreateWalletResponse +import com.tangem.commands.PurgeWalletCommand +import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.file.WriteFileDataCommand +import com.tangem.common.CompletionResult +import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.file.DeleteFilesTask + +class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessionRunnable { + override val requiresPin2 = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + if (session.environment.card?.walletPublicKey != null) { + PurgeWalletCommand().run(session) { response -> + when (response) { + is CompletionResult.Success -> { + session.environment.card = + session.environment.card?.copy(status = CardStatus.Empty) + finishTask(session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) + } + } + } else { + finishTask(session, callback) + } + } + + private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { + DeleteFilesTask().run(session) { deleteResponse -> + when (deleteResponse) { + is CompletionResult.Success -> { + val file = TwinCardsManager.createFileWithPublicKey(firstPublicKey) + WriteFileDataCommand(file).run(session) { result -> + when (result) { + is CompletionResult.Success -> CreateWalletTask().run(session, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + is CompletionResult.Failure -> + callback(CompletionResult.Failure(deleteResponse.error)) + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..0255ed6f5a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.domain.twins + +import com.tangem.Message +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.commands.file.FileData +import com.tangem.commands.file.WriteFileDataCommand +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.common.tlv.TlvEncoder +import com.tangem.common.tlv.TlvTag +import com.tangem.common.tlv.serialize +import com.tangem.tap.domain.TwinsHelper +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.tangemSdkManager + +class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) { + + private val currentCardId: String = scanNoteResponse.card.cardId + private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) + + private var currentCardPublicKey: String? = null + private var secondCardPublicKey: String? = null + + suspend fun createFirstWallet(message: Message): SimpleResult { + val response = tangemSdkManager.runTaskAsync( + CreateFirstTwinWalletTask(), currentCardId, message + ) + when (response) { + is CompletionResult.Success -> { + currentCardPublicKey = response.data.walletPublicKey.toHexString() + return SimpleResult.Success + } + is CompletionResult.Failure -> return SimpleResult.failure(response.error) + } + + } + + + suspend fun createSecondWallet(message: Message): SimpleResult { + val response = tangemSdkManager.runTaskAsync( + CreateSecondTwinWalletTask(currentCardPublicKey!!), secondCardId, message + ) + when (response) { + is CompletionResult.Success -> { + secondCardPublicKey = response.data.walletPublicKey.toHexString() + return SimpleResult.Success + } + is CompletionResult.Failure -> return SimpleResult.failure(response.error) + } + + } + + suspend fun complete(message: Message): Result { + val response = tangemSdkManager.runTaskAsync( + WriteFileDataCommand(createFileWithPublicKey(secondCardPublicKey!!)), + currentCardId, message + ) + return when (response) { + is CompletionResult.Success -> { + val walletManager = WalletManagerFactory.makeMultisigWalletManager( + scanNoteResponse.card, secondCardPublicKey!!.hexToBytes() + ) + return Result.Success(scanNoteResponse.copy( + walletManager = walletManager, + secondTwinPublicKey = secondCardPublicKey + )) + } + is CompletionResult.Failure -> Result.failure(response.error) + } + } + + companion object { + fun createFileWithPublicKey(publicKey: String): FileData { + val nameTlv = TlvEncoder().encode(TlvTag.FileName, "TwinPublicKey") + val pubKey = TlvEncoder().encode(TlvTag.FileData, publicKey.hexToBytes()) + val tlvs = listOf(nameTlv, pubKey).serialize() + return FileData.DataProtectedByPasscode(tlvs) + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..ddb6d6854b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt @@ -0,0 +1,65 @@ +package com.tangem.tap.domain + +import com.tangem.tap.common.extensions.isEven + +class TwinsHelper { + companion object { + const val TWIN_FILE_NAME = "TwinPublicKey" + + fun getTwinCardNumber(cardId: String): TwinCardNumber? { + return when { + firstCardBatches.map { cardId.startsWith(it) }.contains(true) -> { + TwinCardNumber.First + } + secondCardBatches.map { cardId.startsWith(it) }.contains(true) -> { + TwinCardNumber.Second + } + else -> { + null + } + } + } + + fun getTwinsCardId(cardId: String): String? { + val cardIdWithNewBatch = when (getTwinCardNumber(cardId) ?: return null) { + TwinCardNumber.First -> { + val batchIndex = if (cardId.startsWith(firstCardBatches[1])) 1 else 2 + cardId.replace(firstCardBatches[batchIndex], secondCardBatches[batchIndex]) + } + TwinCardNumber.Second -> { + val batchIndex = if (cardId.startsWith(secondCardBatches[1])) 1 else 2 + cardId.replace(secondCardBatches[batchIndex], firstCardBatches[batchIndex]) + } + } + val cardIdWithoutChecksum = cardIdWithNewBatch.dropLast(1) + val checkSum = cardIdWithoutChecksum.calculateLuhn() + return cardIdWithoutChecksum + checkSum + } + + private val firstCardBatches = listOf("CB61", "CB64") + private val secondCardBatches = listOf("CB62", "CB65") + } +} + +private fun String.calculateLuhn(): Int { + return 10 - this.reversed() + .mapIndexed { index, c -> + val digit = if (c in '0'..'9') c - '0' else c - 'A' + if (!index.isEven()) { + digit + } else { + val newDigit = digit * 2 + if (newDigit >= 10) newDigit - 9 else newDigit + } + } + .sum() % 10 +} + +enum class TwinCardNumber(val number: Int) { + First(1), Second(2); + + fun pairNumber(): TwinCardNumber = when (this) { + First -> Second + Second -> First + } +} \ No newline at end of file From db314577a079dbd878e1b1185807bbb66a821d69 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Dec 2020 16:38:44 +0300 Subject: [PATCH 4/5] Updated on 2026-08-14 --- .../features/details/redux/DetailsAction.kt | 50 +++++- .../details/redux/DetailsMiddleware.kt | 5 +- .../features/details/redux/DetailsReducer.kt | 28 +++- .../features/details/redux/DetailsState.kt | 6 +- .../redux/twins/CreateTwinWalletMiddleware.kt | 104 ++++++++++++ .../redux/twins/CreateTwinWalletReducer.kt | 67 ++++++++ .../redux/twins/CreateTwinWalletState.kt | 17 ++ .../features/details/ui/DetailsFragment.kt | 23 ++- .../ui/twins/CreateTwinWalletFragment.kt | 152 ++++++++++++++++++ .../ui/twins/TwinWalletWarningFragment.kt | 41 +++++ 10 files changed, 477 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/twins/CreateTwinWalletFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt 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 448e0a48ad..1fb22f9243 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,9 +1,13 @@ package com.tangem.tap.features.details.redux +import com.tangem.Message import com.tangem.blockchain.common.Wallet -import com.tangem.commands.Card +import com.tangem.commands.common.card.Card import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.domain.TwinCardNumber +import com.tangem.tap.domain.tasks.ScanNoteResponse +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 @@ -12,10 +16,11 @@ sealed class DetailsAction : Action { data class PrepareScreen( val card: Card, + val scanNoteResponse: ScanNoteResponse, val wallet: Wallet?, val fiatCurrencyName: FiatCurrencyName, val fiatCurrencies: List? = null, - ): DetailsAction() + ) : DetailsAction() object ShowDisclaimer : DetailsAction() @@ -23,24 +28,54 @@ sealed class DetailsAction : Action { sealed class EraseWallet : DetailsAction() { object Check : EraseWallet() object Proceed : EraseWallet() { - object NotAllowedByCard: EraseWallet(), NotificationAction { + object NotAllowedByCard : EraseWallet(), NotificationAction { override val messageResource = R.string.error_purge_prohibited } - object NotEmpty: EraseWallet(), NotificationAction { + + object NotEmpty : EraseWallet(), NotificationAction { override val messageResource = R.string.details_notification_erase_wallet_not_possible } } + object Confirm : EraseWallet() object Cancel : EraseWallet() object Failure : EraseWallet() object Success : EraseWallet() } + sealed class CreateTwinWalletAction : DetailsAction() { + object ShowWarning : CreateTwinWalletAction() + object ShowAlert : CreateTwinWalletAction() + object HideAlert : CreateTwinWalletAction() + data class Proceed( + val twinCardNumber: TwinCardNumber?, val createTwinWallet: CreateTwinWallet + ) : CreateTwinWalletAction() + + object Cancel : CreateTwinWalletAction() { + object Confirm : CreateTwinWalletAction() + } + + data class LaunchFirstStep(val message: Message) : CreateTwinWalletAction() { + object Success : CreateTwinWalletAction() + object Failure : CreateTwinWalletAction() + } + + data class LaunchSecondStep(val message: Message) : CreateTwinWalletAction() { + object Success : CreateTwinWalletAction() + object Failure : CreateTwinWalletAction() + } + + data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() { + data class Success(val scanNoteResponse: ScanNoteResponse) : CreateTwinWalletAction() + object Failure : CreateTwinWalletAction() + } + } + sealed class AppCurrencyAction : DetailsAction() { data class SetCurrencies(val currencies: List) : AppCurrencyAction() object ChooseAppCurrency : AppCurrencyAction() - object Cancel: AppCurrencyAction() - data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction() + object Cancel : AppCurrencyAction() + data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName) : AppCurrencyAction() } sealed class ManageSecurity : DetailsAction() { @@ -50,8 +85,9 @@ sealed class DetailsAction : Action { object Success : ManageSecurity() object Failure : ManageSecurity() } + data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() { - object AlreadySet: ManageSecurity(), NotificationAction { + object AlreadySet : ManageSecurity(), NotificationAction { override val messageResource = R.string.details_notification_security_option_already_active } } 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 e3a4f73b18..d603c66c39 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -6,6 +6,7 @@ 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.wallet.redux.WalletAction import com.tangem.tap.network.coinmarketcap.CoinMarketCapService @@ -22,6 +23,7 @@ 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 -> @@ -30,7 +32,8 @@ class DetailsMiddleware { is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action) is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) - is DetailsAction.ShowDisclaimer -> { + is DetailsAction.CreateTwinWalletAction -> twinWalletMiddleware.handle(action) + is DetailsAction.ShowDisclaimer -> { store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } 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 5f4641aed7..52e245bd7b 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 @@ -1,10 +1,16 @@ package com.tangem.tap.features.details.redux -import com.tangem.commands.Card -import com.tangem.commands.Settings + +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.masks.Product +import com.tangem.commands.common.card.masks.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapWorkarounds +import com.tangem.tap.domain.TwinsHelper import com.tangem.tap.domain.extensions.toSendableAmounts +import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer +import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState +import com.tangem.wallet.BuildConfig import org.rekotlin.Action import java.util.* @@ -31,6 +37,9 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { is DetailsAction.ManageSecurity -> { handleSecurityAction(action, detailsState) } + is DetailsAction.CreateTwinWalletAction -> { + CreateTwinWalletReducer.handle(action, detailsState) + } else -> detailsState } } @@ -47,13 +56,25 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta SecurityOption.LongTap } } + val twinsState = if (action.card.cardData?.productMask?.contains(Product.TwinCard) == true) { + CreateTwinWalletState( + scanResponse = action.scanNoteResponse, + twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), + createTwinWallet = null, + showAlert = false, + allowRecreatingWallet = BuildConfig.DEBUG + ) + } else { + null + } return DetailsState( card = action.card, wallet = action.wallet, cardInfo = action.card.toCardInfo(), appCurrencyState = AppCurrencyState( action.fiatCurrencyName ), - securityScreenState = SecurityScreenState(currentOption = securityOption) + securityScreenState = SecurityScreenState(currentOption = securityOption), + createTwinWalletState = twinsState ) } @@ -169,6 +190,7 @@ private fun handleSecurityAction( } } + private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.cardData?.issuerName ?: return null 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 7c152c57ee..4730507ebc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -1,10 +1,11 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet -import com.tangem.commands.Card +import com.tangem.commands.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.network.coinmarketcap.FiatCurrency import org.rekotlin.StateType import java.util.* @@ -17,6 +18,7 @@ data class DetailsState( val eraseWalletState: EraseWalletState? = null, val confirmScreenState: ConfirmScreenState? = null, val securityScreenState: SecurityScreenState? = null, + val createTwinWalletState: CreateTwinWalletState? = null ) : StateType data class CardInfo( @@ -39,4 +41,4 @@ data class AppCurrencyState( 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 new file mode 100644 index 0000000000..279ae9a5c0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt @@ -0,0 +1,104 @@ +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.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +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) { + DetailsAction.CreateTwinWalletAction.ShowWarning -> + 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()) + } + is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> { + store.state.globalState.scanNoteResponse?.let { + twinsManager = TwinCardsManager(it) + } + 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.message) + 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 -> { + store.dispatch(GlobalAction.SaveScanNoteResponse(action.scanNoteResponse)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + 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 new file mode 100644 index 0000000000..6b9ff37533 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt @@ -0,0 +1,67 @@ +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) { + DetailsAction.CreateTwinWalletAction.ShowWarning -> { + 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.copy(createTwinWalletState = CreateTwinWalletState( + scanResponse = null, + twinCardNumber = action.twinCardNumber + ?: state.createTwinWalletState?.twinCardNumber, + createTwinWallet = action.createTwinWallet, + showAlert = false, + step = CreateTwinWalletStep.FirstStep, + )) + } + 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 new file mode 100644 index 0000000000..d42808e26e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.features.details.redux.twins + +import com.tangem.tap.domain.TwinCardNumber +import com.tangem.tap.domain.tasks.ScanNoteResponse + +data class CreateTwinWalletState( + val scanResponse: ScanNoteResponse?, + 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 df604f266d..6ad375022a 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 @@ -5,6 +5,8 @@ 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.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState @@ -66,11 +68,26 @@ 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) + } + } + + + 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, twinCardNumber) + ))) + } + 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(defaultColor) + 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) + ) + )) + } + 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(defaultColor) + v_step_2.setBackgroundColor(defaultColor) + 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) { + 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) { _, _ -> + 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 new file mode 100644 index 0000000000..9896b988f7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt @@ -0,0 +1,41 @@ +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.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.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_details_twin_cards_warning.* + +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) + btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } + btn_start.setOnClickListener { + store.dispatch( + DetailsAction.CreateTwinWalletAction.Proceed( + null, createTwinWallet = CreateTwinWallet.RecreateWallet + )) + } + } + + +} \ No newline at end of file From 86c103c40b3455f5759fb9714d50eb9893b2c628 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Dec 2020 17:09:28 +0300 Subject: [PATCH 5/5] Updated on 2026-08-14 --- app/src/main/assets/features_dev.json | 4 +++ app/src/main/assets/features_prod.json | 4 +++ .../com/tangem/tap/domain/TapWalletManager.kt | 5 +++- .../tangem/tap/domain/config/ConfigManager.kt | 9 +++++++ .../features/details/redux/DetailsAction.kt | 1 + .../features/details/redux/DetailsReducer.kt | 3 +-- .../tap/features/wallet/redux/WalletAction.kt | 7 +++-- .../features/wallet/redux/WalletReducer.kt | 26 +++++++++++++------ .../tap/features/wallet/redux/WalletState.kt | 3 ++- .../tap/features/wallet/ui/WalletFragment.kt | 3 ++- 10 files changed, 50 insertions(+), 15 deletions(-) diff --git a/app/src/main/assets/features_dev.json b/app/src/main/assets/features_dev.json index 33cf6e331b..236d0285bd 100644 --- a/app/src/main/assets/features_dev.json +++ b/app/src/main/assets/features_dev.json @@ -6,5 +6,9 @@ { "name": "isTopUpEnabled", "value": true + }, + { + "name": "isCreatingTwinCardsAllowed", + "value": false } ] \ No newline at end of file diff --git a/app/src/main/assets/features_prod.json b/app/src/main/assets/features_prod.json index ede61eb088..39b226d053 100644 --- a/app/src/main/assets/features_prod.json +++ b/app/src/main/assets/features_prod.json @@ -6,5 +6,9 @@ { "name": "isTopUpEnabled", "value": false + }, + { + "name": "isCreatingTwinCardsAllowed", + "value": true } ] \ No newline at end of file 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 f9da8875d4..11fa152761 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -100,7 +100,10 @@ class TapWalletManager { val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId) val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId) if (secondCardId != null && cardNumber != null) { - store.dispatch(WalletAction.TwinsAction.SetTwinCard(secondCardId, cardNumber)) + store.dispatch(WalletAction.TwinsAction.SetTwinCard( + secondCardId, cardNumber, + configManager?.config?.isCreatingTwinCardsAllowed ?: false + )) } } loadData(data) diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt index 339aeb3124..5bacbd31ba 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt @@ -11,6 +11,7 @@ data class Config( val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", val isWalletPayIdEnabled: Boolean = true, val isTopUpEnabled: Boolean = false, + val isCreatingTwinCardsAllowed: Boolean = false ) class ConfigManager( @@ -38,6 +39,7 @@ class ConfigManager( when (name) { isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false) isTopUpEnabled -> config = config.copy(isTopUpEnabled = false) + isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false) } } @@ -45,6 +47,8 @@ class ConfigManager( when (name) { isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled) isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled) + isCreatingTwinCardsAllowed -> config = + config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) } } @@ -60,6 +64,10 @@ class ConfigManager( config = config.copy(isTopUpEnabled = newValue) defaultConfig = defaultConfig.copy(isTopUpEnabled = newValue) } + isCreatingTwinCardsAllowed -> { + config = config.copy(isCreatingTwinCardsAllowed = newValue) + defaultConfig = defaultConfig.copy(isCreatingTwinCardsAllowed = newValue) + } } } @@ -83,6 +91,7 @@ class ConfigManager( companion object { const val isWalletPayIdEnabled = "isWalletPayIdEnabled" const val isTopUpEnabled = "useTopUp" + const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed" const val coinMarketCapKey = "coinMarketCapKey" const val moonPayApiKey = "moonPayApiKey" const val moonPayApiSecretKey = "moonPayApiSecretKey" 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 1fb22f9243..fe8c56708e 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 @@ -18,6 +18,7 @@ sealed class DetailsAction : Action { val card: Card, val scanNoteResponse: ScanNoteResponse, val wallet: Wallet?, + val isCreatingTwinWalletAllowed: Boolean?, val fiatCurrencyName: FiatCurrencyName, val fiatCurrencies: List? = null, ) : DetailsAction() 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 52e245bd7b..58997e57b6 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 @@ -10,7 +10,6 @@ import com.tangem.tap.domain.TwinsHelper import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState -import com.tangem.wallet.BuildConfig import org.rekotlin.Action import java.util.* @@ -62,7 +61,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), createTwinWallet = null, showAlert = false, - allowRecreatingWallet = BuildConfig.DEBUG + allowRecreatingWallet = action.isCreatingTwinWalletAllowed ) } else { null 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 b8d37bc479..58ffdb8acc 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 @@ -94,11 +94,14 @@ sealed class WalletAction : Action { data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction() } - data class ChangeSelectedAddress(val type: AddressType): WalletAction() + data class ChangeSelectedAddress(val type: AddressType) : WalletAction() sealed class TwinsAction : WalletAction() { object ShowOnboarding : TwinsAction() object SetOnboardingShown : TwinsAction() - data class SetTwinCard(val secondCardId: String, val number: TwinCardNumber) : TwinsAction() + data class SetTwinCard( + val secondCardId: String, 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/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 92e07f9616..05436a495a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -34,12 +34,17 @@ private fun internalReduce(action: Action, state: AppState): WalletState { when (action) { is WalletAction.ResetState -> newState = WalletState() - is WalletAction.EmptyWallet -> newState = newState.copy( - state = ProgressState.Done, - currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), - mainButton = WalletMainButton.CreateWalletButton(true), - topUpState = TopUpState(false) - ) + is WalletAction.EmptyWallet -> { + val creatingWalletAllowed = !(newState.twinCardsState != null && + newState.twinCardsState?.isCreatingTwinCardsAllowed != true) + + newState = newState.copy( + state = ProgressState.Done, + currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), + mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed), + topUpState = TopUpState(false) + ) + } is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { @@ -224,13 +229,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState { twinCardsState = TwinCardsState( secondCardId = action.secondCardId, cardNumber = action.number, - newState.twinCardsState?.showTwinOnboarding ?: false) + showTwinOnboarding = newState.twinCardsState?.showTwinOnboarding + ?: false, + isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed + ) ) } is WalletAction.TwinsAction.ShowOnboarding -> { newState = newState.copy( twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true) - ?: TwinCardsState(null, null, true) + ?: TwinCardsState(null, null, + showTwinOnboarding = true, + isCreatingTwinCardsAllowed = false) ) } is WalletAction.TwinsAction.SetOnboardingShown -> { 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 40872e913b..7593182093 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 @@ -105,5 +105,6 @@ data class TopUpState( data class TwinCardsState( val secondCardId: String?, val cardNumber: TwinCardNumber?, - val showTwinOnboarding: Boolean + val showTwinOnboarding: Boolean, + val isCreatingTwinCardsAllowed: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 29d5da7833..6e921e5b9b 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 @@ -101,7 +101,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber