From bb1f7b4c97bb5d131f8cf2863c60992d00d76d39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Aug 2021 19:31:01 +0300 Subject: [PATCH 01/55] Updated on 2026-08-14 --- app/build.gradle | 16 ++++++++-------- dependencies.gradle | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5bad46aa84..d9946006d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,9 +67,9 @@ dependencies { implementation fileTree(include: ['*.aar'], dir: 'libs') implementation 'androidx.core:core-ktx:1.6.0' - implementation 'androidx.appcompat:appcompat:1.3.0' - implementation 'androidx.fragment:fragment-ktx:1.3.5' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.fragment:fragment-ktx:1.3.6' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation 'com.google.android.material:material:1.4.0' implementation "androidx.core:core-ktx:1.6.0" implementation 'com.google.android.play:core:1.10.0' @@ -113,8 +113,8 @@ dependencies { // Network and Json implementation 'com.squareup.retrofit2:retrofit:2.8.1' implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' - implementation 'com.squareup.moshi:moshi:1.9.2' - implementation "com.squareup.moshi:moshi-kotlin:1.9.2" + implementation 'com.squareup.moshi:moshi:1.12.0' + implementation "com.squareup.moshi:moshi-kotlin:1.12.0" implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' // // //Crypto @@ -122,8 +122,8 @@ dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation "com.google.truth:truth:1.0.1" - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation "com.google.truth:truth:1.1.3" + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index e4beaa5aaa..cb6bb51434 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.5.20', + kotlin : '1.5.21', build_gradle: '4.2.2', ] From a5598d3909760fab04dbf2783863c96019449fb9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Aug 2021 13:39:08 +0300 Subject: [PATCH 02/55] Updated on 2026-08-14 --- .../disclaimer/ui/DisclaimerFragment.kt | 5 ++++- .../drawable/bg_half_transparent_overlay.xml | 9 +++++++++ .../main/res/layout/fragment_disclaimer.xml | 19 +++++++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/bg_half_transparent_overlay.xml diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index fb7ebe023d..ec7a45dd3d 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -58,6 +58,9 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), override fun newState(state: DisclaimerState) { if (activity == null) return - if (state.accepted) btn_accept.hide() + if (state.accepted) { + half_transparent_overlay.hide() + btn_accept.hide() + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_half_transparent_overlay.xml b/app/src/main/res/drawable/bg_half_transparent_overlay.xml new file mode 100644 index 0000000000..4b5937d793 --- /dev/null +++ b/app/src/main/res/drawable/bg_half_transparent_overlay.xml @@ -0,0 +1,9 @@ + + + + \ 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 0c771b9995..bec0ceace0 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -28,17 +28,15 @@ android:id="@+id/cl_details_confirm" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="33dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> + app:layout_constraintBottom_toBottomOf="parent"> @@ -62,18 +60,27 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintGuide_percent="0.37" /> + + + app:layout_constraintStart_toEndOf="@+id/guideline" + android:layout_marginBottom="24dp" /> From cf2e7407344cf972ab488bfe933be05b9167d3be Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Aug 2021 13:39:26 +0300 Subject: [PATCH 03/55] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 415eb6e9cc..0cf3af21e6 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 415eb6e9ccfcdc3573391b869adeba433f790087 +Subproject commit 0cf3af21e6f441e6323a1ba127ee7518168797df From b3ae1d31acc23d7713798584eeed988293e652a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Aug 2021 12:27:47 +0300 Subject: [PATCH 04/55] Updated on 2026-08-14 --- .../com/tangem/tap/common/redux/AppState.kt | 2 +- .../tap/features/send/BaseStoreFragment.kt | 16 +------ .../send/redux/middlewares/SendMiddleware.kt | 48 ++++++++++--------- .../tap/features/send/ui/SendFragment.kt | 20 ++++++++ 4 files changed, 47 insertions(+), 39 deletions(-) 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 02deb7d980..8762583a80 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 @@ -39,7 +39,7 @@ data class AppState( logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, HomeMiddleware().homeMiddleware, WalletMiddleware().walletMiddleware, - sendMiddleware, + SendMiddleware().sendMiddleware, DetailsMiddleware().detailsMiddleware, DisclaimerMiddleware().disclaimerMiddleware, TokensMiddleware().tokensMiddleware, diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt index d7b8d2af90..5ad9b1147a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -4,12 +4,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar -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_wallet.* @@ -27,18 +24,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { 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) - ) - } - } - }) + val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) exitTransition = inflater.inflateTransition(R.transition.fade) 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 2c5b8ebd58..3b19952951 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 @@ -41,35 +41,37 @@ import java.util.* /** [REDACTED_AUTHOR] */ -val sendMiddleware: Middleware = { dispatch, appState -> - { nextDispatch -> - { action -> - when (action) { - is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch) - is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch) - is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch) - is SendActionUi.SendAmountToRecipient -> - verifyAndSendTransaction(action, appState(), dispatch) - is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch) - is SendAction.Warnings.Update -> updateWarnings(dispatch) - is SendActionUi.CheckIfTransactionDataWasProvided -> { - val transactionData = appState()?.sendState?.externalTransactionData - if (transactionData != null) { - store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress( - transactionData.destinationAddress, false - )) - store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO)) - store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount)) - store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(), - false)) +class SendMiddleware { + val sendMiddleware: Middleware = { dispatch, appState -> + { nextDispatch -> + { action -> + when (action) { + is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch) + is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch) + is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch) + is SendActionUi.SendAmountToRecipient -> + verifyAndSendTransaction(action, appState(), dispatch) + is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch) + is SendAction.Warnings.Update -> updateWarnings(dispatch) + is SendActionUi.CheckIfTransactionDataWasProvided -> { + val transactionData = appState()?.sendState?.externalTransactionData + if (transactionData != null) { + store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress( + transactionData.destinationAddress, false + )) + store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO)) + store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount)) + store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(), + false)) + } } } + nextDispatch(action) } - nextDispatch(action) } } -} +} private fun verifyAndSendTransaction( action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit, ) { 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 9736cc16e0..bb675cf3fe 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,6 +7,7 @@ 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 @@ -20,6 +21,7 @@ import com.tangem.tap.common.entities.TapCurrency import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.extensions.setOnImeActionListener import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity +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.* @@ -31,6 +33,7 @@ import com.tangem.tap.features.send.redux.FeeActionUi.* import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber +import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.mainScope @@ -60,6 +63,23 @@ 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) From 613b68b9730222b24849906620f7943fb8b8ac2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Aug 2021 15:55:13 +0300 Subject: [PATCH 05/55] Updated on 2026-08-14 --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 13b2127afa..4cc00a7e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,7 @@ local.properties .gradle # User-specific configurations -.idea/caches/ -.idea/libraries/ -.idea/*.xml -.idea/codeStyles +.idea # LayoutInspector files /captures From 935ae544e9a2cd613476706febdf7cec9b837fb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:25:26 +0300 Subject: [PATCH 06/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/extensions/Navigation.kt | 1 + .../features/wallet/ui/{dialogs => }/TwinsOnboardingFragment.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename app/src/main/java/com/tangem/tap/features/wallet/ui/{dialogs => }/TwinsOnboardingFragment.kt (98%) 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 d56ae0af85..77b292ac22 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 @@ -15,6 +15,7 @@ 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.tokens.ui.AddTokensFragment +import com.tangem.tap.features.wallet.ui.TwinsOnboardingFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment 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/TwinsOnboardingFragment.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt rename to app/src/main/java/com/tangem/tap/features/wallet/ui/TwinsOnboardingFragment.kt index a20b06aa72..0d3abe02e2 100644 --- 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/TwinsOnboardingFragment.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.wallet.ui.dialogs +package com.tangem.tap.features.wallet.ui import android.os.Bundle import android.view.View From 82dba6811abbf9611fbf2cd6c6ccf393bcc1d688 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:30:58 +0300 Subject: [PATCH 07/55] Updated on 2026-08-14 --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index e01cbb450b..9d12481f3f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.5.21', - build_gradle: '7.0.0', + kotlin : '1.5.30', + build_gradle: '7.0.1', ] From 7e39873b235ecc124f5fa38507eca2b25d093ed6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:34:14 +0300 Subject: [PATCH 08/55] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/Handler.kt | 26 ++++++++++++++++ .../com/tangem/tap/common/extensions/Toast.kt | 31 +++++++++++++++++++ .../com/tangem/tap/common/extensions/UI.kt | 21 +++++++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/Handler.kt create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/Toast.kt diff --git a/app/src/main/java/com/tangem/tap/common/Handler.kt b/app/src/main/java/com/tangem/tap/common/Handler.kt new file mode 100644 index 0000000000..f36d4d1541 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/Handler.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.common + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper + +private val uiHandler = Handler(Looper.getMainLooper()) +private val backgroundHandler = Handler(HandlerThread("AppMainHandlerThread").apply { start() }.looper) + +fun postUi(ms: Long = 0, func: Runnable) { + if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms) +} + +fun postBackground(ms: Long = 0, func: Runnable) { + if (ms == 0L) backgroundHandler.post { func.run() } else backgroundHandler.postDelayed(func, ms) +} + +fun post(ms: Long = 0, func: Runnable) { + if (ms == 0L) { + func.run() + } else { + val currentLooper = Looper.myLooper() ?: return + + Handler(currentLooper).postDelayed(func, ms) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt b/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt new file mode 100644 index 0000000000..23d969af40 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.common.extensions + +import android.content.Context +import android.view.View +import android.widget.Toast +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment + +fun Context.toast(message: String, length: Int = Toast.LENGTH_LONG) { + Toast.makeText(this, message, length).show() +} + +fun Context.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) { + Toast.makeText(this, this.getString(messageRes), length).show() +} + +fun View.toast(message: String, length: Int = Toast.LENGTH_LONG) { + context.toast(message, length) +} + +fun View.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) { + context.toast(context.getString(messageRes), length) +} + +fun Fragment.toast(message: String, length: Int = Toast.LENGTH_LONG) { + context?.toast(message, length) +} + +fun Fragment.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) { + context?.let { it.toast(it.getString(messageRes), length) } +} \ 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 d29b032ef3..e468ec2203 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 @@ -5,6 +5,7 @@ import android.content.* import android.content.res.Resources import android.graphics.drawable.Drawable import android.os.Build +import android.os.Bundle import android.util.TypedValue import android.view.View import android.view.ViewGroup @@ -16,6 +17,7 @@ import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.google.android.material.card.MaterialCardView +import com.tangem.common.extensions.VoidCallback fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? { return ContextCompat.getDrawable(requireContext(), drawableResId) @@ -76,8 +78,8 @@ fun Context.dpToPixels(dp: Int): Int = TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics ).toInt() -tailrec fun Context?.getActivity(): Activity? = this as? Activity ?: - (this as? ContextWrapper)?.baseContext?.getActivity() +tailrec fun Context?.getActivity(): Activity? = this as? Activity + ?: (this as? ContextWrapper)?.baseContext?.getActivity() fun MaterialCardView.setMargins( marginLeftDp: Int = 16, @@ -141,4 +143,19 @@ fun Context.shareText(text: String) { fun Fragment.shareText(text: String) { requireContext().shareText(text) +} + +fun Context.safeStartActivity( + intent: Intent, + options: Bundle? = null, + fallback: ((ActivityNotFoundException) -> Unit)? = null, + finally: VoidCallback? = null +) { + try { + this.startActivity(intent, options) + } catch (ex: ActivityNotFoundException) { + fallback?.invoke(ex) + } finally { + finally?.invoke() + } } \ No newline at end of file From 35ac528768c01dd5808b13ed37ec6d98781e7a86 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:37:07 +0300 Subject: [PATCH 09/55] Updated on 2026-08-14 --- .../com/tangem/tap/common/entities/Button.kt | 16 +++++++++++++++- .../tap/features/send/redux/SendScreenAction.kt | 4 ++-- .../send/redux/middlewares/SendMiddleware.kt | 6 +++--- .../send/redux/reducers/SendScreenReducer.kt | 5 ++++- .../tap/features/send/redux/states/SendState.kt | 7 ++++--- .../ui/stateSubscribers/SendStateSubscriber.kt | 16 ++-------------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt index 986a170564..3255e490ca 100644 --- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt @@ -1,3 +1,17 @@ package com.tangem.tap.common.entities -open class Button(val enabled: Boolean) \ No newline at end of file +import com.tangem.tap.common.toggleWidget.ProgressState +import com.tangem.tap.features.send.redux.states.ButtonState + +open class Button(val enabled: Boolean) + +open class IndeterminateProgressButton( + val state: ButtonState +) : Button(state != ButtonState.DISABLED) { + + val progressState: ProgressState + get() = when (state) { + ButtonState.PROGRESS -> ProgressState.Progress() + else -> ProgressState.None + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 921e29f667..6f20155539 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -11,7 +11,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.SendButtonState +import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -137,7 +137,7 @@ sealed class SendActionUi : SendScreenActionUi { sealed class SendAction : SendScreenAction { - data class ChangeSendButtonState(val state: SendButtonState) : SendAction() + data class ChangeSendButtonState(val state: ButtonState) : SendAction() object SendSuccess : SendAction(), ToastNotificationAction { override val messageResource: Int = R.string.send_transaction_success } 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 3b19952951..c6fb811fba 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 @@ -23,7 +23,7 @@ import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.SendButtonState +import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope @@ -121,7 +121,7 @@ private fun sendTransaction( externalTransactionData: ExternalTransactionData?, dispatch: (Action) -> Unit, ) { - dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS)) + dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS)) var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress) transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) } @@ -236,7 +236,7 @@ private fun sendTransaction( } } } - dispatch(SendAction.ChangeSendButtonState(SendButtonState.ENABLED)) + dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 58b36afe00..673f19cb66 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.tap.common.CurrencyConverter +import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.IdStateHolder @@ -41,7 +42,9 @@ class SendScreenReducer { private class SendReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { val result = when (action) { - is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state) + is SendAction.ChangeSendButtonState -> { + sendState.copy(sendButtonState = IndeterminateProgressButton(action.state)) + } is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action) is SendAction.Dialog.SendTransactionFails -> sendState.copy(dialog = action) is SendAction.Dialog.Hide -> sendState.copy(dialog = null) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index eed6fa793c..3f3d32df5e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.isZero import com.tangem.tap.common.CurrencyConverter +import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.entities.TapCurrency import com.tangem.tap.common.redux.global.StateDialog import com.tangem.tap.common.text.DecimalDigitsInputFilter @@ -39,7 +40,7 @@ data class SendState( val feeState: FeeState = FeeState(), val receiptState: ReceiptState = ReceiptState(), val sendWarningsList: List = listOf(), - val sendButtonState: SendButtonState = SendButtonState.DISABLED, + val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), val dialog: StateDialog? = null, val externalTransactionData: ExternalTransactionData? = null ) : SendScreenState { @@ -87,7 +88,7 @@ data class SendState( } } - fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED + fun getButtonState(): ButtonState = if (isReadyToSend()) ButtonState.ENABLED else ButtonState.DISABLED fun getTotalAmountToSend(value: BigDecimal = amountState.amountToSendCrypto): BigDecimal { val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded @@ -117,7 +118,7 @@ data class SendState( } } -enum class SendButtonState { +enum class ButtonState { ENABLED, DISABLED, PROGRESS } 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 05c7af7d18..39b4eefe8f 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 @@ -116,20 +116,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber } } - when (state.sendButtonState) { - SendButtonState.ENABLED -> { - fg.btnSend.isEnabled = true - sendFragment.sendBtn.changeState(ProgressState.None) - } - SendButtonState.DISABLED -> { - fg.btnSend.isEnabled = false - sendFragment.sendBtn.changeState(ProgressState.None) - } - SendButtonState.PROGRESS -> { - fg.btnSend.isEnabled = true - sendFragment.sendBtn.changeState(ProgressState.Progress()) - } - } + sendFragment.sendBtn.changeState(state.sendButtonState.progressState) + sendFragment.sendBtn.mainView.isEnabled = state.sendButtonState.enabled val rv = fg.rv_warning_messages val adapter = rv.adapter as? WarningMessagesAdapter ?: return From c938b9fbe3d5533536ffe71536bb53a0b23dc8a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:41:28 +0300 Subject: [PATCH 10/55] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 33 ++++++++++++++++--- .../redux/navigation/NavigationAction.kt | 19 +++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) 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 77b292ac22..b3657a070c 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,9 +1,12 @@ package com.tangem.tap.common.extensions +import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager +import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.redux.navigation.AppScreen +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 @@ -18,13 +21,26 @@ import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.TwinsOnboardingFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment 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) { +fun FragmentActivity.openFragment( + screen: AppScreen, + addToBackstack: Boolean, + fgShareTransition: FragmentShareTransition? = null +) { val transaction = this.supportFragmentManager.beginTransaction() - transaction.replace(R.id.fragment_container, fragmentFactory(screen), screen.name) - if (addToBackStack && screen != AppScreen.Home) transaction.addToBackStack(null) + val fragment = fragmentFactory(screen) + fgShareTransition?.apply { + fragment.sharedElementEnterTransition = enterTransitionSet + fragment.sharedElementReturnTransition = exitTransitionSet + shareElements.forEach { shareElement -> + shareElement.wView.get()?.let { view -> + transaction.addSharedElement(view, shareElement.name) + } + } + } + transaction.replace(R.id.fragment_container, fragment, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) transaction.commitAllowingStateLoss() } @@ -39,6 +55,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { return tag?.let { AppScreen.valueOf(tag) } } +fun FragmentActivity.addOnBackPressedDispatcher( + isEnabled: Boolean = true, + onBackPressed: VoidCallback +): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) { + override fun handleOnBackPressed() { + onBackPressed() + } +}).also { this.onBackPressedDispatcher.addCallback(it) } + private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() 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 6da57dfec8..9a4794b29d 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,12 +1,17 @@ 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 sealed class NavigationAction : Action { - data class NavigateTo(val screen: AppScreen, val addToBackstack: Boolean = true) : - NavigationAction() + data class NavigateTo( + val screen: AppScreen, + val addToBackstack: Boolean = true, + val fragmentShareTransition: FragmentShareTransition? = null + ) : NavigationAction() data class PopBackTo(val screen: AppScreen? = null) : NavigationAction() @@ -14,4 +19,12 @@ sealed class NavigationAction : Action { data class ActivityCreated(val activity: WeakReference) : NavigationAction() object ActivityDestroyed : NavigationAction() -} \ No newline at end of file +} + +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 From 3545b4574362a66eb813e706d0c861bff60f4782 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:45:12 +0300 Subject: [PATCH 11/55] Updated on 2026-08-14 --- .../tap/common/redux/navigation/NavigationMiddleware.kt | 6 +++++- .../toggleWidget/IndeterminateProgressButtonWidget.kt | 2 ++ .../com/tangem/tap/common/toggleWidget/ViewStateWidget.kt | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 43c8ed406e..31fe730615 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -13,7 +13,11 @@ val navigationMiddleware: Middleware = { dispatch, state -> val navState = state()?.navigationState when (action) { is NavigationAction.NavigateTo -> { - navState?.activity?.get()?.openFragment(action.screen, action.addToBackstack) + navState?.activity?.get()?.openFragment( + action.screen, + action.addToBackstack, + action.fragmentShareTransition + ) } is NavigationAction.PopBackTo -> { if (action.screen == AppScreen.Home) { diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt index c6cb5f22bd..153f7c662d 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt @@ -23,6 +23,8 @@ open class IndeterminateProgressButtonWidget( changeState(initialState) } + override val mainView: View = button + override fun changeState(state: WidgetState) { val progressState = state as? ProgressState ?: return diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt index 246268de7a..0c06410ace 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.toggleWidget +import android.view.View + /** [REDACTED_AUTHOR] @@ -7,6 +9,7 @@ package com.tangem.tap.common.toggleWidget interface WidgetState interface ViewStateWidget { + val mainView: View fun changeState(state: WidgetState) } From 91493bc01bb466d370484121dc946079d3a49bae Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Sep 2021 21:52:52 +0300 Subject: [PATCH 12/55] Updated on 2026-08-14 --- .../com/tangem/tap/domain/extensions/Card.kt | 2 ++ .../tap/domain/tasks/CreateWalletsTask.kt | 18 +++++++----------- .../tangem/tap/domain/tasks/ScanNoteTask.kt | 13 +++++-------- .../tap/domain/twins/TwinCardsManager.kt | 6 +++--- .../tap/features/send/BaseStoreFragment.kt | 2 +- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index be6eb20dcb..aeef948edf 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -7,6 +7,8 @@ fun Card.getSingleWallet(): CardWallet? { return wallets.firstOrNull() } +fun Card.hasWallets(): Boolean = wallets.isNotEmpty() + fun Card.hasSignedHashes(): Boolean { return wallets.any { it.totalSignedHashes ?: 0 > 0 } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt index 1443371141..e185b62b4c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -9,19 +9,15 @@ import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.wallet.CreateWalletCommand -class CreateWalletsTask(curves: List = emptyList()) : CardSessionRunnable { +class CreateWalletsTask(curves: List? = null) : CardSessionRunnable { - private val curves = if (curves.isEmpty()) { - listOf( - EllipticCurve.Secp256k1, - EllipticCurve.Ed25519, - EllipticCurve.Secp256r1, - ) - } else { - curves - } + private val curves = curves ?: listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + ) - var index = 0 + private var index = 0 override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val curve = curves[index] 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 ca90ead2c5..9f31169566 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 @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.CompletionResult import com.tangem.common.card.Card -import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.card.WalletData import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable @@ -18,13 +18,11 @@ import com.tangem.operations.ScanTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.TapWorkarounds.isExcluded -import com.tangem.tap.domain.TapWorkarounds.isMultiCurrencyWallet import com.tangem.tap.domain.TapWorkarounds.noteCurrency import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.store -import com.tangem.wallet.R data class ScanNoteResponse( val card: Card, @@ -71,7 +69,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable= 4) { + } else if (card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) { createMissingWalletsIfNeeded(card, session, callback) } else { callback(CompletionResult.Success( @@ -88,14 +86,13 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable Date: Thu, 23 Sep 2021 10:49:04 +0300 Subject: [PATCH 13/55] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 9d12481f3f..43b263845b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ kotlin : '1.5.30', - build_gradle: '7.0.1', + build_gradle: '7.0.2', ] From 5eb8b71a1f3526c1b36836688d590e1a177d46a6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 11:28:43 +0300 Subject: [PATCH 14/55] Updated on 2026-08-14 --- app/build.gradle | 4 +- .../tap/common/extensions/Collections.kt | 7 ++ .../common/redux/NotificationsMiddleware.kt | 45 +++++--- .../com/tangem/tap/domain/TangemSdkManager.kt | 22 ++-- .../tangem/tap/domain/TapBlockchainConfig.kt | 13 --- .../java/com/tangem/tap/domain/TapErrors.kt | 5 +- .../com/tangem/tap/domain/TapWalletManager.kt | 10 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 46 +++----- .../com/tangem/tap/domain/UrlBitmapLoader.kt | 38 +++++++ .../com/tangem/tap/domain/extensions/Card.kt | 18 +++ .../domain/extensions/WalletManagerFactory.kt | 10 +- .../tangem/tap/domain/tasks/ScanNoteTask.kt | 33 ++---- .../tangem/tap/domain/twins/TwinsHelper.kt | 2 +- .../features/details/redux/DetailsReducer.kt | 14 +-- .../features/details/ui/DetailsFragment.kt | 8 +- .../wallet/models/PendingTransaction.kt | 9 ++ .../redux/middlewares/WarningsMiddleware.kt | 12 +- .../wallet/redux/reducers/WalletReducer.kt | 26 +---- .../tap/persistence/PreferencesStorage.kt | 21 ++-- .../tap/persistence/UsedCardsPrefStorage.kt | 107 ++++++++++++++++++ .../main/res/values/strings_untranslated.xml | 26 +++++ 21 files changed, 316 insertions(+), 160 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt create mode 100644 app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt diff --git a/app/build.gradle b/app/build.gradle index a1a8cda618..923c2c5326 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,8 +77,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'com.tangem:blockchain:develop-38' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-70' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-70' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-78' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-78' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt index d1c417e842..b264485c44 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt @@ -14,4 +14,11 @@ fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean { val toRemove = this.filter(predicate) this.removeAll(toRemove) return toRemove.isNotEmpty() +} + +fun MutableList.replaceBy(item: T, predicate: (T) -> Boolean) { + val toRemove = this.filter(predicate) + val indexes = toRemove.map { indexOf(it) } + this.removeAll(toRemove) + indexes.forEach { this.add(it, item) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index 8a55bcfafe..c797764bbd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.tap.domain.MultiMessageError import com.tangem.tap.domain.TapError import com.tangem.tap.domain.assembleErrors import com.tangem.tap.notificationsHandler +import com.tangem.wallet.BuildConfig import org.rekotlin.Action import org.rekotlin.Middleware import java.lang.ref.WeakReference @@ -63,23 +64,29 @@ fun getMessageString(context: Context, message: Int, args: List?): String { val notificationsMiddleware: Middleware = { dispatch, state -> { next -> { action -> - when (action) { - is NotificationAction -> notificationsHandler?.showNotification(action.messageResource) - is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource) - is ErrorAction -> { - when (action.error) { - is MultiMessageError -> { - val multiError = action.error as MultiMessageError - notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder) - } - else -> { - val args = (action.error as? ArgError)?.args ?: listOf() - notificationsHandler?.showNotification(action.error.localizedMessage, args) - } - } + handleNotificationAction(action) + next(action) + } + } +} + +private fun handleNotificationAction(action: Action) { + if (action is Debug && !BuildConfig.DEBUG) return + + when (action) { + is NotificationAction -> notificationsHandler?.showNotification(action.messageResource) + is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource) + is ErrorAction -> { + when (action.error) { + is MultiMessageError -> { + val multiError = action.error as MultiMessageError + notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder) + } + else -> { + val args = (action.error as? ArgError)?.args ?: listOf() + notificationsHandler?.showNotification(action.error.localizedMessage, args) } } - next(action) } } } @@ -94,4 +101,10 @@ interface NotificationAction : Action { interface ErrorAction : Action { val error: TapError -} \ No newline at end of file +} + +// Processed only in the debug builds +interface Debug +interface DebugNotification : Debug, NotificationAction +interface DebugToastNotification : Debug, ToastNotificationAction +interface DebugErrorAction : Debug, ErrorAction \ 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 56fe47fb45..adc0404f24 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -22,7 +22,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -35,16 +35,22 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co analyticsHandler: AnalyticsHandler, messageRes: Int? = null, ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) - val result = runTaskAsyncReturnOnMain(ScanNoteTask(), - initialMessage = Message( - context.getString(messageRes ?: R.string.initial_message_scan_header) - )) - if (result is CompletionResult.Failure) { + + return runTaskAsyncReturnOnMain( + ScanNoteTask(), + initialMessage = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) + ).also { sendScanFailuresToAnalytics(analyticsHandler, it) } + } + + private fun sendScanFailuresToAnalytics( + analyticsHandler: AnalyticsHandler, + result: CompletionResult + ) { + if (result is CompletionResult.Failure && result.error is TangemSdkError) { (result.error as? TangemSdkError)?.let { error -> analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan) } } - return result } suspend fun createWallet(cardId: String?): CompletionResult { @@ -106,7 +112,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } fun changeDisplayedCardIdNumbersCount(card: Card) { - tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null + tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTangemTwin()) 4 else null } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt deleted file mode 100644 index b9ec4705a5..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.tap.domain - -class TapBlockchainConfig { - companion object { - val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT" - val blockcypherTokens: Set = setOf( - "aa8184b0e0894b88a5688e01b3dc1e82", - "56c4ca23c6484c8f8864c32fde4def8d", - "66a8a37c5e9d4d2c9bb191acfe7f93aa" - ) - val infuraProjectId: String = "613a0b14833145968b1f656240c7d245" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index ca57897238..8ad394c14f 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -21,6 +21,7 @@ sealed class TapError( ) : Throwable(), TapErrors, ArgError { object UnknownError : TapError(R.string.send_error_unknown) + object ScanCardError : TapError(R.string.scan_card_error) data class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created) object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message) @@ -38,8 +39,8 @@ sealed class TapError( object DustChange : TapError(R.string.send_error_dust_change) data class CreateAccountUnderfunded(override val args: List) : TapError(R.string.send_error_no_target_account) - sealed class XmlError { - object AssetAccountNotCreated: TapError(R.string.send_error_no_account_xlm) + sealed class XmlError { + object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm) } data class ValidateTransactionErrors( 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 466e463f19..0193a9d2a1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -15,7 +15,7 @@ import com.tangem.tap.domain.extensions.* import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.domain.twins.TwinsHelper -import com.tangem.tap.domain.twins.isTwinCard +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 @@ -98,7 +98,7 @@ 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.isTwinCard()) { + if (data.card.isTangemTwin()) { val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId) val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId) if (secondCardId != null && cardNumber != null) { @@ -143,7 +143,7 @@ class TapWalletManager { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) } data.card.wallets.isEmpty() || - (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { store.dispatch(WalletAction.EmptyWallet) } else -> { @@ -152,7 +152,7 @@ class TapWalletManager { val blockchain = data.getBlockchain() val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) - if (blockchain != null && primaryWalletManager != null) { + if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { val primaryToken = data.getPrimaryToken() store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) @@ -236,7 +236,7 @@ class TapWalletManager { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) } data.card.wallets.isEmpty() || - (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { store.dispatch(WalletAction.EmptyWallet) } 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 8af0e99415..ffc138b170 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -4,10 +4,10 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion -import com.tangem.tap.domain.TapWorkarounds.isNote import com.tangem.tap.domain.TapWorkarounds.isStart2Coin +import com.tangem.tap.domain.TapWorkarounds.isTangemNote import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin import java.util.* object TapWorkarounds { @@ -28,26 +28,10 @@ object TapWorkarounds { return excludedBatch || excludedIssuerName } - fun Card.isNote(): Boolean { - return notesBatches.contains(batchId) - } + fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) + fun Card.isTangemWallet(): Boolean = tangemWalletBatches.contains(batchId) - fun Card.isMultiCurrencyWallet(): Boolean { - return multiCurrencyWalletsBatches.contains(batchId) - } - - val Card.noteCurrency: Blockchain? - get() { - return when (batchId) { - "AB01" -> Blockchain.Bitcoin - "AB02" -> Blockchain.Ethereum - "AB03" -> Blockchain.CardanoShelley - "AB04" -> Blockchain.Dogecoin - "AB05" -> Blockchain.Binance - "AB06" -> Blockchain.XRP - else -> null - } - } + fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId] private const val START_2_COIN_ISSUER = "start2coin" private const val TEST_CARD_BATCH = "99FF" @@ -64,21 +48,21 @@ object TapWorkarounds { "TTM BANK" ) - private val notesBatches = listOf( - "AB01", - "AB02", - "AB03", - "AB04", - "AB05", - "AB06", - ) + private val tangemWalletBatches = listOf("AC01") - private val multiCurrencyWalletsBatches = listOf("AC01") + private val tangemNoteBatches = mapOf( + "AB01" to Blockchain.Bitcoin, + "AB02" to Blockchain.Ethereum, + "AB03" to Blockchain.CardanoShelley, + "AB04" to Blockchain.Dogecoin, + "AB05" to Blockchain.Binance, + "AB06" to Blockchain.XRP, + ) } val Card.isMultiwalletAllowed: Boolean get() { - return !isTwinCard() && !isStart2Coin && !isNote() + return !isTangemTwin() && !isStart2Coin && !isTangemNote() && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || getSingleWallet()?.curve == EllipticCurve.Secp256k1) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt new file mode 100644 index 0000000000..d09d8ed3bc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.domain + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target +import com.tangem.common.services.Result + +/** +[REDACTED_AUTHOR] + */ +class UrlBitmapLoader { + + fun loadBitmap(url: String, callback: (Result) -> Unit) { + Picasso.get().load(url).into(DownloadTarget(callback)) + } + + fun loadBitmap(url: String, target: DownloadTarget) { + Picasso.get().load(url).into(target) + } +} + +// It adds the ability to trigger multiple downloads with a unique callback. +open class DownloadTarget( + val callback: (Result) -> Unit, +) : Target { + + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + callback(Result.Success(bitmap)) + } + + override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) { + callback.invoke(Result.Failure(e)) + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable?) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index aeef948edf..dc3fc1a618 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -2,6 +2,9 @@ package com.tangem.tap.domain.extensions import com.tangem.common.card.Card import com.tangem.common.card.CardWallet +import com.tangem.common.extensions.toHexString +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.features.wallet.redux.Artwork fun Card.getSingleWallet(): CardWallet? { return wallets.firstOrNull() @@ -9,6 +12,10 @@ fun Card.getSingleWallet(): CardWallet? { fun Card.hasWallets(): Boolean = wallets.isNotEmpty() +fun Card.hasNoWallets(): Boolean = wallets.isEmpty() + +fun Card.hasSingleWallet(): Boolean = wallets.size == 1 + fun Card.hasSignedHashes(): Boolean { return wallets.any { it.totalSignedHashes ?: 0 > 0 } } @@ -17,6 +24,17 @@ fun Card.signedHashesCount(): Int { return wallets.map { it.totalSignedHashes ?: 0 }.sum() } +fun Card.getArtworkUrl(artworkId: String?): String? { + return when { + artworkId != null -> { + OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> null + } +} + val Card.remainingSignatures: Int? get() = this.getSingleWallet()?.remainingSignatures 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 5ec3f1afa9..0ab0ec0293 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,7 @@ 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.ScanNoteResponse -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin fun WalletManagerFactory.makeWalletManagerForApp( card: Card, @@ -54,16 +54,18 @@ fun WalletManagerFactory.makePrimaryWalletManager( ): WalletManager? { val card = data.card val blockchain = if (card.isTestCard) { - data.getBlockchain()?.getTestnetVersion() + data.getBlockchain().getTestnetVersion() } else { data.getBlockchain() } val supportedCurves = blockchain?.getSupportedCurves() ?: return null + val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) } val wallet = selectWallet(wallets) val publicKey = wallet?.publicKey ?: return null - val curveToUse = wallet.curve ?: return null - return if (card.isTwinCard() && data.secondTwinPublicKey != null) { + val curveToUse = wallet.curve + + return if (card.isTangemTwin() && 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 index 9f31169566..510d2785d8 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 @@ -2,9 +2,7 @@ package com.tangem.tap.domain.tasks import com.tangem.* import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion @@ -17,12 +15,12 @@ import com.tangem.operations.CommandResponse import com.tangem.operations.ScanTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError +import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain import com.tangem.tap.domain.TapWorkarounds.isExcluded -import com.tangem.tap.domain.TapWorkarounds.noteCurrency +import com.tangem.tap.domain.TapWorkarounds.isTangemNote import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.domain.twins.isTwinCard -import com.tangem.tap.store +import com.tangem.tap.domain.twins.isTangemTwin data class ScanNoteResponse( val card: Card, @@ -30,9 +28,9 @@ data class ScanNoteResponse( val secondTwinPublicKey: String? = null, ) : CommandResponse { - fun getBlockchain(): Blockchain? { - if (card.noteCurrency != null) return card.noteCurrency - val blockchainName: String = walletData?.blockchain ?: return null + fun getBlockchain(): Blockchain { + if (card.isTangemNote()) return card.getTangemNoteBlockchain() ?: return Blockchain.Unknown + val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown return Blockchain.fromId(blockchainName) } @@ -67,7 +65,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable= FirmwareVersion.MultiWalletAvailable) { createMissingWalletsIfNeeded(card, session, callback) @@ -146,20 +144,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { - val notAllowedByCard = - state.card?.settings?.isPermanentWallet == true - || state.card?.isWalletDataSupported == true + val notAllowedByAnyWallet = state.card?.wallets?.any { it.settings.isPermanent } ?: false + val notAllowedByCard = notAllowedByAnyWallet || state.card?.isWalletDataSupported == true val notEmpty = state.wallets.any { - !it.recentTransactions.toPendingTransactions(it.address).isNullOrEmpty() - || it.amounts.toSendableAmounts().isNotEmpty() + it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty() } val eraseWalletState = when { notAllowedByCard -> EraseWalletState.NotAllowedByCard 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 55f9711482..640cc47cf0 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 @@ -12,7 +12,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.twins.getTwinCardIdForUser -import com.tangem.tap.domain.twins.isTwinCard +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 @@ -68,7 +68,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber.toPendingTransactionsForToken(token: Token, walletAddress: String): List { return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) } +} + +fun Wallet.getPendingTransactions(): List { + return recentTransactions.toPendingTransactions(address) +} + +fun Wallet.hasPendingTransactions(): Boolean { + return recentTransactions.toPendingTransactions(address).isNotEmpty() } \ No newline at end of file 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 d3a6c2d046..d69b213ece 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.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.preferencesStorage @@ -41,7 +41,7 @@ class WarningsMiddleware { is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline() is WalletAction.Warnings.CheckHashesCount.SaveCardId -> { val cardId = globalState?.scanNoteResponse?.card?.cardId - cardId?.let { preferencesStorage.saveScannedCardId(it) } + cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) } } is WalletAction.Warnings.AppRating.RemindLater -> { preferencesStorage.appRatingLaunchObserver.applyDelayedShowing() @@ -91,7 +91,7 @@ class WarningsMiddleware { showWarningLowRemainingSignaturesIfNeeded(card) if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) { addWarningMessage(WarningMessagesManager.devCardWarning()) - } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { + } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) } } if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { @@ -119,7 +119,7 @@ class WarningsMiddleware { private fun checkIfWarningNeeded( card: Card, ): WarningMessage? { - if (card.isTwinCard()) return null + if (card.isTangemTwin()) return null if (card.isMultiwalletAllowed) { return if (card.hasSignedHashes()) { @@ -150,9 +150,9 @@ class WarningsMiddleware { if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return val card = store.state.globalState.scanNoteResponse?.card - if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return + if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return - if (card.isTwinCard() || card.isMultiwalletAllowed) return + if (card.isTangemTwin() || 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/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 9bdef4b340..8c6db9d9a2 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 @@ -2,12 +2,11 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.toHexString -import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.redux.* @@ -215,29 +214,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.LoadFiatRate.Success -> newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState) is WalletAction.LoadArtwork -> { - val cardId = action.card.cardId - val cardPublicKey = action.card.cardPublicKey.toHexString() - val artworkUrl = when { - action.artworkId != null -> { - OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey, action.artworkId) - } - action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID) -> { - Artwork.SERGIO_CARD_URL - } - action.card.cardId.startsWith(Artwork.MARTA_CARD_ID) -> { - Artwork.MARTA_CARD_URL - } - newState.twinCardsState?.cardNumber != null -> { - when (newState.twinCardsState?.cardNumber) { + val artworkUrl = action.card.getArtworkUrl(action.artworkId) + ?: when (newState.twinCardsState?.cardNumber) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 - null -> Artwork.DEFAULT_IMG_URL + else -> Artwork.DEFAULT_IMG_URL } - } - else -> { - Artwork.DEFAULT_IMG_URL - } - } newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) } is WalletAction.ShowDialog.QrCode -> { 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 871e3eb3d5..77c8be322d 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -19,10 +19,13 @@ class PreferencesStorage(applicationContext: Application) { private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) val appRatingLaunchObserver: AppRatingLaunchObserver + val usedCardsPrefStorage: UsedCardsPrefStorage init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences) + usedCardsPrefStorage.migrate() } private val fiatCurrenciesAdapter: JsonAdapter> by lazy { @@ -54,20 +57,11 @@ class PreferencesStorage(applicationContext: Application) { fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) - fun saveScannedCardId(cardId: String) { - val scannedCardsIds: String = restoreScannedCardIds() - if (!scannedCardsIds.contains(cardId)) { - preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply() - } - } - + @Deprecated("Use UsedCardsPrefStorage instead") fun wasCardScannedBefore(cardId: String): Boolean { - return restoreScannedCardIds().contains(cardId) + return usedCardsPrefStorage.wasScanned(cardId) } - private fun restoreScannedCardIds(): String = - preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: "" - fun saveDisclaimerAccepted() { preferences.edit().putBoolean(DISCLAIMER_ACCEPTED_KEY, true).apply() } @@ -93,7 +87,6 @@ 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 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" @@ -102,8 +95,8 @@ class PreferencesStorage(applicationContext: Application) { } class AppRatingLaunchObserver( - private val preferences: SharedPreferences, - private val launchCounts: Int, + private val preferences: SharedPreferences, + private val launchCounts: Int, ) { private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" private val K_FUNDS_FOUND_DATE = "fundsFoundDate" diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt new file mode 100644 index 0000000000..b6ebfd8a32 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -0,0 +1,107 @@ +package com.tangem.tap.persistence + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.tap.common.extensions.replaceBy +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +class UsedCardsPrefStorage( + private val preferences: SharedPreferences, +) { + + private val jsonConverter = MoshiJsonConverter.INSTANCE + + internal fun migrate() { + val scannedIds = preferences.getString(SCANNED_CARDS_IDS_KEY, null) ?: return + + val usedCardsInfo = scannedIds.split(",").map { UsedCardInfo(it.trim(), true) } + if (save(usedCardsInfo.toMutableList())) { + preferences.edit { this.remove(SCANNED_CARDS_IDS_KEY) } + } + } + + fun scanned(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true) + ?: UsedCardInfo(cardId, true) + + save(foundItem, restoredList) + } + + fun wasScanned(cardId: String): Boolean { + return findCardInfo(cardId)?.isScanned ?: false + } + + fun activationStarted(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true) + ?: UsedCardInfo(cardId, isActivationStarted = true) + + save(foundItem, restoredList) + } + + fun activationIsStarted(cardId: String): Boolean { + return findCardInfo(cardId)?.isActivationStarted ?: false + } + + fun activated(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivated = true) + ?: UsedCardInfo(cardId, isActivated = true) + + save(foundItem, restoredList) + } + + fun wasActivated(cardId: String): Boolean { + return findCardInfo(cardId)?.isActivated ?: false + } + + private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? { + val findInList = list ?: restore() + return findInList.firstOrNull { it.cardId == cardId } + } + + private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList) { + val info = usedCardInfo ?: return + + usedCardsInfo.replaceBy(info) { it.cardId == info.cardId } + save(usedCardsInfo) + } + + private fun save(list: MutableList): Boolean { + return try { + val json = jsonConverter.toJson(list) + preferences.edit { putString(USED_CARDS_INFO, json) } + true + } catch (ex: Exception) { + Timber.e(ex) + false + } + } + + private fun restore(): MutableList { + val json = preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf() + return try { + jsonConverter.fromJson(json)!! + } catch (ex: Exception) { + preferences.edit(true) { remove(USED_CARDS_INFO) } + mutableListOf() + } + } + + companion object { + private const val USED_CARDS_INFO = "usedCardsInfo" + private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" + } +} + +private data class UsedCardInfo( + val cardId: String, + val isScanned: Boolean = false, + val isActivationStarted: Boolean = false, + val isActivated: Boolean = false, +) \ 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 9404210575..888918f16b 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -1,4 +1,6 @@ + Scan card failed. Try again + Buy Sell Trade @@ -168,4 +170,28 @@ this wallet. Do you want to buy or sell crypto? Oops, the current version of the application is not ready to work with this card, please check for updates. + + + Welcome to Tangem + The safest way to buy, use and\n store cryptocurrency + Get a new card + + Activating card + What does it mean? + Create a wallet + Let’s generate all the keys on your card and create a secure wallet + Create wallet + + Top up your wallet + To get started, simply top up the card with any amount + Buy crypto + Show the wallet’s address + Scan the address to top up your wallet + + Done! + Your crypto card is activated and ready to be used + Continue + + Internal error: can\'t create wallet manager + From 99f1b91baeb5bc7a6dfde1c8f2ff38d2b99f24a6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 11:57:44 +0300 Subject: [PATCH 15/55] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/common/redux/AppState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8762583a80..79400725e3 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 @@ -12,7 +12,7 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware import com.tangem.tap.features.disclaimer.redux.DisclaimerState import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.home.redux.HomeState -import com.tangem.tap.features.send.redux.middlewares.sendMiddleware +import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState From a7fc5e10363b2e0788888c6a3a9326db949f13ba Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 12:49:34 +0300 Subject: [PATCH 16/55] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 10 +- .../com/tangem/tap/common/redux/AppReducer.kt | 6 + .../com/tangem/tap/common/redux/AppState.kt | 19 +- .../tap/common/redux/global/GlobalAction.kt | 11 ++ .../common/redux/global/GlobalMidlleware.kt | 27 ++- .../redux/navigation/NavigationAction.kt | 15 +- .../redux/navigation/NavigationState.kt | 15 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 17 +- .../tangem/tap/features/home/HomeFragment.kt | 77 +++++--- .../tap/features/home/redux/HomeAction.kt | 16 +- .../tap/features/home/redux/HomeMiddleware.kt | 126 ++++++------- .../tap/features/home/redux/HomeReducer.kt | 21 ++- .../tap/features/home/redux/HomeState.kt | 14 +- .../features/onboarding/OnboardingService.kt | 168 +++++++++++++++++ .../products/note/OnboardingNoteFragment.kt | 133 ++++++++++++++ .../note/redux/OnboardingNoteAction.kt | 16 ++ .../note/redux/OnboardingNoteMiddleware.kt | 72 ++++++++ .../note/redux/OnboardingNoteReducer.kt | 38 ++++ .../note/redux/OnboardingNoteState.kt | 18 ++ .../old/OnboardingOtherCardsFragment.kt | 50 ++++++ .../wallet/OnboardingWalletFragment.kt | 52 ++++++ .../wallet/redux/OnboardingWalletAction.kt | 19 ++ .../redux/OnboardingWalletMiddleware.kt | 18 ++ .../wallet/redux/OnboardingWalletReducer.kt | 20 +++ .../wallet/redux/OnboardingWalletState.kt | 15 ++ .../onboarding/redux/OnboardingAction.kt | 22 +++ .../onboarding/redux/OnboardingMiddleware.kt | 22 +++ .../onboarding/redux/OnboardingReducer.kt | 24 +++ .../onboarding/redux/OnboardingState.kt | 11 ++ .../onboarding/redux/StateSupportClasses.kt | 26 +++ ...Fragment.kt => OnboardingTwinsFragment.kt} | 2 +- .../wallet/ui/wallet/SingleWalletView.kt | 2 +- app/src/main/res/drawable/bg_circle_gray.xml | 9 + .../main/res/drawable/ic_home_card_back.xml | 108 +++++++++++ .../main/res/drawable/ic_home_card_front.xml | 126 +++++++++++++ app/src/main/res/layout/fragment_home.xml | 169 +++++++++++------- .../main/res/layout/fragment_onboarding.xml | 161 +++++++++++++++++ .../res/layout/fragment_onboarding_wallet.xml | 6 + 38 files changed, 1486 insertions(+), 195 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt rename app/src/main/java/com/tangem/tap/features/wallet/ui/{TwinsOnboardingFragment.kt => OnboardingTwinsFragment.kt} (97%) create mode 100644 app/src/main/res/drawable/bg_circle_gray.xml create mode 100644 app/src/main/res/drawable/ic_home_card_back.xml create mode 100644 app/src/main/res/drawable/ic_home_card_front.xml create mode 100644 app/src/main/res/layout/fragment_onboarding.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_wallet.xml 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 b3657a070c..c7e04cf44f 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 @@ -16,9 +16,12 @@ 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.old.OnboardingOtherCardsFragment +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.TwinsOnboardingFragment +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 @@ -67,6 +70,10 @@ fun FragmentActivity.addOnBackPressedDispatcher( private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() + AppScreen.OnboardingNote -> OnboardingNoteFragment() + AppScreen.OnboardingWallet -> OnboardingWalletFragment() + AppScreen.OnboardingTwins -> OnboardingTwinsFragment() + AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() @@ -75,7 +82,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Disclaimer -> DisclaimerFragment() AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment() AppScreen.CreateTwinWallet -> CreateTwinWalletFragment() - AppScreen.TwinsOnboarding -> TwinsOnboardingFragment() AppScreen.AddTokens -> AddTokensFragment() AppScreen.WalletDetails -> WalletDetailsFragment() AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment() 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 57ed1eaf2c..e60b9cade1 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 @@ -6,6 +6,9 @@ import com.tangem.tap.features.details.redux.DetailsReducer import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer 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.wallet.redux.OnboardingWalletReducer +import com.tangem.tap.features.onboarding.redux.OnboardingReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer @@ -19,6 +22,9 @@ fun appReducer(action: Action, state: AppState?): AppState { navigationState = NavigationReducer.reduce(action, state), globalState = globalReducer(action, state), homeState = HomeReducer.reduce(action, state), + onboardingState = OnboardingReducer.reduce(action, state), + onboardingNoteState = OnboardingNoteReducer.reduce(action, state), + onboardingWalletState = OnboardingWalletReducer.reduce(action, state), walletState = WalletReducer.reduce(action, state), sendState = SendScreenReducer.reduce(action, state.sendState), detailsState = DetailsReducer.reduce(action, state), 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 79400725e3..985a2a75ba 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 @@ -1,7 +1,7 @@ package com.tangem.tap.common.redux +import com.tangem.tap.common.redux.global.GlobalMiddleware import com.tangem.tap.common.redux.global.GlobalState -import com.tangem.tap.common.redux.global.globalMiddleware import com.tangem.tap.common.redux.navigation.NavigationState import com.tangem.tap.common.redux.navigation.navigationMiddleware import com.tangem.tap.features.details.redux.DetailsMiddleware @@ -12,6 +12,12 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware import com.tangem.tap.features.disclaimer.redux.DisclaimerState import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState +import com.tangem.tap.features.onboarding.redux.OnboardingMiddleware +import com.tangem.tap.features.onboarding.redux.OnboardingState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.tokens.redux.TokensMiddleware @@ -25,6 +31,9 @@ data class AppState( val navigationState: NavigationState = NavigationState(), val globalState: GlobalState = GlobalState(), val homeState: HomeState = HomeState(), + val onboardingState: OnboardingState = OnboardingState(), + val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(), + val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(), val walletState: WalletState = WalletState(), val sendState: SendState = SendState(), val detailsState: DetailsState = DetailsState(), @@ -36,8 +45,12 @@ data class AppState( companion object { fun getMiddleware(): List> { return listOf( - logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, - HomeMiddleware().homeMiddleware, + logMiddleware, navigationMiddleware, notificationsMiddleware, + GlobalMiddleware.handler, + HomeMiddleware.handler, + OnboardingMiddleware.handler, + OnboardingNoteMiddleware.handler, + OnboardingWalletMiddleware.handler, WalletMiddleware().walletMiddleware, SendMiddleware().sendMiddleware, DetailsMiddleware().detailsMiddleware, 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 c1c9137aec..928cb7b341 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 @@ -2,6 +2,9 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.tap.common.redux.DebugErrorAction +import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager @@ -14,6 +17,14 @@ import org.rekotlin.Action sealed class GlobalAction : Action { + data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction + + data class ReadCard( + val onSuccess: (suspend (ScanNoteResponse) -> Unit)? = null, + val onFailure: (suspend (TangemError) -> Unit)? = null, + val messageResId: Int? = null, + ) : GlobalAction() + object ScanFailsCounter { data class ChooseBehavior(val result: CompletionResult) : GlobalAction() object Reset : 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 d90e263263..115329af90 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 @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global 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.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager @@ -13,10 +14,19 @@ import com.tangem.tap.network.moonpay.MoonpayService import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.rekotlin.Middleware -val globalMiddleware: Middleware = { dispatch, appState -> +class GlobalMiddleware { + companion object { + val handler = globalMiddlewareHandler + } +} + +private val globalMiddlewareHandler: Middleware = { dispatch, appState -> { nextDispatch -> { action -> when (action) { @@ -78,6 +88,21 @@ val globalMiddleware: Middleware = { dispatch, appState -> } } } + is GlobalAction.ReadCard -> { + scope.launch { + val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler, action.messageResId) + withContext(Dispatchers.Main) { + store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + when (result) { + is CompletionResult.Success -> { + tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) + action.onSuccess?.invoke(result.data) + } + is CompletionResult.Failure -> action.onFailure?.invoke(result.error) + } + } + } + } } nextDispatch(action) } 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 9a4794b29d..375b16e785 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 @@ -2,6 +2,9 @@ package com.tangem.tap.common.redux.navigation import android.view.View import androidx.fragment.app.FragmentActivity +import androidx.transition.ChangeBounds +import androidx.transition.ChangeImageTransform +import androidx.transition.ChangeTransform import androidx.transition.TransitionSet import org.rekotlin.Action import java.lang.ref.WeakReference @@ -27,4 +30,14 @@ data class FragmentShareTransition( val exitTransitionSet: TransitionSet ) -data class ShareElement(val wView: WeakReference, val name: String) \ No newline at end of file +data class ShareElement(val wView: WeakReference, val name: String) + +//TODO: remove transition example +class DetailsTransition : TransitionSet() { + init { + ordering = ORDERING_TOGETHER; + addTransition(ChangeBounds()) + .addTransition(ChangeTransform()) + .addTransition(ChangeImageTransform()) + } +} \ No newline at end of file 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 ed59b9b2d5..4fa02150f0 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,12 +5,19 @@ 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 { - Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer, - CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens, WalletConnectSessions, + Home, + Disclaimer, + OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther, + Wallet, WalletDetails, + Send, + Details, DetailsConfirm, DetailsSecurity, + CreateTwinWalletWarning, CreateTwinWallet, + AddTokens, + WalletConnectSessions, QrScan } \ 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 066b2b83db..12a1cfaccd 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 @@ -1,8 +1,9 @@ package com.tangem.tap.features.disclaimer.redux +import com.tangem.tap.common.post import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.preferencesStorage import com.tangem.tap.store import org.rekotlin.Middleware @@ -14,16 +15,10 @@ class DisclaimerMiddleware { when (action) { is DisclaimerAction.AcceptDisclaimer -> { preferencesStorage.saveDisclaimerAccepted() - 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)) - } + store.dispatch(NavigationAction.PopBackTo()) + + //delayed sending used for better animation + post(550) { store.dispatch(HomeAction.ReadCard) } } } next(action) 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 29cd04dc73..168941d2c5 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 @@ -1,12 +1,20 @@ package com.tangem.tap.features.home import android.app.Dialog +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.extensions.safeStartActivity +import com.tangem.tap.common.extensions.toast import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.navigation.DetailsTransition +import com.tangem.tap.common.redux.navigation.FragmentShareTransition +import com.tangem.tap.common.redux.navigation.ShareElement +import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget +import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeDialog import com.tangem.tap.features.home.redux.HomeState @@ -15,11 +23,36 @@ import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_home.* import org.rekotlin.StoreSubscriber +import java.lang.ref.WeakReference class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber { + lateinit var btnScanCard: ViewStateWidget + private var dialog: Dialog? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val transitionViews = mutableListOf() + transitionViews.add(ShareElement(WeakReference(imv_front_card), "imv_front_card")) + val shareTransition = FragmentShareTransition(transitionViews, DetailsTransition(), DetailsTransition()) + + store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition)) + store.dispatch(HomeAction.Init) + + btn_scan_card?.setOnClickListener { store.dispatch(HomeAction.ReadCard) } + btn_get_new_card?.setOnClickListener { store.dispatch(HomeAction.GoToShop) } + + btnScanCard = IndeterminateProgressButtonWidget(btn_scan_card, progress) + } + override fun onStart() { super.onStart() store.subscribe(this) { state -> @@ -37,16 +70,23 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state -> - { next -> - { action -> - when (action) { - is HomeAction.CheckIfFirstLaunch -> { - store.dispatch( - HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.getCountOfLaunches() == 1) - ) - } - is HomeAction.ReadCard -> { - scope.launch { - val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler) - withContext(Dispatchers.Main) { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - when (result) { - is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) - store.dispatch(GlobalAction.RestoreAppCurrency) - store.state.globalState.tapWalletManager.onCardScanned(result.data) - showDisclaimerOrNavigateToWallet() - } - } - } - } - } - is HomeAction.GoToShop -> { - val uri = Uri.parse(CARD_SHOP_URI) - val intent = Intent(Intent.ACTION_VIEW, uri) - startActivity(action.context, intent, null) - } - } - next(action) - } - } - } - - private fun showDisclaimerOrNavigateToWallet() { - 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 { - private const val CARD_SHOP_URI = + val handler = homeMiddleware + + const val CARD_SHOP_URI = "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" } } +private val homeMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is HomeAction.Init -> { + store.dispatch(GlobalAction.RestoreAppCurrency) + store.dispatch(GlobalAction.GetMoonPayUserStatus) + store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) + } + is HomeAction.ReadCard -> { + if (!preferencesStorage.wasDisclaimerAccepted()) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + } else { + launchOnboardingService() + } + } + is HomeAction.GoToShop -> store.dispatch(HomeAction.SetOpenUrl(HomeMiddleware.CARD_SHOP_URI)) + } + next(action) + } + } +} +private fun launchOnboardingService() { + fun changeButtonState(state: ButtonState) { + val btnState = IndeterminateProgressButton(state) + store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) + } + changeButtonState(ButtonState.PROGRESS) + val walletManager = store.state.globalState.tapWalletManager + val service = OnboardingService( + walletManager, + preferencesStorage.usedCardsPrefStorage + ) + + service.onReadyToProceed = { + store.dispatch(OnboardingAction.SetData(it)) + changeButtonState(ButtonState.ENABLED) + store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen)) + } + + service.onFailedToProceedToOnboardingCase = { + changeButtonState(ButtonState.ENABLED) + walletManager.onCardScanned(it) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + + service.onError = { + changeButtonState(ButtonState.ENABLED) + store.dispatch(GlobalAction.DebugShowError(TapError.CustomError(it))) + } + + store.dispatch(GlobalAction.ReadCard(service::onSuccess, { + changeButtonState(ButtonState.ENABLED) + store.dispatch(GlobalAction.DebugShowError(TapError.ScanCardError)) + })) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index 3c031ed7b2..6fe2462f08 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -13,18 +13,27 @@ private fun internalReduce(action: Action, state: AppState): HomeState { if (action !is HomeAction) return state.homeState - var homeState = state.homeState + var state = state.homeState when (action) { - is HomeAction.CheckIfFirstLaunch.Result -> { - homeState = homeState.copy(firstLaunch = action.firstLaunch) + is HomeAction.SetFragmentShareTransition -> { + state = state.copy(shareTransition = action.shareTransition) + } + is HomeAction.SetTermsOfUseState -> { + state = state.copy(isDisclaimerAccepted = action.isDisclaimerAccepted) + } + is HomeAction.SetOpenUrl -> { + state = state.copy(openUrl = action.url) + } + is HomeAction.ChangeScanCardButtonState -> { + state = state.copy(btnScanState = action.state) } is HomeAction.ShowDialog.ScanFails -> { - homeState = homeState.copy(dialog = HomeDialog.ScanFailsDialog) + state = state.copy(dialog = HomeDialog.ScanFailsDialog) } is HomeAction.HideDialog -> { - homeState = homeState.copy(dialog = null) + state = state.copy(dialog = null) } } - return homeState + return state } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index c9fce9b492..9a261cd1bf 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -1,13 +1,19 @@ package com.tangem.tap.features.home.redux +import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.navigation.FragmentShareTransition +import com.tangem.tap.features.send.redux.states.ButtonState import org.rekotlin.StateType data class HomeState( - val firstLaunch: Boolean = true, - val dialog: StateDialog? = null + val openUrl: String? = null, + val isDisclaimerAccepted: Boolean = false, + val shareTransition: FragmentShareTransition? = null, + val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED), + val dialog: StateDialog? = null, ) : StateType -sealed class HomeDialog: StateDialog { - object ScanFailsDialog: HomeDialog() +sealed class HomeDialog : StateDialog { + object ScanFailsDialog : HomeDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt new file mode 100644 index 0000000000..c54cfc75e8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt @@ -0,0 +1,168 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.common.card.Card +import com.tangem.common.extensions.guard +import com.tangem.common.services.Result +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapWalletManager +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.UrlBitmapLoader +import com.tangem.tap.domain.extensions.getArtworkUrl +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.twins.isTangemTwin +import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction +import com.tangem.tap.features.onboarding.redux.OnboardingData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.persistence.UsedCardsPrefStorage +import com.tangem.tap.store +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class OnboardingService( + private val tapWalletManager: TapWalletManager, + private val cardInfoStorage: UsedCardsPrefStorage +) { + + var onReadyToProceed: ((OnboardingData) -> Unit)? = null + set(value) { + field = value + onboardingDataLinker.onReady = { value?.invoke(it) } + } + var onFailedToProceedToOnboardingCase: (suspend (ScanNoteResponse) -> Unit)? = null + var onError: ((String) -> Unit)? = null + + private val onboardingDataLinker = OnboardingDataLinker() + + suspend fun onSuccess(response: ScanNoteResponse) { + onboardingDataLinker.scanNoteResponse = response + val card = response.card + val hasWallets = card.hasWallets() + + if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) { + if (hasWallets) { + cardInfoStorage.activated(card.cardId) + onFailedToProceedToOnboardingCase?.invoke(response) + return + } else { + cardInfoStorage.activated(card.cardId) // вызвать только после того, как был создан кошелек + onboardingDataLinker.navigateTo = AppScreen.OnboardingOther + } + } + if (cardInfoStorage.wasActivated(card.cardId)) { + onFailedToProceedToOnboardingCase?.invoke(response) + return + } + + loadCardImage(card) { onboardingDataLinker.cardArtwork = it } + + if (hasWallets) { + val walletManagerFactory = tapWalletManager.walletManagerFactory + when { + card.isTangemNote() -> loadBalanceNote(walletManagerFactory, response) + card.isTangemWallet() -> loadBalanceWallet(walletManagerFactory, response) + card.isTangemTwin() -> loadBalanceTwins(walletManagerFactory, response) + } + } else { + onboardingDataLinker.valueOfWalletAmount = BigDecimal.ZERO + } + + onboardingDataLinker.navigateTo = when { + card.isTangemNote() -> AppScreen.OnboardingNote + card.isTangemWallet() -> AppScreen.OnboardingWallet + card.isTangemTwin() -> AppScreen.OnboardingTwins + else -> AppScreen.OnboardingOther + } + } + + private suspend fun loadBalanceNote(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { + val walletManager: WalletManager = walletManagerFactory.makePrimaryWalletManager(response).guard { + onError?.invoke("Can't proceed: primary wallet manager didn't create") + return + } + walletManager.update() + store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) + + val wallet = walletManager.wallet + val valueOfAmount = wallet.amounts[AmountType.Coin]?.value + if (valueOfAmount == null) { + onError?.invoke("Amount can't be NULL") + return + } + + onboardingDataLinker.valueOfWalletAmount = valueOfAmount + } + + private suspend fun loadBalanceWallet(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { + + } + + private suspend fun loadBalanceTwins(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { + + } + + private suspend fun loadCardImage(card: Card, callback: (Artwork?) -> Unit) { + fun loadBitmap(url: String, callback: (Artwork?) -> Unit) { + UrlBitmapLoader().loadBitmap(url) { + when (it) { + is Result.Success -> callback(Artwork(url, it.data)) + is Result.Failure -> callback(null) + } + } + } + + when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { + is Result.Success -> { + val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL + loadBitmap(artworkUrl, callback) + } + is Result.Failure -> loadBitmap(Artwork.DEFAULT_IMG_URL, callback) + } + } + +} + +class OnboardingDataLinker { + var onReady: ((OnboardingData) -> Unit)? = null + + internal var scanNoteResponse: ScanNoteResponse? = null + set(value) { + field = value + tryToProceed() + } + internal var cardArtwork: Artwork? = null + set(value) { + field = value + tryToProceed() + } + internal var valueOfWalletAmount: BigDecimal? = null + set(value) { + field = value + tryToProceed() + } + internal var navigateTo: AppScreen? = null + set(value) { + field = value + tryToProceed() + } + + private fun tryToProceed() { + if (!isReady()) return + + val data = OnboardingData(scanNoteResponse!!, cardArtwork!!, valueOfWalletAmount, navigateTo!!) + onReady?.invoke(data) + } + + private fun isReady(): Boolean { + val objList = mutableListOf(scanNoteResponse, cardArtwork, valueOfWalletAmount, navigateTo) + return !objList.any { it == null } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..730c8760fc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.features.onboarding.products.note + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.addOnBackPressedDispatcher +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +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.redux.OnboardingAction +import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_onboarding.* +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activity?.addOnBackPressedDispatcher { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingNoteStep.None)) + } + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + store.dispatch(OnboardingNoteAction.Init) + + imv_front_card.setImageBitmap(store.state.onboardingState.onboardingData?.cardArtwork?.artwork) + } + + override fun subscribeToStore() { + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.onboardingNoteState == newState.onboardingNoteState + }.select { it.onboardingNoteState } + } + } + + override fun newState(state: OnboardingNoteState) { + if (activity == null) return + + pb_state.max = state.steps.size - 1 + + onboarding_main_container.beginDelayedTransition() + when (state.currentStep) { + OnboardingNoteStep.None -> setupNoneState(state) + OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state) + OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state) + OnboardingNoteStep.Done -> setupSuccessState(state) + } + } + + private fun setupNoneState(state: OnboardingNoteState) { + pb_state.progress = state.progress + + btn_main_action.isVisible = true + btn_alternative_action.isVisible = true + btn_check_balance.isVisible = true + tv_header.isVisible = true + tv_body.isVisible = true + + btn_main_action.text = "NULL" + btn_alternative_action.text = "NULL" + btn_check_balance.text = "NULL balance" + tv_header.text = "NULL" + tv_body.text = "NULL" + } + + private fun setupCreateWalletState(state: OnboardingNoteState) { + pb_state.progress = state.progress + + btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) + btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) } + + btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean) + btn_alternative_action.setOnClickListener { } + + btn_check_balance.isVisible = false + btn_check_balance.setOnClickListener { } + + tv_header.setText(R.string.onboarding_create_wallet_header) + tv_body.setText(R.string.onboarding_create_wallet_body) + } + + private fun setupTopUpWalletState(state: OnboardingNoteState) { + pb_state.progress = state.progress + + btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) + btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.TopUp) } + + btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) + btn_alternative_action.setOnClickListener { } + + btn_check_balance.isVisible = false + btn_check_balance.setOnClickListener { } + + tv_header.setText(R.string.onboarding_top_up_header) + tv_body.setText(R.string.onboarding_top_up_body) + } + + private fun setupSuccessState(state: OnboardingNoteState) { + pb_state.progress = state.progress + + btn_main_action.setText(R.string.onboarding_done_button_continue) + btn_main_action.setOnClickListener { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } + + btn_alternative_action.isVisible = false + btn_alternative_action.setText("") + btn_alternative_action.setOnClickListener { } + + btn_check_balance.isVisible = false + btn_check_balance.setOnClickListener { } + + tv_header.setText(R.string.onboarding_done_header) + tv_body.setText(R.string.onboarding_done_body) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..bab556d68b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.features.onboarding.products.note.redux + +import com.tangem.blockchain.common.WalletManager +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingNoteAction : Action { + object Init : OnboardingNoteAction() + object CreateWallet : OnboardingNoteAction() + object TopUp : OnboardingNoteAction() + object Done : OnboardingNoteAction() + + data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7f5ee16c3f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -0,0 +1,72 @@ +package com.tangem.tap.features.onboarding.products.note.redux + +import com.tangem.common.CompletionResult +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.launch +import org.rekotlin.Action +import org.rekotlin.Middleware + +class OnboardingNoteMiddleware { + companion object { + private const val CARD_SHOP_URI = + "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" + val handler = onboardingNoteMiddleware + } +} + +private val onboardingNoteMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + handleMainOnboardingAction(action, ::handleNoteAction) + next(action) + } + } +} + +private fun handleMainOnboardingAction(action: Action, isNotMainAction: (Action) -> Unit) { + val action = action as? OnboardingAction ?: isNotMainAction(action) + + when (action) { + } +} + +private fun handleNoteAction(action: Action) { + val onboardingState = store.state.onboardingState + val noteState = store.state.onboardingNoteState + + when (action) { + is OnboardingNoteAction.SetWalletManager -> { + } + is OnboardingNoteAction.Init -> { + val step = if (noteState.walletManager == null) { + OnboardingNoteStep.CreateWallet + } else { + OnboardingNoteStep.TopUpWallet + } + store.dispatch(OnboardingAction.SetInitialStepOfScreen(step)) + } + is OnboardingNoteAction.CreateWallet -> { + scope.launch { + val scanNoteResponse = onboardingState.onboardingData?.scanNoteResponse + val result = tangemSdkManager.createWallet(scanNoteResponse?.card?.cardId) + when (result) { + is CompletionResult.Success -> { +// val scanNoteResponse = scanNoteResponse!!.copy(card = result.data) +// store.dispatch(OnboardingAction.SetScanNoteResponse(scanNoteResponse)) + } + is CompletionResult.Failure -> { + store.dispatch(OnboardingAction.Error("Failed to create wallet")) + } + } + } + } + is OnboardingNoteAction.TopUp -> { + + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..b6a50be846 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.features.onboarding.products.note.redux + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import org.rekotlin.Action + +class OnboardingNoteReducer { + companion object { + fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState { + var state = appState.onboardingNoteState + + when (action) { + is OnboardingAction.SetInitialStepOfScreen -> { + (action.step as? OnboardingNoteStep)?.let { + if (it != state.currentStep && state.steps.contains(it)) { + state = state.copy(currentStep = it) + } + } + } + OnboardingAction.SwitchToNextStep -> { + val progress = state.progress + if (progress >= 0 && progress != state.steps.size - 1) { + state = state.copy(currentStep = state.steps[progress + 1]) + } + } + is OnboardingNoteAction.SetWalletManager -> { + state = state.copy(walletManager = action.walletManager) + } + + } + + return state +} \ No newline at end of file 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 new file mode 100644 index 0000000000..ae5677779f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.features.onboarding.products.note.redux + +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingNoteState( + val walletManager: WalletManager? = null, + val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, + val steps: List = OnboardingNoteStep.values().toList(), +) : StateType { + + val progress: Int + get() = steps.indexOf(currentStep) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt new file mode 100644 index 0000000000..cada557578 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.features.onboarding.products.old + +import android.os.Bundle +import android.view.View +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.addOnBackPressedDispatcher +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.redux.OnboardingOtherCardsState +import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activity?.addOnBackPressedDispatcher { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) + } + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } + + override fun subscribeToStore() { +// store.subscribe(this) { state -> +// state.skipRepeats { oldState, newState -> +// oldState.onboardingOtherState == newState.onboardingOtherState +// }.select { it.onboardingOtherState } +// } + } + + override fun newState(state: OnboardingOtherCardsState) { + if (activity == null) return + + } +} \ 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 new file mode 100644 index 0000000000..5b2eba2423 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt @@ -0,0 +1,52 @@ +package com.tangem.tap.features.onboarding.products.wallet + +import android.os.Bundle +import android.view.View +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.addOnBackPressedDispatcher +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState +import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activity?.addOnBackPressedDispatcher { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) + } + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + store.dispatch(OnboardingAction.OnboardingOtherAction.Init) + } + + override fun subscribeToStore() { + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.onboardingWalletState == newState.onboardingWalletState + }.select { it.onboardingWalletState } + } + } + + override fun newState(state: OnboardingWalletState) { + if (activity == null) return + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt new file mode 100644 index 0000000000..5cfd213d57 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.features.onboarding.products.wallet.redux + +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingWalletAction : Action { + object Init : OnboardingWalletAction() + object CreateWallet : OnboardingWalletAction() + object TopUp : OnboardingWalletAction() + object Done : OnboardingWalletAction() + + object ProceedBackup : OnboardingWalletAction() + + sealed class Backup : Action { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt new file mode 100644 index 0000000000..c442090de4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.features.onboarding.products.wallet.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Middleware + +class OnboardingWalletMiddleware { + companion object { + val handler = onboardingWalletMiddleware + } +} + +private val onboardingWalletMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + next(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt new file mode 100644 index 0000000000..427f44048e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.features.onboarding.products.wallet.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +class OnboardingWalletReducer { + companion object { + fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState { + var state = appState.onboardingWalletState + + when (action) { + + } + + return state +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt new file mode 100644 index 0000000000..9b8579171a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.features.onboarding.products.wallet.redux + +import com.tangem.tap.features.onboarding.redux.OnboardingStep +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingWalletState( + val any: String? = null +) : StateType + +enum class OnboardingWalletStep : OnboardingStep { + None, CreateWallet, TopUpWallet, Backup, Done +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt new file mode 100644 index 0000000000..7a904b47ee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.features.onboarding.redux + +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingAction : Action { + + data class SetInitialStepOfScreen(val step: OnboardingStep) : OnboardingAction() + object SwitchToNextStep : OnboardingAction() + + data class SetData(val data: OnboardingData) : OnboardingAction() + + sealed class OnboardingOtherAction : Action { + object Init : OnboardingOtherAction() + object CreateWallet : OnboardingOtherAction() + object Done : OnboardingOtherAction() + } + + class Error(val message: String) : OnboardingAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt new file mode 100644 index 0000000000..ea373a5f41 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.features.onboarding.redux + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.store +import org.rekotlin.Middleware + +class OnboardingMiddleware { + companion object { + val handler = onboardingMiddleware + } +} + +private val onboardingMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + val onboardingState = store.state.onboardingState + when (action) { + } + next(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt new file mode 100644 index 0000000000..5ee024922d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features.onboarding.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +class OnboardingReducer { + companion object { + fun reduce(action: Action, state: AppState): OnboardingState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, appState: AppState): OnboardingState { + + if (action !is OnboardingAction) return appState.onboardingState + + var state = appState.onboardingState + when (action) { + is OnboardingAction.SetData -> { + state = state.copy(onboardingData = action.data) + } + } + + return state +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt new file mode 100644 index 0000000000..2a4909de43 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.features.onboarding.redux + +import org.rekotlin.StateType + +data class OnboardingState( + val onboardingData: OnboardingData? = null, +) : StateType + +data class OnboardingOtherCardsState( + val any: String? = null +) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt new file mode 100644 index 0000000000..cc422410a5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.features.onboarding.redux + +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.features.wallet.redux.Artwork +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingData( + val scanNoteResponse: ScanNoteResponse, + val cardArtwork: Artwork, + val balance: BigDecimal?, + val onboardingScreen: AppScreen +) + +interface OnboardingStep + +enum class OnboardingNoteStep : OnboardingStep { + None, CreateWallet, TopUpWallet, Done +} + +enum class OnboardingOtherStep : OnboardingStep { + CreateWallet, Done +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/TwinsOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/features/wallet/ui/TwinsOnboardingFragment.kt rename to app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt index 0d3abe02e2..6bfd2f28df 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/TwinsOnboardingFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt @@ -16,7 +16,7 @@ import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_twin_cards.* import kotlinx.android.synthetic.main.layout_twin_cards.* -class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) { +class OnboardingTwinsFragment : Fragment(R.layout.fragment_twin_cards) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 b80b989b70..aac75ef60b 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 @@ -107,7 +107,7 @@ class SingleWalletView : WalletView { tv_twin_card_number.hide() } if (twinCardsState?.showTwinOnboarding == true) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } diff --git a/app/src/main/res/drawable/bg_circle_gray.xml b/app/src/main/res/drawable/bg_circle_gray.xml new file mode 100644 index 0000000000..94a585da83 --- /dev/null +++ b/app/src/main/res/drawable/bg_circle_gray.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home_card_back.xml b/app/src/main/res/drawable/ic_home_card_back.xml new file mode 100644 index 0000000000..8a71b20ce5 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_card_back.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_home_card_front.xml b/app/src/main/res/drawable/ic_home_card_front.xml new file mode 100644 index 0000000000..3bbf5a93dd --- /dev/null +++ b/app/src/main/res/drawable/ic_home_card_front.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 33157a5fe6..1ebc971c29 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -2,95 +2,126 @@ + android:layout_height="match_parent" + app:layoutDescription="@xml/fragment_welcome_scene"> - - - + android:src="@drawable/ic_home_card_back" + android:translationY="-55dp" + app:layout_constraintEnd_toEndOf="@+id/imv_front_card" + app:layout_constraintStart_toStartOf="@+id/imv_front_card" + app:layout_constraintTop_toBottomOf="@+id/imv_front_card" /> - - - - + android:src="@drawable/ic_home_card_front" + android:transitionName="imv_front_card" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.496" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/view" /> + android:layout_marginBottom="14dp" + android:fontFamily="sans-serif-medium" + android:text="@string/home_welcome_header" + android:textColor="#060606" + android:textSize="24sp" + app:layout_constraintBottom_toTopOf="@+id/tv_home_body" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.508" + app:layout_constraintStart_toStartOf="parent" /> + + + + + + + + + + + app:layout_constraintGuide_percent="0.5" + app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintHorizontal_bias="0.498" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - \ No newline at end of file + + diff --git a/app/src/main/res/layout/fragment_onboarding.xml b/app/src/main/res/layout/fragment_onboarding.xml new file mode 100644 index 0000000000..710cf8a7c5 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml new file mode 100644 index 0000000000..77d9ef65f8 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From 2f1c8959175c10143839865e9a32313d718dba11 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 12:51:05 +0300 Subject: [PATCH 17/55] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/MainActivity.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 89ede4d80d..37682e2bc8 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -17,7 +17,6 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.home.redux.HomeAction import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.CoroutineExceptionHandler @@ -73,10 +72,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { override fun onResume() { super.onResume() notificationsHandler = NotificationsHandler(fragment_container) - if (supportFragmentManager.backStackEntryCount == 0 || - store.state.globalState.scanNoteResponse == null - ) { - store.dispatch(HomeAction.CheckIfFirstLaunch) + + val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 + val isScannedBefore = store.state.globalState.scanNoteResponse != null + val isOnboardingStarted = store.state.onboardingState.onboardingData?.scanNoteResponse != null + if ((backStackIsEmpty || isScannedBefore) && !isOnboardingStarted) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } intentHandler.handleIntent(intent) @@ -107,7 +107,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { if (snackbar != null) return snackbar = Snackbar.make( - fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE + fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE ) if (buttonTitle != null && action != null) { snackbar?.setAction(getString(buttonTitle), action) From d8d81aee62eeaa1934c0de32ffdd5fd75267fc8c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 12:57:44 +0300 Subject: [PATCH 18/55] Updated on 2026-08-14 --- app/src/main/res/layout/fragment_home.xml | 3 +-- app/src/main/res/layout/fragment_onboarding.xml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 1ebc971c29..f8d352de67 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -2,8 +2,7 @@ + android:layout_height="match_parent"> Date: Fri, 24 Sep 2021 17:44:21 +0300 Subject: [PATCH 19/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/extensions/Navigation.kt | 1 + app/src/main/java/com/tangem/tap/common/images/Picasso.kt | 1 + app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt | 4 ++++ .../main/java/com/tangem/tap/features/home/HomeFragment.kt | 2 +- .../java/com/tangem/tap/features/home/redux/HomeMiddleware.kt | 3 ++- 5 files changed, 9 insertions(+), 2 deletions(-) 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 c7e04cf44f..3a119fd683 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 @@ -36,6 +36,7 @@ fun FragmentActivity.openFragment( fgShareTransition?.apply { fragment.sharedElementEnterTransition = enterTransitionSet fragment.sharedElementReturnTransition = exitTransitionSet + transaction.setReorderingAllowed(true) shareElements.forEach { shareElement -> shareElement.wView.get()?.let { view -> transaction.addSharedElement(view, shareElement.name) diff --git a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt index c3b1d139a3..df364efdb7 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt @@ -23,6 +23,7 @@ class PicassoHelper { private fun getOkHttpForPicasso(application: Application): OkHttpClient { val okHttpBuilder = OkHttpClient.Builder() okHttpBuilder.cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE)) + okHttpBuilder.callTimeout(15000, TimeUnit.MILLISECONDS) okHttpBuilder.addInterceptor { chain -> val cacheControl = CacheControl.Builder() .maxStale(KEEP_CACHE_MAX_DAYS, TimeUnit.DAYS) diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt index d09d8ed3bc..1f4897a487 100644 --- a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -5,6 +5,7 @@ import android.graphics.drawable.Drawable import com.squareup.picasso.Picasso import com.squareup.picasso.Target import com.tangem.common.services.Result +import timber.log.Timber /** [REDACTED_AUTHOR] @@ -26,13 +27,16 @@ open class DownloadTarget( ) : Target { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + Timber.d("onBitmapLoaded") callback(Result.Success(bitmap)) } override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) { + Timber.d("onBitmapFailed") callback.invoke(Result.Failure(e)) } override fun onPrepareLoad(placeHolderDrawable: Drawable?) { + Timber.d("onPrepareLoad") } } \ 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 168941d2c5..457509098f 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 @@ -41,7 +41,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber() - transitionViews.add(ShareElement(WeakReference(imv_front_card), "imv_front_card")) + transitionViews.add(ShareElement(WeakReference(imv_front_card), imv_front_card.transitionName)) val shareTransition = FragmentShareTransition(transitionViews, DetailsTransition(), DetailsTransition()) store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition)) 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 0a6e7945f4..bbb37ceded 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 @@ -61,7 +61,8 @@ private fun launchOnboardingService() { service.onReadyToProceed = { store.dispatch(OnboardingAction.SetData(it)) changeButtonState(ButtonState.ENABLED) - store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen)) + val sharedTransition = store.state.homeState.shareTransition + store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen, fragmentShareTransition = sharedTransition)) } service.onFailedToProceedToOnboardingCase = { From ed8aa753ce1d51873744e6c9a0239fedf08eb754 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 24 Sep 2021 18:28:40 +0300 Subject: [PATCH 20/55] Updated on 2026-08-14 --- .../redux/navigation/NavigationAction.kt | 27 +++++-------------- .../transitions/OnboardingTransitions.kt | 27 +++++++++++++++++++ .../tangem/tap/features/home/HomeFragment.kt | 11 +++++--- 3 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt 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 375b16e785..d77f97b263 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 @@ -2,18 +2,15 @@ package com.tangem.tap.common.redux.navigation import android.view.View import androidx.fragment.app.FragmentActivity -import androidx.transition.ChangeBounds -import androidx.transition.ChangeImageTransform -import androidx.transition.ChangeTransform import androidx.transition.TransitionSet import org.rekotlin.Action import java.lang.ref.WeakReference sealed class NavigationAction : Action { data class NavigateTo( - val screen: AppScreen, - val addToBackstack: Boolean = true, - val fragmentShareTransition: FragmentShareTransition? = null + val screen: AppScreen, + val addToBackstack: Boolean = true, + val fragmentShareTransition: FragmentShareTransition? = null ) : NavigationAction() data class PopBackTo(val screen: AppScreen? = null) : NavigationAction() @@ -25,19 +22,9 @@ sealed class NavigationAction : Action { } data class FragmentShareTransition( - val shareElements: List, - val enterTransitionSet: TransitionSet, - val exitTransitionSet: TransitionSet + val shareElements: List, + val enterTransitionSet: TransitionSet, + val exitTransitionSet: TransitionSet ) -data class ShareElement(val wView: WeakReference, val name: String) - -//TODO: remove transition example -class DetailsTransition : TransitionSet() { - init { - ordering = ORDERING_TOGETHER; - addTransition(ChangeBounds()) - .addTransition(ChangeTransform()) - .addTransition(ChangeImageTransform()) - } -} \ No newline at end of file +data class ShareElement(val wView: WeakReference, val name: String) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt new file mode 100644 index 0000000000..7d9334e123 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.transitions + +import androidx.transition.ChangeBounds +import androidx.transition.ChangeImageTransform +import androidx.transition.TransitionSet + +/** +[REDACTED_AUTHOR] + */ +class FrontCardEnterTransition : TransitionSet() { + init { + ordering = ORDERING_TOGETHER; + addTransition(ChangeImageTransform()) + addTransition(ChangeBounds()) +// addTransition(Fade()) +// addTransition(Fade(Visibility.MODE_IN)) +// addTransition(ChangeTransform()) + } +} + +class FrontCardExitTransition : TransitionSet() { + init { + ordering = ORDERING_TOGETHER; + addTransition(ChangeImageTransform()) + addTransition(ChangeBounds()) + } +} \ 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 457509098f..e9f86f005b 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 @@ -10,11 +10,12 @@ import androidx.transition.TransitionInflater import com.tangem.tap.common.extensions.safeStartActivity import com.tangem.tap.common.extensions.toast import com.tangem.tap.common.redux.global.StateDialog -import com.tangem.tap.common.redux.navigation.DetailsTransition import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget +import com.tangem.tap.common.transitions.FrontCardEnterTransition +import com.tangem.tap.common.transitions.FrontCardExitTransition import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeDialog import com.tangem.tap.features.home.redux.HomeState @@ -40,9 +41,11 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber() - transitionViews.add(ShareElement(WeakReference(imv_front_card), imv_front_card.transitionName)) - val shareTransition = FragmentShareTransition(transitionViews, DetailsTransition(), DetailsTransition()) + val shareTransition = FragmentShareTransition( + listOf(ShareElement(WeakReference(imv_front_card), imv_front_card.transitionName)), + FrontCardEnterTransition(), + FrontCardExitTransition() + ) store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition)) store.dispatch(HomeAction.Init) From 63eaedff8aef5a5424f436ff4faaa66c53501e48 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 25 Sep 2021 01:51:33 +0300 Subject: [PATCH 21/55] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 43 +++++++++++-------- .../com/tangem/tap/common/extensions/Store.kt | 36 ++++++++++++++-- .../tangem/tap/common/redux/StateDialog.kt | 10 +++++ .../tap/common/redux/global/GlobalAction.kt | 14 ++++-- .../common/redux/global/GlobalMidlleware.kt | 6 +-- .../tap/common/redux/global/GlobalState.kt | 6 +-- .../redux/walletconnect/WalletConnectState.kt | 2 +- .../tangem/tap/features/home/HomeFragment.kt | 38 +--------------- .../tap/features/home/redux/HomeAction.kt | 14 +++--- .../tap/features/home/redux/HomeMiddleware.kt | 3 +- .../tap/features/home/redux/HomeReducer.kt | 9 ---- .../tap/features/home/redux/HomeState.kt | 15 ++----- .../features/send/redux/SendScreenAction.kt | 4 +- .../features/send/redux/states/SendState.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 1 - .../tap/features/wallet/redux/WalletState.kt | 9 ++-- .../wallet/redux/reducers/WalletReducer.kt | 37 ++++++++-------- .../wallet/ui/WalletDetailsFragment.kt | 2 +- .../wallet/ui/dialogs/ScanFailsDialog.kt | 8 +--- .../wallet/ui/wallet/MultiWalletView.kt | 6 +-- .../wallet/ui/wallet/SingleWalletView.kt | 5 +-- 21 files changed, 130 insertions(+), 140 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index f58e132937..218c96776f 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -2,9 +2,13 @@ package com.tangem.tap.common import android.app.Dialog import android.content.Context +import com.tangem.tap.common.redux.AppDialog 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.products.note.OnboardingAddressInfoDialog +import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R import org.rekotlin.StoreSubscriber @@ -37,35 +41,40 @@ class DialogManager : StoreSubscriber { val context = context ?: return if (dialog != null) return - when (state.dialog) { + dialog = when (state.dialog) { + is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is WalletConnectDialog.UnsupportedCard -> - dialog = SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, - context = context + SimpleAlertDialog.create( + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, + context = context ) is WalletConnectDialog.OpeningSessionRejected -> { - dialog = SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_same_wcuri, - context = context + SimpleAlertDialog.create( + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_same_wcuri, + context = context ) } is WalletConnectDialog.SessionTimeout -> { - dialog = SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_error_timeout, - context = context + SimpleAlertDialog.create( + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_error_timeout, + context = context ) } is WalletConnectDialog.ApproveWcSession -> - dialog = ApproveWcSessionDialog.create(state.dialog.session, context) + ApproveWcSessionDialog.create(state.dialog.session, context) is WalletConnectDialog.ClipboardOrScanQr -> - dialog = ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context) + ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context) is WalletConnectDialog.RequestTransaction -> - dialog = TransactionDialog.create(state.dialog.dialogData, context) + TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> - dialog = PersonalSignDialog.create(state.dialog.data, context) + PersonalSignDialog.create(state.dialog.data, context) + is OnboardingAction.Dialog.AddressInfo -> { + OnboardingAddressInfoDialog(state.dialog, context) + } + else -> null } dialog?.show() } 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 99e6d8fe5d..06cff9e110 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 @@ -1,6 +1,8 @@ package com.tangem.tap.common.extensions -import com.tangem.tap.common.redux.AppState +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.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -8,8 +10,36 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Store -fun Store.dispatchOnMain(action: Action) { +fun Store<*>.dispatchOnMain(action: Action) { scope.launch(Dispatchers.Main) { store.dispatch(action) } -} \ No newline at end of file +} + +fun Store<*>.dispatchOpenUrl(url: String) { + store.dispatch(NavigationAction.OpenUrl(url)) +} + +fun Store<*>.dispatchNotification(resId: Int) { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.ShowNotification(resId)) + } +} + +fun Store<*>.dispatchToastNotification(resId: Int) { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.ShowToastNotification(resId)) + } +} + +fun Store<*>.dispatchDialogShow(dialog: StateDialog) { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.ShowDialog(dialog)) + } +} + +fun Store<*>.dispatchDialogHide() { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.HideDialog) + } +} 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 new file mode 100644 index 0000000000..e643ceb207 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.common.redux + +/** +[REDACTED_AUTHOR] + */ +interface StateDialog + +sealed class AppDialog : StateDialog { + object ScanFailsDialog : 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 928cb7b341..4c4675eb96 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 @@ -4,6 +4,9 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.tap.common.redux.DebugErrorAction +import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -17,6 +20,14 @@ import org.rekotlin.Action sealed class GlobalAction : Action { + // notifications + data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction + data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction + + // dialogs + data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() + object HideDialog : GlobalAction() + data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction data class ReadCard( @@ -56,9 +67,6 @@ sealed class GlobalAction : Action { data class SendFeedback(val emailData: EmailData) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() - data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() - object HideDialog : GlobalAction() - object GetMoonPayUserStatus : GlobalAction() { data class Success(val moonPayUserStatus: MoonPayUserStatus) : 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 115329af90..f6e8152a77 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 @@ -4,10 +4,11 @@ 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.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.moonpay.MoonpayService @@ -37,8 +38,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState if (action.result.error is TangemSdkError.UserCancelled) { store.dispatch(GlobalAction.ScanFailsCounter.Increment) if (store.state.globalState.scanCardFailsCounter >= 2) { - store.dispatch(HomeAction.ShowDialog.ScanFails) - store.dispatch(WalletAction.ShowDialog.ScanFails) + store.dispatchDialogShow(AppDialog.ScanFailsDialog) } } else { store.dispatch(GlobalAction.ScanFailsCounter.Reset) 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 f370fca90c..558dcaab95 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 @@ -1,6 +1,7 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.configurable.config.ConfigManager @@ -27,7 +28,4 @@ data class GlobalState( ) : StateType typealias CryptoCurrencyName = String -typealias FiatCurrencyName = String - - -interface StateDialog \ No newline at end of file +typealias FiatCurrencyName = String \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 757584cc46..38aeaabc84 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.trustwallet.walletconnect.models.WCPeerMeta 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 e9f86f005b..7633a60893 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 @@ -1,15 +1,9 @@ package com.tangem.tap.features.home -import android.app.Dialog -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.safeStartActivity -import com.tangem.tap.common.extensions.toast -import com.tangem.tap.common.redux.global.StateDialog import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget @@ -17,9 +11,7 @@ import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.common.transitions.FrontCardEnterTransition import com.tangem.tap.common.transitions.FrontCardExitTransition import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.home.redux.HomeDialog import com.tangem.tap.features.home.redux.HomeState -import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_home.* @@ -28,9 +20,7 @@ import java.lang.ref.WeakReference class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber { - lateinit var btnScanCard: ViewStateWidget - - private var dialog: Dialog? = null + private lateinit var btnScanCard: ViewStateWidget override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,36 +64,10 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber { - if (dialog == null) dialog = ScanFailsDialog.create(requireContext()).apply { show() } - } - else -> { - dialog?.dismiss() - dialog = null - } - } - } - override fun onDestroyView() { store.dispatch(HomeAction.SetFragmentShareTransition(null)) super.onDestroyView() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index b38e74f995..c6b4cda59c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -5,18 +5,14 @@ import com.tangem.tap.common.redux.navigation.FragmentShareTransition import org.rekotlin.Action sealed class HomeAction : Action { - object Init : HomeAction() - data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction() - + // from ui object ReadCard : HomeAction() object GoToShop : HomeAction() - class SetOpenUrl(val url: String?) : HomeAction() - class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction() + // internal + object Init : HomeAction() + data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction() + data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction() data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction() - object ShowDialog : HomeAction() { - object ScanFails : HomeAction() - } - object HideDialog : HomeAction() } 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 bbb37ceded..d364664c17 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 @@ -1,6 +1,7 @@ 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.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -38,7 +39,7 @@ private val homeMiddleware: Middleware = { dispatch, state -> launchOnboardingService() } } - is HomeAction.GoToShop -> store.dispatch(HomeAction.SetOpenUrl(HomeMiddleware.CARD_SHOP_URI)) + is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) } next(action) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index 6fe2462f08..0b9b044622 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -21,18 +21,9 @@ private fun internalReduce(action: Action, state: AppState): HomeState { is HomeAction.SetTermsOfUseState -> { state = state.copy(isDisclaimerAccepted = action.isDisclaimerAccepted) } - is HomeAction.SetOpenUrl -> { - state = state.copy(openUrl = action.url) - } is HomeAction.ChangeScanCardButtonState -> { state = state.copy(btnScanState = action.state) } - is HomeAction.ShowDialog.ScanFails -> { - state = state.copy(dialog = HomeDialog.ScanFailsDialog) - } - is HomeAction.HideDialog -> { - state = state.copy(dialog = null) - } } return state diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 9a261cd1bf..29a7825aae 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -1,19 +1,12 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.redux.global.StateDialog import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.send.redux.states.ButtonState import org.rekotlin.StateType data class HomeState( - val openUrl: String? = null, - val isDisclaimerAccepted: Boolean = false, - val shareTransition: FragmentShareTransition? = null, - val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED), - val dialog: StateDialog? = null, -) : StateType - -sealed class HomeDialog : StateDialog { - object ScanFailsDialog : HomeDialog() -} \ No newline at end of file + val isDisclaimerAccepted: Boolean = false, + val shareTransition: FragmentShareTransition? = null, + val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED), +) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 6f20155539..b0b9263034 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -5,13 +5,13 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.tap.common.redux.ErrorAction +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.ToastNotificationAction -import com.tangem.tap.common.redux.global.StateDialog import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage +import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 3f3d32df5e..ed98986ae3 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -7,7 +7,7 @@ import com.tangem.common.extensions.isZero import com.tangem.tap.common.CurrencyConverter import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.entities.TapCurrency -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.text.DecimalDigitsInputFilter import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage 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 488adca48d..51d6c3740d 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 @@ -122,7 +122,6 @@ sealed class WalletAction : Action { object ShowDialog : WalletAction() { object QrCode : WalletAction() - object ScanFails : WalletAction() object SignedHashesMultiWalletDialog : WalletAction() object ChooseTradeActionDialog : WalletAction() } 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 0d81713483..cdabd1500c 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 @@ -5,8 +5,9 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.extensions.isAboveZero import com.tangem.tap.common.entities.Button +import com.tangem.tap.common.extensions.toQrCode +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.common.redux.global.StateDialog import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.domain.topup.TradeCryptoHelper @@ -141,7 +142,6 @@ sealed class WalletDialog: StateDialog { ) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() - object ScanFailsDialog : WalletDialog() object SignedHashesMultiWalletDialog : WalletDialog() object ChooseTradeActionDialog : WalletDialog() } @@ -165,8 +165,9 @@ data class AddressData( val type: AddressType, val shareUrl: String, val exploreUrl: String, - val qrCode: Bitmap? = null -) +) { + val qrCode: Bitmap by lazy { shareUrl.toQrCode() } +} data class Artwork( val artworkId: String, 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 8c6db9d9a2..d999216bba 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 @@ -232,9 +232,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) ) } - is WalletAction.ShowDialog.ScanFails -> { - newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog) - } is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> { newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog) } @@ -276,30 +273,34 @@ private fun internalReduce(action: Action, state: AppState): WalletState { fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null + val listOfAddressData = wallet.createAddressesData() + // restore a selected wallet address + var indexOfSelectedWallet = 0 + walletAddresses?.let { + val index = + listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } + if (index != -1) indexOfSelectedWallet = index + } + return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData) +} + +fun Wallet.createAddressesData(): List { val listOfAddressData = mutableListOf() // put a defaultAddress at the first place - wallet.addresses.forEach { + addresses.forEach { val addressData = AddressData( - it.value, - it.type, - wallet.getShareUri(it.value), - wallet.getExploreUrl(it.value) + it.value, + it.type, + getShareUri(it.value), + getExploreUrl(it.value) ) - if (it.type == wallet.blockchain.defaultAddressType()) { + if (it.type == blockchain.defaultAddressType()) { listOfAddressData.add(0, addressData) } else { listOfAddressData.add(addressData) } } - - // restore a selected wallet address - var indexOfSelectedWallet = 0 - walletAddresses?.let { - val index = - listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } - if (index != -1) indexOfSelectedWallet = index - } - return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData) + return listOfAddressData } private fun handleCheckSignedHashesActions( 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 c2ff298244..1592ae4eb2 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 @@ -12,7 +12,7 @@ import androidx.transition.TransitionInflater import com.squareup.picasso.Picasso import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index b3f63a0236..0f164965d4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -2,10 +2,9 @@ package com.tangem.tap.features.wallet.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.feedback.ScanFailsEmail -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R @@ -23,10 +22,7 @@ class ScanFailsDialog { store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) } setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { - store.dispatch(HomeAction.HideDialog) - store.dispatch(WalletAction.HideDialog) - } + setOnDismissListener { store.dispatchDialogHide() } }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index d40eaa4496..aa0aaee36c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -4,7 +4,7 @@ import android.app.Dialog import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.redux.global.StateDialog +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.features.tokens.redux.TokensAction @@ -14,7 +14,6 @@ import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter -import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -134,9 +133,6 @@ class MultiWalletView : WalletView { val fragment = fragment ?: return val context = fragment.context ?: return when (walletDialog) { - is WalletDialog.ScanFailsDialog -> { - if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } - } is WalletDialog.SignedHashesMultiWalletDialog -> { if (dialog == null) { dialog = SignedHashesWarningDialog.create(context).apply { show() } 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 aac75ef60b..d2e4e77cc5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import android.widget.Button import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.redux.global.StateDialog +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 @@ -248,9 +248,6 @@ class SingleWalletView : WalletView { this.show(walletDialog.amounts) } } - is WalletDialog.ScanFailsDialog -> { - if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } - } is WalletDialog.SignedHashesMultiWalletDialog -> { if (dialog == null) { dialog = SignedHashesWarningDialog.create(context).apply { show() } From 34a364fca10d7017c62eff114c116ca5b6997093 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 25 Sep 2021 03:57:57 +0300 Subject: [PATCH 22/55] Updated on 2026-08-14 --- .../com/tangem/tap/common/images/Picasso.kt | 23 +++++++++++++++++++ .../com/tangem/tap/domain/UrlBitmapLoader.kt | 15 +++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt index df364efdb7..7d2a956bde 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt @@ -3,9 +3,12 @@ package com.tangem.tap.common.images import android.app.Application import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso +import com.tangem.wallet.BuildConfig import okhttp3.Cache import okhttp3.CacheControl import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import timber.log.Timber import java.io.File import java.util.concurrent.TimeUnit @@ -17,6 +20,7 @@ class PicassoHelper { val picasso = Picasso.Builder(application) .downloader(OkHttp3Downloader(getOkHttpForPicasso(application))) .build() + picasso.isLoggingEnabled = BuildConfig.DEBUG Picasso.setSingletonInstance(picasso) } @@ -24,6 +28,7 @@ class PicassoHelper { val okHttpBuilder = OkHttpClient.Builder() okHttpBuilder.cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE)) okHttpBuilder.callTimeout(15000, TimeUnit.MILLISECONDS) + okHttpBuilder.addInterceptor { chain -> val cacheControl = CacheControl.Builder() .maxStale(KEEP_CACHE_MAX_DAYS, TimeUnit.DAYS) @@ -34,7 +39,25 @@ class PicassoHelper { .build() chain.proceed(neverExpireRequest) } + addDebugInterceptors(okHttpBuilder) + return okHttpBuilder.build() } + + private fun addDebugInterceptors(okHttpBuilder: OkHttpClient.Builder) { + if (!BuildConfig.DEBUG) return + + val picassoInterceptor = HttpLoggingInterceptor(PicassoOkHttpLogger()).apply { + level = HttpLoggingInterceptor.Level.BODY + } + okHttpBuilder.addInterceptor(picassoInterceptor) + } + + } +} + +private class PicassoOkHttpLogger : HttpLoggingInterceptor.Logger { + override fun log(message: String) { + Timber.d(message) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt index 1f4897a487..fe202ed0ca 100644 --- a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -5,38 +5,41 @@ import android.graphics.drawable.Drawable import com.squareup.picasso.Picasso import com.squareup.picasso.Target import com.tangem.common.services.Result -import timber.log.Timber /** [REDACTED_AUTHOR] */ class UrlBitmapLoader { - fun loadBitmap(url: String, callback: (Result) -> Unit) { - Picasso.get().load(url).into(DownloadTarget(callback)) + val target = DownloadTarget(callback) + protectedFromGarbageCollectorTargets.add(target) + Picasso.get().load(url).into(target) } fun loadBitmap(url: String, target: DownloadTarget) { + protectedFromGarbageCollectorTargets.add(target) Picasso.get().load(url).into(target) } + } +private val protectedFromGarbageCollectorTargets = mutableListOf() + // It adds the ability to trigger multiple downloads with a unique callback. open class DownloadTarget( val callback: (Result) -> Unit, ) : Target { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { - Timber.d("onBitmapLoaded") callback(Result.Success(bitmap)) + protectedFromGarbageCollectorTargets.remove(this) } override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) { - Timber.d("onBitmapFailed") callback.invoke(Result.Failure(e)) + protectedFromGarbageCollectorTargets.remove(this) } override fun onPrepareLoad(placeHolderDrawable: Drawable?) { - Timber.d("onPrepareLoad") } } \ No newline at end of file From 3658f492403549e32051718d0d69508bd3b06b2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Sep 2021 06:49:52 +0300 Subject: [PATCH 23/55] Updated on 2026-08-14 --- app/build.gradle | 6 +++--- .../com/tangem/tap/features/details/redux/DetailsReducer.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 923c2c5326..e33f448654 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,9 +76,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-38' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-78' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-78' + implementation 'com.tangem:blockchain:develop-41' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-79' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-79' // WebView implementation "androidx.browser:browser:1.3.0" 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 62253c27f2..61b94447a1 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 @@ -185,7 +185,7 @@ private fun handleSecurityAction( private fun prepareAllowedSecurityOptions( card: Card?, currentSecurityOption: SecurityOption? ): EnumSet { - val prohibitDefaultPin = card?.settings?.isRemovingAccessCodeAllowed != true + val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java) if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) { From 0ec0d790280977f5575f28060666a69c0365b3b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Sep 2021 07:07:40 +0300 Subject: [PATCH 24/55] Updated on 2026-08-14 --- .../com/tangem/tap/features/onboarding/OnboardingService.kt | 4 ++-- .../java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt index c54cfc75e8..2fc0925f2e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt @@ -49,11 +49,11 @@ class OnboardingService( if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) { if (hasWallets) { - cardInfoStorage.activated(card.cardId) + cardInfoStorage.activate(card.cardId) onFailedToProceedToOnboardingCase?.invoke(response) return } else { - cardInfoStorage.activated(card.cardId) // вызвать только после того, как был создан кошелек + cardInfoStorage.activate(card.cardId) // вызвать только после того, как был создан кошелек onboardingDataLinker.navigateTo = AppScreen.OnboardingOther } } diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index b6ebfd8a32..f903e6425e 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -48,7 +48,7 @@ class UsedCardsPrefStorage( return findCardInfo(cardId)?.isActivationStarted ?: false } - fun activated(cardId: String) { + fun activate(cardId: String) { val restoredList = restore() val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivated = true) ?: UsedCardInfo(cardId, isActivated = true) From 9034dcd682ed1cf74f097f9b0609c498c1f78e4f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Sep 2021 01:05:15 +0300 Subject: [PATCH 25/55] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/common/entities/Button.kt | 6 +++--- .../toggleWidget/IndeterminateProgressButtonWidget.kt | 7 ++++--- .../com/tangem/tap/common/toggleWidget/ViewStateWidget.kt | 5 ----- .../send/ui/stateSubscribers/SendStateSubscriber.kt | 1 - .../com/tangem/tap/features/wallet/redux/WalletState.kt | 3 ++- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt index 3255e490ca..a2d4127c8f 100644 --- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.entities -import com.tangem.tap.common.toggleWidget.ProgressState import com.tangem.tap.features.send.redux.states.ButtonState +import com.tangem.tap.features.wallet.redux.ProgressState open class Button(val enabled: Boolean) @@ -11,7 +11,7 @@ open class IndeterminateProgressButton( val progressState: ProgressState get() = when (state) { - ButtonState.PROGRESS -> ProgressState.Progress() - else -> ProgressState.None + ButtonState.PROGRESS -> ProgressState.Loading + else -> ProgressState.Done } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt index 153f7c662d..b4eacfff4e 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt @@ -5,6 +5,7 @@ import android.view.View import com.google.android.material.button.MaterialButton import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.wallet.redux.ProgressState /** [REDACTED_AUTHOR] @@ -12,7 +13,7 @@ import com.tangem.tap.common.extensions.show open class IndeterminateProgressButtonWidget( private val button: MaterialButton, private val progress: View, - initialState: ProgressState = ProgressState.None + initialState: ProgressState = ProgressState.Done ) : ViewStateWidget { private var initialButtonText: CharSequence = button.text @@ -29,8 +30,8 @@ open class IndeterminateProgressButtonWidget( val progressState = state as? ProgressState ?: return when (progressState) { - is ProgressState.None -> switchToNone() - is ProgressState.Progress -> switchToProgress() + ProgressState.Done, ProgressState.Error -> switchToNone() + ProgressState.Loading -> switchToProgress() } } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt index 0c06410ace..48ba0a7291 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt @@ -11,9 +11,4 @@ interface WidgetState interface ViewStateWidget { val mainView: View fun changeState(state: WidgetState) -} - -sealed class ProgressState : WidgetState { - object None : ProgressState() - data class Progress(val progress: Int = 0) : ProgressState() } \ No newline at end of file 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 39b4eefe8f..fb2633ff3a 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 @@ -10,7 +10,6 @@ import androidx.core.text.bold import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.getMessageString import com.tangem.tap.common.text.DecimalDigitsInputFilter -import com.tangem.tap.common.toggleWidget.ProgressState import com.tangem.tap.domain.MultiMessageError import com.tangem.tap.domain.assembleErrors import com.tangem.tap.features.send.BaseStoreFragment 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 cdabd1500c..f57259ac3c 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 @@ -8,6 +8,7 @@ import com.tangem.tap.common.entities.Button import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.CryptoCurrencyName +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 @@ -146,7 +147,7 @@ sealed class WalletDialog: StateDialog { object ChooseTradeActionDialog : WalletDialog() } -enum class ProgressState { Loading, Done, Error } +enum class ProgressState: WidgetState { Loading, Done, Error } enum class ErrorType { NoInternetConnection } From 318efa8d214bcdf8ea42da1e8da8d527965a5f08 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 2 Oct 2021 18:01:56 +0300 Subject: [PATCH 26/55] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt index fe202ed0ca..6a63eaf0b8 100644 --- a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -2,6 +2,8 @@ package com.tangem.tap.domain import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.os.Handler +import android.os.Looper import com.squareup.picasso.Picasso import com.squareup.picasso.Target import com.tangem.common.services.Result @@ -10,10 +12,12 @@ import com.tangem.common.services.Result [REDACTED_AUTHOR] */ class UrlBitmapLoader { + private val mainHandler = Handler(Looper.getMainLooper()) + fun loadBitmap(url: String, callback: (Result) -> Unit) { val target = DownloadTarget(callback) protectedFromGarbageCollectorTargets.add(target) - Picasso.get().load(url).into(target) + mainHandler.post { Picasso.get().load(url).into(target) } } fun loadBitmap(url: String, target: DownloadTarget) { From baaa3173811008e13951f853497fa77c6a6a6582 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Oct 2021 17:20:16 +0300 Subject: [PATCH 27/55] Updated on 2026-08-14 --- .../tap/common/extensions/WalletManager.kt | 32 +++++++++++++++ .../com/tangem/tap/domain/TapWalletManager.kt | 40 +++++++++---------- 2 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt new file mode 100644 index 0000000000..d8b5b136dd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -0,0 +1,32 @@ +package com.tangem.tap.common.extensions + +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.services.Result +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.amountToCreateAccount +import com.tangem.tap.domain.extensions.isNoAccountError +import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.network.NetworkConnectivity + +/** +[REDACTED_AUTHOR] + */ +suspend fun WalletManager.loadWalletData(): Result = try { + update() + Result.Success(wallet) +} catch (exception: Exception) { + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + Result.Failure(TapError.NoInternetConnection) + } else { + val blockchain = wallet.blockchain + val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken()) + + if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) { + Result.Failure(TapError.NoAccount(amountToCreateAccount.toString())) + } else { + val message = exception.localizedMessage ?: "Unknown exception during WalletManager update" + Result.Failure(TapError.CustomError(message)) + } + } +} \ 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 0193a9d2a1..6e4dec24f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -5,6 +5,8 @@ import com.tangem.common.card.Card import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.loadWalletData import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository @@ -38,13 +40,7 @@ class TapWalletManager { by lazy { WalletManagerFactory(blockchainSdkConfig) } suspend fun loadWalletData(walletManager: WalletManager) { - val result = try { - walletManager.update() - Result.Success(walletManager.wallet) - } catch (exception: Exception) { - Result.Failure(exception) - } - handleUpdateWalletResult(result, walletManager) + handleUpdateWalletResult(walletManager.loadWalletData(), walletManager) } suspend fun updateWallet(walletManager: WalletManager) { @@ -261,23 +257,23 @@ class TapWalletManager { when (result) { is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data)) is Result.Failure -> { - if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) - return@withContext - } - val error = result.error - val blockchain = walletManager.wallet.blockchain - if (error != null && blockchain.isNoAccountError(error)) { - val token = walletManager.wallet.getFirstToken() - val amountToCreateAccount = blockchain.amountToCreateAccount(token) - if (amountToCreateAccount != null) { - store.dispatch(WalletAction.LoadWallet.NoAccount( - walletManager.wallet, - amountToCreateAccount.toString())) - return@withContext + val error = (result.error as? TapError) ?: TapError.UnknownError + when (error) { + TapError.NoInternetConnection -> { + store.dispatch(WalletAction.LoadData.Failure(error)) + } + is TapError.NoAccount -> { + store.dispatch(WalletAction.LoadWallet + .NoAccount(walletManager.wallet, error.amountToCreateAccount)) + } + is TapError.CustomError -> { + store.dispatch(WalletAction.LoadWallet + .Failure(walletManager.wallet, error.customMessage)) + } + else -> { + store.dispatchDebugErrorNotification(error) } } - store.dispatch(WalletAction.LoadWallet.Failure(walletManager.wallet, result.error?.localizedMessage)) } } } From ca51d6803aa9cf9dfa8d29f3a8c516fa1c943618 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:12:14 +0300 Subject: [PATCH 28/55] Updated on 2026-08-14 --- app/build.gradle | 8 +- .../tap/common/extensions/Collections.kt | 9 +- .../tangem/tap/common/extensions/Coroutine.kt | 16 +++ .../tap/common/extensions/Navigation.kt | 2 +- .../com/tangem/tap/common/extensions/Store.kt | 22 ++++ .../tangem/tap/common/extensions/ViewGroup.kt | 8 +- .../redux/navigation/NavigationAction.kt | 4 +- .../toggleWidget/RefreshBalanceWidget.kt | 100 ++++++++++++++++++ .../transitions/OnboardingTransitions.kt | 33 ++++-- .../com/tangem/tap/domain/UrlBitmapLoader.kt | 4 +- .../tap/features/wallet/redux/WalletState.kt | 2 + .../tap/persistence/PreferencesStorage.kt | 7 +- .../tap/persistence/UsedCardsPrefStorage.kt | 19 ++-- 13 files changed, 198 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt create mode 100644 app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt diff --git a/app/build.gradle b/app/build.gradle index e33f448654..69493ced41 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,9 +76,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-41' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-79' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-79' + implementation 'com.tangem:blockchain:develop-42' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-80' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-80' // WebView implementation "androidx.browser:browser:1.3.0" @@ -122,6 +122,8 @@ dependencies { // //Crypto implementation "com.madgag.spongycastle:core:1.58.0.0" + // animation + implementation "com.airbnb.android:lottie:3.4.0" testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.1.3" diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt index b264485c44..8492ae44f1 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt @@ -16,9 +16,16 @@ fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean { return toRemove.isNotEmpty() } -fun MutableList.replaceBy(item: T, predicate: (T) -> Boolean) { +fun MutableList.replaceBy(item: T, predicate: (T) -> Boolean): Boolean { val toRemove = this.filter(predicate) + if (toRemove.isEmpty()) return false + val indexes = toRemove.map { indexOf(it) } this.removeAll(toRemove) indexes.forEach { this.add(it, item) } + return true +} + +fun MutableList.replaceByOrAdd(item: T, predicate: (T) -> Boolean) { + if (!replaceBy(item, predicate)) add(item) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt new file mode 100644 index 0000000000..63c62dfdc2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.extensions + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** +[REDACTED_AUTHOR] + */ +suspend fun withMainContext(block: suspend CoroutineScope.() -> T) { + withContext(Dispatchers.Main, block) +} + +suspend fun withIOContext(block: suspend CoroutineScope.() -> T) { + withContext(Dispatchers.IO, block) +} \ No newline at end of file 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 3a119fd683..02c794329c 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -17,7 +17,7 @@ import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFra 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.old.OnboardingOtherCardsFragment +import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment 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 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 06cff9e110..f5fd832f2b 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 @@ -3,6 +3,7 @@ package com.tangem.tap.common.extensions 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.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -32,6 +33,27 @@ fun Store<*>.dispatchToastNotification(resId: Int) { } } + +fun Store<*>.dispatchErrorNotification(error: TapError) { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.ShowErrorNotification(error)) + } +} + +/** + * @param fatal used to indicate errors that should not normally have occurred + */ +fun Store<*>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) { + val prefix = if (fatal) "FATAL ERROR: " else "DEBUG ERROR: " + dispatchDebugErrorNotification(TapError.CustomError("$prefix $message")) +} + +fun Store<*>.dispatchDebugErrorNotification(error: TapError) { + scope.launch(Dispatchers.Main) { + store.dispatch(GlobalAction.DebugShowErrorNotification(error)) + } +} + fun Store<*>.dispatchDialogShow(dialog: StateDialog) { scope.launch(Dispatchers.Main) { store.dispatch(GlobalAction.ShowDialog(dialog)) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt index fb9c20bfdb..81be964cb1 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.extensions import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.view.ViewParent import androidx.core.view.forEach @@ -15,11 +16,8 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -fun ViewGroup.inflate(viewToInflate: Int, rootView: ViewGroup?, parent: ViewGroup) { - if (rootView == null) { - val inflatedView = LayoutInflater.from(context).inflate(viewToInflate, rootView) - parent.addView(inflatedView) - } +fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View { + return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot) } fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) { 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 d77f97b263..331dc07e1a 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 @@ -9,8 +9,8 @@ import java.lang.ref.WeakReference sealed class NavigationAction : Action { data class NavigateTo( val screen: AppScreen, - val addToBackstack: Boolean = true, - val fragmentShareTransition: FragmentShareTransition? = null + val fragmentShareTransition: FragmentShareTransition? = null, + val addToBackstack: Boolean = true ) : NavigationAction() data class PopBackTo(val screen: AppScreen? = null) : NavigationAction() 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 new file mode 100644 index 0000000000..e7ef08e026 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -0,0 +1,100 @@ +package com.tangem.tap.common.toggleWidget + +import android.view.View +import android.view.ViewGroup +import android.view.animation.* +import android.widget.ViewSwitcher +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class RefreshBalanceWidget( + private val root: ViewGroup +) : ViewStateWidget { + + var isShowing: Boolean? = null + private set + + override val mainView: View = root.findViewById(R.id.btn_refresh_balance) + + + private val viewSwitcher: ViewSwitcher by lazy { mainView.findViewById(R.id.switcher_refresh_arrow_to_progress) } + + private val arrowView = mainView.findViewById(R.id.imv_arrow_refresh) + + private var progressViewAnimation: Animation? = null + + init { + viewSwitcher.inAnimation = AlphaAnimation(0f, 1f).apply { + duration = 400 + interpolator = LinearInterpolator() + } + viewSwitcher.outAnimation = AlphaAnimation(1f, 0f).apply { + duration = 400 + interpolator = LinearInterpolator() + } + } + + override fun changeState(state: WidgetState) { + val progressState = state as? ProgressState ?: return + + val currentStateByView = getState() + + if (progressState == currentStateByView) return + + animateState(progressState) + viewSwitcher.showNext() + + } + + private fun animateState(state: ProgressState) { + when (state) { + ProgressState.Done, ProgressState.Error -> { + progressViewAnimation?.cancel() + } + ProgressState.Loading -> { + val animation = 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 + } + } + } + + fun show(show: Boolean) { + if (show == isShowing) return + + isShowing = show + if (show) { + mainView.startAnimation(ShowAnimation()) + } else { + mainView.startAnimation(AlphaAnimation(1f, 0f).apply { fillAfter = true }) + } + } + + private fun isArrowRefreshActive(): Boolean = viewSwitcher.currentView.id == R.id.fl_arrow_refresh_container + + private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading +} + +class ShowAnimation : AnimationSet(true) { + init { + addAnimation(ScaleAnimation( + 0f, 1f, + 0f, 1f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f) + ) + addAnimation(AlphaAnimation(0f, 1f)) + duration = 300 + interpolator = AnticipateOvershootInterpolator() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt index 7d9334e123..32785f0147 100644 --- a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt +++ b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt @@ -1,8 +1,6 @@ package com.tangem.tap.common.transitions -import androidx.transition.ChangeBounds -import androidx.transition.ChangeImageTransform -import androidx.transition.TransitionSet +import androidx.transition.* /** [REDACTED_AUTHOR] @@ -10,18 +8,37 @@ import androidx.transition.TransitionSet class FrontCardEnterTransition : TransitionSet() { init { ordering = ORDERING_TOGETHER; - addTransition(ChangeImageTransform()) - addTransition(ChangeBounds()) -// addTransition(Fade()) -// addTransition(Fade(Visibility.MODE_IN)) -// addTransition(ChangeTransform()) + addTransition(Fade()) + addTransition(ChangeImageTransform()) // изменения внутри ImageView + addTransition(ChangeTransform()) // изменение размеров, углов накона + addTransition(ChangeBounds()) // изменение положения } } class FrontCardExitTransition : TransitionSet() { init { ordering = ORDERING_TOGETHER; + addTransition(Fade()) addTransition(ChangeImageTransform()) + addTransition(ChangeTransform()) addTransition(ChangeBounds()) } +} + +class HomeToOnboardingTransition : TransitionSet() { + init { + ordering = ORDERING_TOGETHER; + addTransition(Fade()) + addTransition(ChangeTransform()) + addTransition(ChangeBounds()) + } +} + +class InternalNoteLayoutTransition : TransitionSet() { + init { + ordering = ORDERING_TOGETHER; + addTransition(ChangeTransform()) + addTransition(ChangeBounds()) + addTransition(Fade()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt index 6a63eaf0b8..c06d9b0001 100644 --- a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -39,8 +39,8 @@ open class DownloadTarget( protectedFromGarbageCollectorTargets.remove(this) } - override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) { - callback.invoke(Result.Failure(e)) + override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { + callback.invoke(Result.Failure(e ?: Exception("Unknown exception"))) protectedFromGarbageCollectorTargets.remove(this) } 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 f57259ac3c..6c3db01f3c 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 @@ -182,6 +182,8 @@ data class Artwork( const val MARTA_CARD_ID = "BC02" const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" + + const val TEMP_CARDANO = "https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659" } } 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 77c8be322d..12d5eaa695 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -102,13 +102,16 @@ class AppRatingLaunchObserver( private val K_FUNDS_FOUND_DATE = "fundsFoundDate" private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" + private val FUNDS_FOUND_DATE_UNDEFINED = -1L private val deferShowing = 20 private val firstShowing = 3 private var fundsFoundDate: Calendar? = null init { - val dateTimeMs = preferences.getLong(K_FUNDS_FOUND_DATE, -1) - if (dateTimeMs > 0) fundsFoundDate = Calendar.getInstance().apply { timeInMillis = dateTimeMs } + val msWhenFundsWasFound = preferences.getLong(K_FUNDS_FOUND_DATE, FUNDS_FOUND_DATE_UNDEFINED) + if (msWhenFundsWasFound != FUNDS_FOUND_DATE_UNDEFINED) { + fundsFoundDate = Calendar.getInstance().apply { timeInMillis = msWhenFundsWasFound } + } } fun foundWalletWithFunds() { diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index f903e6425e..17238689f5 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.common.extensions.replaceBy +import com.tangem.tap.common.extensions.replaceByOrAdd import timber.log.Timber /** @@ -48,18 +48,14 @@ class UsedCardsPrefStorage( return findCardInfo(cardId)?.isActivationStarted ?: false } - fun activate(cardId: String) { + fun activationFinished(cardId: String) { val restoredList = restore() - val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivated = true) - ?: UsedCardInfo(cardId, isActivated = true) + val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = false) + ?: UsedCardInfo(cardId, isActivationStarted = false) save(foundItem, restoredList) } - fun wasActivated(cardId: String): Boolean { - return findCardInfo(cardId)?.isActivated ?: false - } - private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? { val findInList = list ?: restore() return findInList.firstOrNull { it.cardId == cardId } @@ -68,7 +64,7 @@ class UsedCardsPrefStorage( private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList) { val info = usedCardInfo ?: return - usedCardsInfo.replaceBy(info) { it.cardId == info.cardId } + usedCardsInfo.replaceByOrAdd(info) { it.cardId == info.cardId } save(usedCardsInfo) } @@ -86,13 +82,13 @@ class UsedCardsPrefStorage( private fun restore(): MutableList { val json = preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf() return try { - jsonConverter.fromJson(json)!! + jsonConverter.fromJson(json, jsonConverter.typedList(UsedCardInfo::class.java))!! } catch (ex: Exception) { preferences.edit(true) { remove(USED_CARDS_INFO) } mutableListOf() } } - + companion object { private const val USED_CARDS_INFO = "usedCardsInfo" private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" @@ -103,5 +99,4 @@ private data class UsedCardInfo( val cardId: String, val isScanned: Boolean = false, val isActivationStarted: Boolean = false, - val isActivated: Boolean = false, ) \ No newline at end of file From 3abd3268ff302f776c99dc94c925edbece441f30 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:15:59 +0300 Subject: [PATCH 29/55] Updated on 2026-08-14 --- .../res/drawable/anim_progress_balance.xml | 9 + app/src/main/res/drawable/bg_circle_gray.xml | 9 - .../res/drawable/ic_activation_success.xml | 24 +++ .../main/res/drawable/ic_arrow_refresh.xml | 9 + app/src/main/res/drawable/ic_copy_green.xml | 19 ++ .../main/res/drawable/ic_home_back_card.xml | 102 +++++++++++ .../main/res/drawable/ic_home_card_back.xml | 108 ----------- .../main/res/drawable/ic_home_card_front.xml | 126 ------------- .../main/res/drawable/ic_home_front_card.xml | 118 ++++++++++++ .../main/res/drawable/ic_progress_balance.xml | 10 ++ app/src/main/res/drawable/ic_share_green.xml | 12 ++ app/src/main/res/drawable/ripple.xml | 32 ++++ app/src/main/res/drawable/shape_circle.xml | 11 +- .../drawable/shape_rectangle_rounded_100.xml | 8 + .../drawable/shape_rectangle_rounded_8.xml | 8 + .../res/drawable/shape_refresh_button.xml | 15 ++ .../layout/dialog_onboarding_address_info.xml | 112 ++++++++++++ app/src/main/res/layout/fragment_home.xml | 170 ++++++------------ .../main/res/layout/fragment_onboarding.xml | 160 ----------------- .../res/layout/fragment_onboarding_main.xml | 57 ++++++ .../layout_onboarding_bottom_action_views.xml | 81 +++++++++ .../res/layout/layout_onboarding_note.xml | 85 +++++++++ .../main/res/layout/layout_pseudo_toolbar.xml | 23 +++ app/src/main/res/layout/layout_token.xml | 2 +- .../layout/lp_onboarding_create_wallet.xml | 85 +++++++++ .../main/res/layout/lp_onboarding_done.xml | 83 +++++++++ .../res/layout/lp_onboarding_topup_wallet.xml | 83 +++++++++ app/src/main/res/layout/view_confetti.xml | 13 ++ .../view_onboarding_refresh_balance.xml | 62 +++++++ .../res/layout/view_onboarding_tv_balance.xml | 27 +++ app/src/main/res/raw/anim_confetti.json | 1 + app/src/main/res/values/colors.xml | 4 +- .../main/res/values/strings_untranslated.xml | 1 + app/src/main/res/values/styles.xml | 20 ++- 34 files changed, 1166 insertions(+), 523 deletions(-) create mode 100644 app/src/main/res/drawable/anim_progress_balance.xml delete mode 100644 app/src/main/res/drawable/bg_circle_gray.xml create mode 100644 app/src/main/res/drawable/ic_activation_success.xml create mode 100644 app/src/main/res/drawable/ic_arrow_refresh.xml create mode 100644 app/src/main/res/drawable/ic_copy_green.xml create mode 100644 app/src/main/res/drawable/ic_home_back_card.xml delete mode 100644 app/src/main/res/drawable/ic_home_card_back.xml delete mode 100644 app/src/main/res/drawable/ic_home_card_front.xml create mode 100644 app/src/main/res/drawable/ic_home_front_card.xml create mode 100644 app/src/main/res/drawable/ic_progress_balance.xml create mode 100644 app/src/main/res/drawable/ic_share_green.xml create mode 100644 app/src/main/res/drawable/ripple.xml create mode 100644 app/src/main/res/drawable/shape_rectangle_rounded_100.xml create mode 100644 app/src/main/res/drawable/shape_rectangle_rounded_8.xml create mode 100644 app/src/main/res/drawable/shape_refresh_button.xml create mode 100644 app/src/main/res/layout/dialog_onboarding_address_info.xml delete mode 100644 app/src/main/res/layout/fragment_onboarding.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_main.xml create mode 100644 app/src/main/res/layout/layout_onboarding_bottom_action_views.xml create mode 100644 app/src/main/res/layout/layout_onboarding_note.xml create mode 100644 app/src/main/res/layout/layout_pseudo_toolbar.xml create mode 100644 app/src/main/res/layout/lp_onboarding_create_wallet.xml create mode 100644 app/src/main/res/layout/lp_onboarding_done.xml create mode 100644 app/src/main/res/layout/lp_onboarding_topup_wallet.xml create mode 100644 app/src/main/res/layout/view_confetti.xml create mode 100644 app/src/main/res/layout/view_onboarding_refresh_balance.xml create mode 100644 app/src/main/res/layout/view_onboarding_tv_balance.xml create mode 100644 app/src/main/res/raw/anim_confetti.json diff --git a/app/src/main/res/drawable/anim_progress_balance.xml b/app/src/main/res/drawable/anim_progress_balance.xml new file mode 100644 index 0000000000..ada4670265 --- /dev/null +++ b/app/src/main/res/drawable/anim_progress_balance.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_circle_gray.xml b/app/src/main/res/drawable/bg_circle_gray.xml deleted file mode 100644 index 94a585da83..0000000000 --- a/app/src/main/res/drawable/bg_circle_gray.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_activation_success.xml b/app/src/main/res/drawable/ic_activation_success.xml new file mode 100644 index 0000000000..7957c82493 --- /dev/null +++ b/app/src/main/res/drawable/ic_activation_success.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_refresh.xml b/app/src/main/res/drawable/ic_arrow_refresh.xml new file mode 100644 index 0000000000..1acc645cec --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_copy_green.xml b/app/src/main/res/drawable/ic_copy_green.xml new file mode 100644 index 0000000000..a4145be642 --- /dev/null +++ b/app/src/main/res/drawable/ic_copy_green.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/ic_home_back_card.xml b/app/src/main/res/drawable/ic_home_back_card.xml new file mode 100644 index 0000000000..f15c47ba3f --- /dev/null +++ b/app/src/main/res/drawable/ic_home_back_card.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_home_card_back.xml b/app/src/main/res/drawable/ic_home_card_back.xml deleted file mode 100644 index 8a71b20ce5..0000000000 --- a/app/src/main/res/drawable/ic_home_card_back.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_home_card_front.xml b/app/src/main/res/drawable/ic_home_card_front.xml deleted file mode 100644 index 3bbf5a93dd..0000000000 --- a/app/src/main/res/drawable/ic_home_card_front.xml +++ /dev/null @@ -1,126 +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 new file mode 100644 index 0000000000..19969e806e --- /dev/null +++ b/app/src/main/res/drawable/ic_home_front_card.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_progress_balance.xml b/app/src/main/res/drawable/ic_progress_balance.xml new file mode 100644 index 0000000000..d31defe1e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_progress_balance.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_green.xml b/app/src/main/res/drawable/ic_share_green.xml new file mode 100644 index 0000000000..57ee232b90 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_green.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ripple.xml b/app/src/main/res/drawable/ripple.xml new file mode 100644 index 0000000000..c6e07a265f --- /dev/null +++ b/app/src/main/res/drawable/ripple.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle.xml b/app/src/main/res/drawable/shape_circle.xml index f857819f4d..59a662d141 100644 --- a/app/src/main/res/drawable/shape_circle.xml +++ b/app/src/main/res/drawable/shape_circle.xml @@ -1,6 +1,9 @@ - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_100.xml b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml new file mode 100644 index 0000000000..7f26fb8be5 --- /dev/null +++ b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml new file mode 100644 index 0000000000..a788f39093 --- /dev/null +++ b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_refresh_button.xml b/app/src/main/res/drawable/shape_refresh_button.xml new file mode 100644 index 0000000000..ac9508438d --- /dev/null +++ b/app/src/main/res/drawable/shape_refresh_button.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml new file mode 100644 index 0000000000..92c780e969 --- /dev/null +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index f8d352de67..710f915add 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,126 +1,72 @@ - + android:layout_height="match_parent" + android:background="@color/backgroundWhite" + android:fitsSystemWindows="true" + android:orientation="vertical"> - + android:fitsSystemWindows="false" + app:liftOnScroll="true"> - + - + - + - + - + - + - + - + - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding.xml b/app/src/main/res/layout/fragment_onboarding.xml deleted file mode 100644 index 6924a50981..0000000000 --- a/app/src/main/res/layout/fragment_onboarding.xml +++ /dev/null @@ -1,160 +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 new file mode 100644 index 0000000000..bfd04532b4 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml b/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml new file mode 100644 index 0000000000..f9542e5c56 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_note.xml b/app/src/main/res/layout/layout_onboarding_note.xml new file mode 100644 index 0000000000..9804063722 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_note.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_pseudo_toolbar.xml b/app/src/main/res/layout/layout_pseudo_toolbar.xml new file mode 100644 index 0000000000..f9ae5fe2ab --- /dev/null +++ b/app/src/main/res/layout/layout_pseudo_toolbar.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/src/main/res/layout/layout_token.xml b/app/src/main/res/layout/layout_token.xml index 8e03d11ca1..fc9486f79c 100644 --- a/app/src/main/res/layout/layout_token.xml +++ b/app/src/main/res/layout/layout_token.xml @@ -20,7 +20,7 @@ android:id="@+id/v_token_background" android:layout_width="match_parent" android:layout_height="0dp" - android:background="@color/backgroundGray" + android:background="@color/backgroundLightGray" app:layout_constraintTop_toBottomOf="@id/tv_token_title" app:layout_constraintBottom_toBottomOf="@id/tv_token_fiat_amount"/> diff --git a/app/src/main/res/layout/lp_onboarding_create_wallet.xml b/app/src/main/res/layout/lp_onboarding_create_wallet.xml new file mode 100644 index 0000000000..7bccae6a3e --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done.xml b/app/src/main/res/layout/lp_onboarding_done.xml new file mode 100644 index 0000000000..803e41075e --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 0000000000..ab70711896 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_confetti.xml b/app/src/main/res/layout/view_confetti.xml new file mode 100644 index 0000000000..ad07b6e0bd --- /dev/null +++ b/app/src/main/res/layout/view_confetti.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_refresh_balance.xml b/app/src/main/res/layout/view_onboarding_refresh_balance.xml new file mode 100644 index 0000000000..c9bf3aacb2 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_refresh_balance.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_tv_balance.xml b/app/src/main/res/layout/view_onboarding_tv_balance.xml new file mode 100644 index 0000000000..3c353e9948 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_tv_balance.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/anim_confetti.json b/app/src/main/res/raw/anim_confetti.json new file mode 100644 index 0000000000..f2d1e7f974 --- /dev/null +++ b/app/src/main/res/raw/anim_confetti.json @@ -0,0 +1 @@ +{"v":"5.6.3","fr":30,"ip":0,"op":120,"w":1242,"h":2688,"nm":"Comp 1","ddd":1,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[692,1011.9999999999999,0],"ix":2},"a":{"a":0,"k":[71,-332,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-48,204],[72.555,109.763],[-9.41,53.993],[11.376,60.133],[-20.778,48.214],[-43.71,34.149],[-28,80]],"o":[[48,-204],[-46.308,-70.056],[6.44,-36.951],[-18.438,-97.46],[10.775,-25.002],[128,-100],[28,-80]],"v":[[71,-332],[151,-620],[209.484,-736.613],[141,-856],[199.096,-959.097],[193,-1114],[371,-1280]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":46.384765625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11.923,"s":[0]},{"t":42.0771484375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":2,"op":112,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[648,1040,0],"ix":2},"a":{"a":0,"k":[27,-304,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[28,112],[154,-50],[122,52],[60,164]],"o":[[0,0],[-24,-96],[-150.386,48.827],[-141.68,-60.388],[-60,-164]],"v":[[27,-304],[-107,-530],[-249,-720],[-335,-908],[-537,-1284]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":27,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[674,1228,0],"ix":2},"a":{"a":0,"k":[53,-116,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-34,-98],[21,-81],[-20,-38],[-18,-48],[-8,-34],[-62,-38],[-24,-36],[-62,-30],[-84,-22]],"o":[[34,98],[-11.008,42.46],[12.721,24.17],[18,48],[9.222,39.192],[62,38],[24,36],[62,30],[84,22]],"v":[[53,-116],[134,25],[94,107],[181,157],[150,265],[237,323],[250,463],[383,540],[480,694]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.03137254901960784,0.807843137254902,0.3607843137254902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22.692,"s":[0]},{"t":55,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.538,"s":[0]},{"t":50.6923828125,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":6,"op":112,"st":6,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[704,1180,0],"ix":2},"a":{"a":0,"k":[83,-164,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-38,-60],[-40.126,23.279],[42.5,-0.5],[-35,2.5],[-47.28,-26.863],[-20,-30],[-21.113,-24.13],[-34,-42],[-16,-40]],"o":[[38,60],[65.5,-38],[-20.024,0.236],[41.06,-2.933],[44,25],[20,30],[14,16],[34,42],[16,40]],"v":[[83,-164],[166.5,-75],[217.5,-41.5],[222.5,-81.5],[377,-77],[378,1],[458,17],[478,91],[616,178]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":31.3076171875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-3,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[730,1100,0],"ix":2},"a":{"a":0,"k":[109,-244,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-72,-10],[-51,10],[13,-16],[-24.905,-7.729],[-10,10]],"o":[[72,10],[51,-10],[-10.168,12.514],[29,9],[10,-10]],"v":[[109,-244],[483,-178],[533,-129],[556,-83],[618,-93]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16.23,"s":[0]},{"t":48.5380859375,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":44.23046875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":112,"st":3,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[678,1086,0],"ix":2},"a":{"a":0,"k":[57,-258,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-106,94],[-78,82],[-2,37],[-15,15],[-27.82,25.594],[-23,24],[-38.877,17.279],[-24,-14]],"o":[[106,-94],[78,-82],[2.405,-44.488],[18.028,-18.028],[25,-23],[19.594,-20.446],[36,-16],[24,14]],"v":[[57,-258],[204,-404],[337,-493],[346,-597],[426,-624],[454,-699],[542,-728],[611,-805]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":31.3076171875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-3,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[640,1170,0],"ix":2},"a":{"a":0,"k":[19,-174,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[92,-22],[38,-38],[78,-8],[38,-10],[10,-20]],"o":[[-92,22],[-38,38],[-78,8],[-38,10],[-10,20]],"v":[[19,-174],[-115,-120],[-283,-46],[-423,-50],[-517,32]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5.462,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[661,1191,0],"ix":2},"a":{"a":0,"k":[40,-153,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.398,-37.618],[29.992,-37.49],[41,-30],[60.195,-37.77],[80.591,1.465],[-31.635,-5.678],[109.189,24.816],[22,-30]],"o":[[-34,78],[-44,55],[-59.359,43.434],[-51,32],[-55,-1],[39,7],[-132,-30],[-22,30]],"v":[[40,-153],[-41,-58],[-104,84],[-253,140],[-359,228],[-381,175],[-459,204],[-652,321]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":18.385,"s":[0]},{"t":50.6923828125,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":42.0771484375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":2,"op":112,"st":2,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[646,1126,0],"ix":2},"a":{"a":0,"k":[25,-218,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[90,52],[20,-18],[44,46],[60,-36],[34,64],[-46,-10],[34,-50],[52,12],[28,10]],"o":[[-90,-52],[-20,18],[-44,-46],[-60,36],[-34,-64],[46,10],[-34,50],[-52,-12],[-28,-10]],"v":[[25,-218],[-175,-248],[-271,-286],[-379,-328],[-487,-310],[-535,-444],[-469,-396],[-547,-342],[-627,-366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5.462,"s":[0]},{"t":37.76953125,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":184,"ix":10},"p":{"a":0,"k":[633,1188,0],"ix":2},"a":{"a":0,"k":[12,-198,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":0,"bm":0},{"ddd":1,"ind":11,"ty":4,"nm":"Shape Layer 41","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[3.99,-626.075,396.962],"ti":[-3.99,626.075,-396.962]},{"t":34.7802734375,"s":[30.337,-3944.458,2381.774]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":12,"ty":4,"nm":"Shape Layer 40","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[172.235,-647.46,153.434],"ti":[-172.235,647.46,-153.434]},{"t":80,"s":[1039.806,-4072.764,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":13,"ty":4,"nm":"Shape Layer 39","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0.179,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[140.553,-166.199,-42.398],"ti":[-140.553,166.199,42.398]},{"t":15.0078125,"s":[849.715,-1185.203,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":14,"ty":3,"nm":"Null 2","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-49,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":-4,"bm":0},{"ddd":0,"ind":15,"ty":3,"nm":"Null 3","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":82,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":-4,"bm":0},{"ddd":1,"ind":16,"ty":4,"nm":"Shape Layer 105","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[727.321,51.433,435.455],"ti":[-727.321,-51.433,-435.455]},{"t":34.544921875,"s":[4250.962,186.167,2612.729]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":17,"ty":4,"nm":"Shape Layer 104","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[527.848,55.985,153.434],"ti":[-527.848,-55.985,-153.434]},{"t":54.552734375,"s":[3054.124,213.477,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":18,"ty":4,"nm":"Shape Layer 103","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[4.145,138.496,-42.398],"ti":[-4.145,-138.496,42.398]},{"t":15.0078125,"s":[-88.093,708.543,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":19,"ty":4,"nm":"Shape Layer 102","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-0.179,"s":[-112.965,-122.431,0],"to":[-61.615,187.567,26.907],"ti":[61.615,-187.567,-26.907]},{"t":31.0732421875,"s":[-482.654,1002.974,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":20,"ty":4,"nm":"Shape Layer 101","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.824},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-90.863,90.863,0],"ti":[88.083,-357.903,-768.642]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.164},"t":17.479,"s":[-658.144,422.748,0],"to":[-84.157,341.951,734.383],"ti":[-714.89,-84.078,-197.259]},{"t":86.6826171875,"s":[3631.196,927.217,1183.551]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":21,"ty":4,"nm":"Shape Layer 100","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.796},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-134.704,42.073,0],"ti":[7.203,-205.415,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.157},"t":28,"s":[-921.188,130.006,0],"to":[-19.567,558.035,0],"ti":[-540.328,-229.432,0]},{"t":116,"s":[2320.778,1506.598,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":22,"ty":4,"nm":"Shape Layer 99","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.775},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-115.257,9.245,0],"ti":[52.068,-163.912,-53.249]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.155},"t":19.951,"s":[-804.504,-66.963,0],"to":[-48.777,153.551,49.883],"ti":[-156.371,-109.917,-25.485]},{"t":91,"s":[2733.865,1051.661,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":23,"ty":4,"nm":"Shape Layer 98","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.855},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-138.996,-53.461,0],"ti":[-421.763,15.031,16.673]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.174},"t":21.187,"s":[-946.942,-443.198,0],"to":[421.763,-15.031,-16.673],"ti":[-560.76,-38.43,16.673]},{"t":100.2763671875,"s":[2417.616,-212.616,-100.037]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":24,"ty":4,"nm":"Shape Layer 97","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.673},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-87.16,25.736,-45.544],"ti":[-273.542,-218.754,122.377]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":31.073,"s":[-660.653,26.246,-273.265],"to":[129.994,103.957,-58.156],"ti":[-320.287,-255.677,-112.981]},{"t":111.3984375,"s":[3683.402,989.215,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":25,"ty":4,"nm":"Shape Layer 96","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.853},"o":{"x":0.167,"y":0.167},"t":0,"s":[-89.038,-0.153,0],"to":[-552.568,-84.135,-9.152],"ti":[-1514.035,-462.134,43.281]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.183},"t":38,"s":[95.207,-392.324,-54.91],"to":[684.779,209.018,-19.576],"ti":[-315.276,-115.122,160.297]},{"t":97,"s":[1986.864,298.408,-1016.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":26,"ty":4,"nm":"Shape Layer 95","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.774},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-110.347,-144.949,-65.532],"ti":[-365.662,-268.839,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.147},"t":26,"s":[-799.777,-997.863,-393.192],"to":[365.662,268.839,214.28],"ti":[-476.009,-413.788,-279.812]},{"t":76.796875,"s":[2056.275,1484.866,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":27,"ty":4,"nm":"Shape Layer 94","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-90.481,-247.593,46.629],"ti":[90.481,247.593,-46.629]},{"t":44,"s":[-680.583,-1613.73,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":28,"ty":4,"nm":"Shape Layer 93","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[69.003,-110.281,-16.168],"ti":[-69.003,110.281,16.168]},{"t":32,"s":[276.323,-789.855,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":29,"ty":4,"nm":"Shape Layer 92","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[305.417,119.743,41.862],"ti":[-305.417,-119.743,-41.862]},{"t":27,"s":[1694.805,590.289,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":30,"ty":4,"nm":"Shape Layer 91","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[318.521,75.426,-43.518],"ti":[-318.521,-75.426,43.518]},{"t":24,"s":[1773.433,324.385,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":31,"ty":4,"nm":"Shape Layer 90","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[77.354,134.863,-215.584],"ti":[-77.354,-134.863,215.584]},{"t":27.3662109375,"s":[326.43,681.005,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":32,"ty":4,"nm":"Shape Layer 89","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[301.698,164.851,-47.459],"ti":[-301.698,-164.851,47.459]},{"t":103.9833984375,"s":[1697.223,866.674,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":33,"ty":4,"nm":"Shape Layer 88","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[159.503,223.195,-0.572],"ti":[-159.503,-223.195,0.572]},{"t":37.251953125,"s":[844.051,1216.741,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":34,"ty":4,"nm":"Shape Layer 87","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[33.76,196.346,-7.096],"ti":[-33.76,-196.346,7.096]},{"t":40.958984375,"s":[89.594,1055.648,-42.576]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":35,"ty":4,"nm":"Shape Layer 86","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-16.649,163.663,0],"ti":[16.649,-163.663,0]},{"t":24.89453125,"s":[-212.861,859.547,0]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":36,"ty":4,"nm":"Shape Layer 85","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-71.747,128.05,0],"ti":[71.747,-128.05,0]},{"t":49.609375,"s":[-543.449,645.87,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":37,"ty":4,"nm":"Shape Layer 84","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.728},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-81.837,27.324,33.544],"ti":[-153.275,-262.436,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.131},"t":21,"s":[-603.986,41.512,201.262],"to":[153.275,262.436,33.544],"ti":[-235.112,-235.112,0]},{"t":50.845703125,"s":[806.688,1452.185,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":38,"ty":4,"nm":"Shape Layer 83","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.652},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-171.831,20.57,3.486],"ti":[602.026,-193.886,-848.553]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.142},"t":35,"s":[-1143.953,0.988,20.917],"to":[-778.519,946.911,884.934],"ti":[-1189.71,-488.979,-884.978]},{"t":139,"s":[5994.306,2934.863,5330.786]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":39,"ty":4,"nm":"Shape Layer 82","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.883},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-120.682,-96.313,17.582],"ti":[57.484,-266.769,15.802]},{"i":{"x":0.833,"y":0.817},"o":{"x":0.167,"y":0.198},"t":15.008,"s":[-837.059,-700.31,105.493],"to":[-25.433,118.027,-6.991],"ti":[-511.749,-461.825,46.841]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.143},"t":54.553,"s":[271.713,386.113,403.89],"to":[308.693,278.579,-28.255],"ti":[-142.63,-100.689,10.833]},{"t":76.796875,"s":[1060.877,1065.009,-67.253]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":40,"ty":4,"nm":"Shape Layer 81","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[135.751,-389.001,217.62],"ti":[-135.751,389.001,-217.62]},{"t":27,"s":[676.808,-2462.175,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":41,"ty":4,"nm":"Shape Layer 80","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.849},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-70.649,26.407,0],"ti":[-291.085,-122.356,90.248]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.171},"t":16,"s":[-561.59,30.269,0],"to":[291.085,122.356,-90.248],"ti":[-361.735,-95.95,90.248]},{"t":82.9755859375,"s":[1608.818,605.966,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":42,"ty":4,"nm":"Shape Layer 79","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.704},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-49.159,-63.557,4.223],"ti":[-0.005,148.838,-20.562]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.144},"t":24,"s":[-432.65,-509.511,25.34],"to":[0.017,-477.286,65.936],"ti":[-415.767,197.78,-61.713]},{"t":91.6259765625,"s":[2061.954,-1696.192,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":43,"ty":4,"nm":"Shape Layer 78","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[33.977,-166.361,22.596],"ti":[-33.977,166.361,-22.596]},{"t":30,"s":[66.167,-1126.338,135.575]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":44,"ty":4,"nm":"Shape Layer 77","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[345.992,-256.663,189.679],"ti":[-345.992,256.663,-189.679]},{"t":27,"s":[1938.258,-1668.151,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":45,"ty":4,"nm":"Shape Layer 76","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.826},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[120.875,-60.552,0],"ti":[-340.061,254.939,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.163},"t":27.366,"s":[587.554,-491.48,0],"to":[340.061,-254.939,0],"ti":[-219.187,194.387,0]},{"t":82.9755859375,"s":[1902.674,-1657.802,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":46,"ty":4,"nm":"Shape Layer 75","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.926},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[208.471,-17.277,73.2],"ti":[-257.939,74.184,269.358]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.228},"t":15.897,"s":[1113.129,-231.834,439.203],"to":[257.939,-74.184,-269.358],"ti":[-49.469,56.907,342.559]},{"t":90,"s":[1409.942,-573.277,-1616.149]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":47,"ty":4,"nm":"Shape Layer 74","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[308.185,154.066,17.938],"ti":[-308.185,-154.066,-17.938]},{"t":24.89453125,"s":[1711.415,796.226,107.63]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":48,"ty":4,"nm":"Shape Layer 73","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-47.653,624.272,396.962],"ti":[47.653,-624.272,-396.962]},{"t":34.7802734375,"s":[-267.632,3580.053,2381.774]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":49,"ty":4,"nm":"Shape Layer 72","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-143.363,403.815,153.434],"ti":[143.363,-403.815,-153.434]},{"t":54.552734375,"s":[-841.892,2257.314,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":50,"ty":4,"nm":"Shape Layer 71","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-196.222,199.787,-42.398],"ti":[196.222,-199.787,42.398]},{"t":32.30859375,"s":[-1159.042,1033.147,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":51,"ty":4,"nm":"Shape Layer 70","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.868},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-181.982,76.554,26.907],"ti":[353.39,-64.568,-26.907]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.207},"t":31.073,"s":[-1073.604,293.746,161.442],"to":[-353.39,64.568,26.907],"ti":[171.408,11.986,0]},{"t":73.08984375,"s":[-2102.054,221.83,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":52,"ty":4,"nm":"Shape Layer 69","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-128.187,-8.964,0],"ti":[128.187,8.964,0]},{"t":33,"s":[-750.834,-219.36,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":53,"ty":4,"nm":"Shape Layer 68","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-120.126,-74.06,0],"ti":[120.126,74.06,0]},{"t":39,"s":[-702.471,-609.938,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":54,"ty":4,"nm":"Shape Layer 67","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.723},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-82.592,-80.92,0],"ti":[157.866,-68.24,-53.249]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.139},"t":27,"s":[-477.265,-651.099,0],"to":[-147.887,63.926,49.883],"ti":[-19.633,-190.127,-25.485]},{"t":80.50390625,"s":[-147.831,2175.133,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":55,"ty":4,"nm":"Shape Layer 66","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.749},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-50.842,-139.975,0],"ti":[92.088,-447.218,16.673]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":36,"s":[-286.764,-1005.43,0],"to":[-92.088,447.218,-16.673],"ti":[41.246,-587.193,16.673]},{"t":100.2763671875,"s":[-534.238,2517.728,-100.037]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":56,"ty":4,"nm":"Shape Layer 65","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[-6.333,-132.667,0],"ti":[83.687,200.398,-334.198]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.153},"t":26,"s":[-31.605,-984.007,0],"to":[-63.434,-151.901,253.321],"ti":[-17.165,-409.463,-112.981]},{"t":111.3984375,"s":[283.153,3184.715,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.671,0.345,0.345,0.5,0.655,0.247,0.461,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":57,"ty":4,"nm":"Shape Layer 64","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.836},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[64.5,-169.594,-9.152],"ti":[-113.79,-117.061,113.731]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.168},"t":27,"s":[393.395,-1205.571,-54.91],"to":[155.887,160.368,-155.806],"ti":[28,-314.6,160.297]},{"t":79,"s":[225.395,682.029,-1016.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":58,"ty":4,"nm":"Shape Layer 63","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.787},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[37,-178.375,-65.532],"ti":[-37,-452.343,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.151},"t":28,"s":[228.395,-1258.256,-393.192],"to":[37,452.343,214.28],"ti":[0,-630.718,-279.812]},{"t":88,"s":[228.395,2526.051,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":59,"ty":4,"nm":"Shape Layer 62","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[127.5,-230.723,46.629],"ti":[-127.5,230.723,-46.629]},{"t":38,"s":[771.395,-1572.344,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":60,"ty":4,"nm":"Shape Layer 61","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[259.979,-20.273,-16.168],"ti":[-259.979,20.273,16.168]},{"t":19,"s":[1566.267,-309.648,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":61,"ty":4,"nm":"Shape Layer 60","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[110,309.06,41.862],"ti":[-110,-309.06,-41.862]},{"t":31,"s":[666.395,1666.35,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":62,"ty":4,"nm":"Shape Layer 59","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[111,205.897,-43.518],"ti":[-111,-205.897,43.518]},{"t":33,"s":[672.395,1047.376,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":63,"ty":4,"nm":"Shape Layer 58","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[14.845,244.879,-215.584],"ti":[-14.845,-244.879,215.584]},{"t":27.3662109375,"s":[95.463,1281.264,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":64,"ty":4,"nm":"Shape Layer 57","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[44.909,224.342,-47.459],"ti":[-44.909,-224.342,47.459]},{"t":58.259765625,"s":[287.74,1180.472,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":65,"ty":4,"nm":"Shape Layer 56","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-63.805,266.808,-0.572],"ti":[63.805,-266.808,0.572]},{"t":51,"s":[-364.539,1435.268,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":66,"ty":4,"nm":"Shape Layer 55","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.877},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-126.036,154.294,-7.096],"ti":[142.905,-395.535,7.096]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.203},"t":39,"s":[-737.928,760.184,-42.576],"to":[-142.905,395.535,-7.096],"ti":[16.869,-241.241,0]},{"t":117,"s":[-839.144,2207.633,-42.576]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":67,"ty":4,"nm":"Shape Layer 54","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-233.472,311.697,-17.966],"ti":[233.472,-311.697,17.966]},{"t":24,"s":[-1382.547,1704.606,-107.798]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":68,"ty":4,"nm":"Shape Layer 53","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.762},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-130.292,21.146,0],"ti":[485.802,-51.233,12.363]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":22,"s":[-763.467,-38.702,0],"to":[-485.802,51.233,-12.363],"ti":[355.509,-30.087,12.363]},{"t":57,"s":[-2896.522,141.82,-74.176]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":69,"ty":4,"nm":"Shape Layer 52","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.78},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-74.311,-43.837,33.544],"ti":[97.505,-287.852,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.149},"t":34,"s":[-427.58,-428.6,201.262],"to":[-97.505,287.852,33.544],"ti":[23.194,-331.689,0]},{"t":102,"s":[-566.743,1561.534,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":70,"ty":4,"nm":"Shape Layer 51","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-124.762,-149.834,3.486],"ti":[124.762,149.834,-3.486]},{"t":26,"s":[-730.286,-1064.579,20.917]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":71,"ty":4,"nm":"Shape Layer 50","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.77},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-6.486,-154.267,17.582],"ti":[239.046,-131.632,15.802]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.142},"t":30,"s":[-20.629,-1091.18,105.493],"to":[-105.762,58.238,-6.991],"ti":[-17.583,-173.702,10.833]},{"t":76.796875,"s":[-107.774,1499.363,-67.253]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":72,"ty":4,"nm":"Shape Layer 49","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[382.643,-152.755,217.62],"ti":[-382.643,152.755,-217.62]},{"t":33,"s":[2302.253,-1104.538,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":73,"ty":4,"nm":"Shape Layer 48","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.831},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[64.5,-93.5,0],"ti":[-64.5,-194.5,90.248]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.166},"t":25,"s":[393.395,-749.007,0],"to":[64.5,194.5,-90.248],"ti":[0,-288,90.248]},{"t":82.9755859375,"s":[393.395,978.993,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":74,"ty":4,"nm":"Shape Layer 47","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.885},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[65.626,-110,4.223],"ti":[-147.468,-10.238,-26.905]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.19},"t":20,"s":[400.149,-848.007,25.34],"to":[361.407,25.09,65.936],"ti":[-121.292,-241.908,-61.713]},{"t":91.6259765625,"s":[1127.898,603.439,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":75,"ty":4,"nm":"Shape Layer 46","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.816},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[147.845,-83.5,22.596],"ti":[-263.557,-41.975,24.609]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.159},"t":24,"s":[893.467,-689.007,135.575],"to":[408.719,65.094,-38.164],"ti":[88.693,-68.281,27.128]},{"t":74,"s":[-447.485,1243.193,-405.659]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":76,"ty":4,"nm":"Shape Layer 45","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.875},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[217.737,24,189.679],"ti":[-563.841,-88.203,-189.679]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.201},"t":26,"s":[1312.816,-44.007,1138.074],"to":[563.841,88.203,189.679],"ti":[-346.104,-64.203,0]},{"t":76.796875,"s":[3389.44,341.208,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":77,"ty":4,"nm":"Shape Layer 44","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.866},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[125,51.5,0],"ti":[-125,-69.882,151.388]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.196},"t":27.366,"s":[756.395,120.993,0],"to":[125,69.882,-151.388],"ti":[0,-18.382,151.388]},{"t":72,"s":[756.395,231.284,-908.328]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":78,"ty":4,"nm":"Shape Layer 43","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[149.808,146,73.2],"ti":[-149.808,-146,-73.2]},{"t":26,"s":[905.246,687.993,439.203]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":79,"ty":4,"nm":"Shape Layer 42","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[85.912,333.667,17.938],"ti":[-85.912,-333.667,-17.938]},{"t":24.89453125,"s":[521.87,1813.993,107.63]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":80,"ty":4,"nm":"Shape Layer 38","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[176.199,-89.062,26.907],"ti":[-176.199,89.062,-26.907]},{"t":31.0732421875,"s":[1063.587,-722.379,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":81,"ty":4,"nm":"Shape Layer 37","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[128.5,0,0],"ti":[-128.5,0,0]},{"t":30,"s":[777.395,-188.007,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":82,"ty":4,"nm":"Shape Layer 36","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[125,65.5,0],"ti":[-125,-65.5,0]},{"t":22,"s":[756.395,204.993,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":83,"ty":4,"nm":"Shape Layer 35","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.782},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[88.036,74.962,0],"ti":[-235.276,83.183,-29.696]},{"i":{"x":0.833,"y":0.693},"o":{"x":0.167,"y":0.155},"t":30,"s":[534.609,261.763,0],"to":[283.126,-100.101,35.736],"ti":[32.848,188.294,-25.485]},{"t":123,"s":[-62.525,-2846.235,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":84,"ty":4,"nm":"Shape Layer 33","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":454.311,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.737},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[42.167,-122.667,0],"ti":[-345.153,74.903,-198.64]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.151},"t":28,"s":[880.395,419.993,0],"to":[432.939,-93.953,249.162],"ti":[-17.165,-409.463,-112.981]},{"t":127,"s":[1064.167,5582.141,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":85,"ty":4,"nm":"Shape Layer 32","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.841},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[21.5,-126.594,-9.152],"ti":[-109.46,-148.367,74.781]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.169},"t":25,"s":[756.395,396.429,-54.90999999999999],"to":[113.887,154.368,-77.806],"ti":[7.464,-520.935,-7.703]},{"t":124,"s":[711.613,3522.04,-8.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":86,"ty":4,"nm":"Shape Layer 31","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.843},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[37,-178.375,-65.532],"ti":[-48.635,-750.211,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.169},"t":29,"s":[849.395,85.744,-393.192],"to":[48.635,750.211,214.28],"ti":[-11.635,-928.586,-279.812]},{"t":145,"s":[919.2079999999999,5657.262,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":87,"ty":4,"nm":"Shape Layer 30","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[127.5,-230.723,46.629],"ti":[-127.5,230.723,-46.629]},{"t":25,"s":[1392.395,-228.34400000000002,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":88,"ty":4,"nm":"Shape Layer 29","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[128.5,-20.273,-16.168],"ti":[-128.5,20.273,16.168]},{"t":44,"s":[1398.395,1034.352,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":89,"ty":4,"nm":"Shape Layer 28","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[110,309.06,41.862],"ti":[-110,-309.06,-41.862]},{"t":41,"s":[1287.395,3010.35,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":90,"ty":4,"nm":"Shape Layer 27","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[111,205.897,-43.518],"ti":[-111,-205.897,43.518]},{"t":31,"s":[1293.395,2391.376,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":91,"ty":4,"nm":"Shape Layer 26","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[1,335.659,-215.584],"ti":[-1,-335.659,215.584]},{"t":58,"s":[633.395,3169.948,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":92,"ty":4,"nm":"Shape Layer 25","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[-147.439,-471.248,-47.459],"ti":[147.439,471.248,47.459]},{"t":137.349609375,"s":[-878.24,-3015.497,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":93,"ty":4,"nm":"Shape Layer 24","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[53.803,-474.312,-0.572],"ti":[-53.803,474.312,0.572]},{"t":71,"s":[329.216,-3033.882,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":94,"ty":4,"nm":"Shape Layer 23","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[436.949,-283.973,0],"ti":[-436.949,283.973,0]},{"t":71,"s":[2628.09,-1891.843,0]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":95,"ty":4,"nm":"Shape Layer 22","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[127.5,-103.954,0],"ti":[-127.5,103.954,0]},{"t":34,"s":[771.395,-811.733,0]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":96,"ty":4,"nm":"Shape Layer 21","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[128.5,-30.183,0],"ti":[-128.5,30.183,0]},{"t":26,"s":[777.395,-369.106,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":97,"ty":4,"nm":"Shape Layer 20","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.716},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[77.188,38.547,33.544],"ti":[-71.564,470.424,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.136},"t":35,"s":[469.524,43.272,201.262],"to":[71.564,-470.424,33.544],"ti":[5.624,508.971,0]},{"t":100,"s":[435.782,-3010.554,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":98,"ty":4,"nm":"Shape Layer 19","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[134.91,140.766,3.486],"ti":[-134.91,-140.766,-3.486]},{"t":21,"s":[815.857,656.587,20.917]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":99,"ty":4,"nm":"Shape Layer 18","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.828},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[17.232,153.439,17.582],"ti":[-229.282,147.986,15.802]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.165},"t":21,"s":[109.785,732.626,105.493],"to":[101.442,-65.474,-6.991],"ti":[29.657,172.052,10.833]},{"t":76.796875,"s":[76.884,-1982.01,-729.705]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":100,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[382.643,-152.755,217.62],"ti":[-382.643,152.755,-217.62]},{"t":25,"s":[2923.253,239.462,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":101,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.829},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[64.5,-93.5,0],"ti":[-78.679,-339.952,90.248]},{"i":{"x":0.833,"y":0.714},"o":{"x":0.167,"y":0.165},"t":30,"s":[1014.395,594.993,0],"to":[78.679,339.952,-90.248],"ti":[-14.179,-433.452,90.248]},{"t":121,"s":[1099.471,3195.705,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":102,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.822]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":102,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.828},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[65.626,-110,4.223],"ti":[-147.468,-10.238,-26.905]},{"i":{"x":0.833,"y":0.887},"o":{"x":0.167,"y":0.163},"t":28,"s":[1021.149,495.993,25.34],"to":[361.407,25.09,65.936],"ti":[-126.131,-151.254,-61.713]},{"t":71,"s":[1777.9360000000001,1403.514,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":103,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[147.845,-83.5,22.596],"ti":[-147.845,83.5,-22.596]},{"t":19,"s":[1514.467,654.993,135.575]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":104,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[393.671,83.751,189.679],"ti":[-393.671,-83.751,-189.679]},{"t":58,"s":[2989.419,1658.499,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":105,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[249,109.5,0],"ti":[-249,-109.5,0]},{"t":68,"s":[2121.395,1812.9929999999997,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":106,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[499.008,399.431,73.2],"ti":[-499.008,-399.431,-73.2]},{"t":96,"s":[3621.4449999999997,3552.5799999999995,439.20300000000003]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":107,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[535.305,103.127,17.938],"ti":[-535.305,-103.127,-17.938]},{"t":82,"s":[3839.226,1774.756,107.63000000000001]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":108,"ty":1,"nm":"Medium Gray-Blue Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[621,1344,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":1242,"sh":2688,"sc":"#272d41","ip":112,"op":112,"st":0,"bm":0,"hidden":0}],"markers":[]} diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 509d2b72be..b4334ef500 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,6 +16,7 @@ #C4291C #FFB71B + #FFFFFF #8E8E93 #636366 #48484A @@ -23,7 +24,7 @@ #2C2C2E #1C1C1E - #F9F9FA + #FFFFFF #F9F9F9 #F4F5F6 #DE000000 @@ -31,6 +32,7 @@ #14181D + #F3F3F3 #F8F8FB #C7C7CC #D1D1D6 diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 888918f16b..17edb1b769 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -191,6 +191,7 @@ this wallet. Done! Your crypto card is activated and ready to be used Continue + Balance 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 3642ef243c..703a685858 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,9 +5,11 @@ @color/colorPrimary @color/colorSecondary @color/darkGray6 - true @color/backgroundLightGray - false + + + + + + + + + \ No newline at end of file From a2883dbdd47eea47fe8db7ca556e81407f699758 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:22:45 +0300 Subject: [PATCH 30/55] Updated on 2026-08-14 --- .../tap/common/redux/NotificationsMiddleware.kt | 2 +- .../main/java/com/tangem/tap/domain/TapErrors.kt | 13 +++++++++---- .../send/ui/stateSubscribers/SendStateSubscriber.kt | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index c797764bbd..2ace551205 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -84,7 +84,7 @@ private fun handleNotificationAction(action: Action) { } else -> { val args = (action.error as? ArgError)?.args ?: listOf() - notificationsHandler?.showNotification(action.error.localizedMessage, args) + notificationsHandler?.showNotification(action.error.messageResource, args) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 8ad394c14f..c84e3f0d29 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -16,13 +16,13 @@ interface MultiMessageError : TapErrors { } sealed class TapError( - @StringRes val localizedMessage: Int, - override val args: List? = null + @StringRes val messageResource: Int, + override val args: List? = null ) : Throwable(), TapErrors, ArgError { object UnknownError : TapError(R.string.send_error_unknown) + open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) object ScanCardError : TapError(R.string.scan_card_error) - data class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created) object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message) object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty) @@ -43,6 +43,11 @@ sealed class TapError( object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm) } + sealed class WalletManagerUpdate { + class NoAccountError(amountToCreateAccount: String): CustomError(amountToCreateAccount) + class InternalError(message: String): CustomError(message) + } + data class ValidateTransactionErrors( override val errorList: List, override val builder: (List) -> String @@ -61,7 +66,7 @@ fun TapErrors.assembleErrors(): MutableList?>> { val idList = mutableListOf?>>() when (this) { is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrors()) } - is TapError -> idList.add(Pair(this.localizedMessage, this.args)) + is TapError -> idList.add(Pair(this.messageResource, this.args)) } return idList } \ No newline at end of file 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 fb2633ff3a..2471b257de 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 @@ -172,7 +172,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber val messageList = multiError.assembleErrors().map { getMessageString(context, it.first, it.second) } multiError.builder(messageList) } - else -> context.getString(state.error.localizedMessage) + else -> context.getString(state.error.messageResource) } fg.tilAmountToSend.enableError(true, message) } else { From 261a16113fce2affa60e60792bf27a14b49c0298 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:29:57 +0300 Subject: [PATCH 31/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/domain/extensions/Blockchain.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 13946dd481..d658847574 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 @@ -16,7 +16,7 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean { fun Blockchain.amountToCreateAccount(token: Token? = null): Double? { return when (this) { Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble() - Blockchain.XRP -> 20.toDouble() + Blockchain.XRP -> 10.toDouble() else -> null } } From 5036f8a383293acf05101fee15f086e53b465c10 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:30:19 +0300 Subject: [PATCH 32/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 4 +- .../com/tangem/tap/common/DialogManager.kt | 7 +- .../tap/common/extensions/WalletManager.kt | 6 +- .../com/tangem/tap/common/redux/AppReducer.kt | 2 - .../com/tangem/tap/common/redux/AppState.kt | 4 - .../tangem/tap/common/redux/StateDialog.kt | 7 + .../tap/common/redux/global/GlobalAction.kt | 17 +- .../common/redux/global/GlobalMidlleware.kt | 13 +- .../tap/common/redux/global/GlobalReducer.kt | 6 + .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 21 +- .../com/tangem/tap/domain/TapWalletManager.kt | 19 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 2 + .../tangem/tap/domain/tasks/ScanNoteTask.kt | 2 +- .../CreateProductWalletAndRescanTask.kt | 139 +++++++++ .../tasks/product/ProductCommandProcessor.kt | 16 + .../domain/tasks/product/ScanProductTask.kt | 141 +++++++++ .../tangem/tap/features/home/HomeFragment.kt | 12 +- .../tap/features/home/redux/HomeMiddleware.kt | 109 ++++--- .../AddressInfoBottomSheetDialog.kt | 44 +++ .../features/onboarding/OnboardingService.kt | 168 ---------- .../products/BaseOnboardingFragment.kt | 80 +++++ .../products/note/OnboardingNoteFragment.kt | 146 +++++---- .../note/redux/OnboardingNoteAction.kt | 21 +- .../note/redux/OnboardingNoteMiddleware.kt | 108 +++++-- .../note/redux/OnboardingNoteReducer.kt | 51 ++- .../note/redux/OnboardingNoteState.kt | 32 +- .../OnboardingOtherCardsFragment.kt | 19 +- .../wallet/OnboardingWalletFragment.kt | 11 - .../wallet/redux/OnboardingWalletState.kt | 3 +- .../onboarding/redux/OnboardingAction.kt | 22 -- .../onboarding/redux/OnboardingMiddleware.kt | 22 -- .../onboarding/redux/OnboardingReducer.kt | 24 -- .../onboarding/redux/OnboardingState.kt | 11 - .../onboarding/redux/StateSupportClasses.kt | 26 -- .../onboarding/service/OnboardingHelper.kt | 33 ++ .../service/OnboardingNoteService.kt | 21 ++ .../service/ProductOnboardingService.kt | 295 ++++++++++++++++++ .../tap/features/send/BaseStoreFragment.kt | 3 + 39 files changed, 1168 insertions(+), 503 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt rename app/src/main/java/com/tangem/tap/features/onboarding/products/{old => otherCards}/OnboardingOtherCardsFragment.kt (62%) delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 37682e2bc8..49856db42b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -75,8 +75,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanNoteResponse != null - val isOnboardingStarted = store.state.onboardingState.onboardingData?.scanNoteResponse != null - if ((backStackIsEmpty || isScannedBefore) && !isOnboardingStarted) { + val isOnboardingServiceActive = store.state.globalState.onboardingService != null + if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } intentHandler.handleIntent(intent) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 218c96776f..114c17c7cd 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -6,8 +6,7 @@ import com.tangem.tap.common.redux.AppDialog 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.products.note.OnboardingAddressInfoDialog -import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -43,6 +42,7 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) + is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect, @@ -71,9 +71,6 @@ class DialogManager : StoreSubscriber { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) - is OnboardingAction.Dialog.AddressInfo -> { - OnboardingAddressInfoDialog(state.dialog, context) - } else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index d8b5b136dd..683f1574a3 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -12,7 +12,7 @@ import com.tangem.tap.network.NetworkConnectivity /** [REDACTED_AUTHOR] */ -suspend fun WalletManager.loadWalletData(): Result = try { +suspend fun WalletManager.safeUpdate(): Result = try { update() Result.Success(wallet) } catch (exception: Exception) { @@ -23,10 +23,10 @@ suspend fun WalletManager.loadWalletData(): Result = try { val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken()) if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) { - Result.Failure(TapError.NoAccount(amountToCreateAccount.toString())) + Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString())) } else { val message = exception.localizedMessage ?: "Unknown exception during WalletManager update" - Result.Failure(TapError.CustomError(message)) + Result.Failure(TapError.WalletManagerUpdate.InternalError(message)) } } } \ No newline at end of file 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 e60b9cade1..4de2f24938 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,7 +8,6 @@ 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.wallet.redux.OnboardingWalletReducer -import com.tangem.tap.features.onboarding.redux.OnboardingReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer @@ -22,7 +21,6 @@ fun appReducer(action: Action, state: AppState?): AppState { navigationState = NavigationReducer.reduce(action, state), globalState = globalReducer(action, state), homeState = HomeReducer.reduce(action, state), - onboardingState = OnboardingReducer.reduce(action, state), onboardingNoteState = OnboardingNoteReducer.reduce(action, state), onboardingWalletState = OnboardingWalletReducer.reduce(action, state), walletState = WalletReducer.reduce(action, state), 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 985a2a75ba..ba9aa021d1 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,8 +16,6 @@ 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.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.redux.OnboardingMiddleware -import com.tangem.tap.features.onboarding.redux.OnboardingState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.tokens.redux.TokensMiddleware @@ -31,7 +29,6 @@ data class AppState( val navigationState: NavigationState = NavigationState(), val globalState: GlobalState = GlobalState(), val homeState: HomeState = HomeState(), - val onboardingState: OnboardingState = OnboardingState(), val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(), val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(), val walletState: WalletState = WalletState(), @@ -48,7 +45,6 @@ data class AppState( logMiddleware, navigationMiddleware, notificationsMiddleware, GlobalMiddleware.handler, HomeMiddleware.handler, - OnboardingMiddleware.handler, OnboardingNoteMiddleware.handler, OnboardingWalletMiddleware.handler, WalletMiddleware().walletMiddleware, 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 e643ceb207..36e1a749a5 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,7 @@ package com.tangem.tap.common.redux +import com.tangem.tap.features.wallet.redux.AddressData + /** [REDACTED_AUTHOR] */ @@ -7,4 +9,9 @@ interface StateDialog sealed class AppDialog : StateDialog { object ScanFailsDialog : AppDialog() + data class AddressInfoDialog( + val addressData: AddressData, + val onCopyAddress: () -> Unit, + val onExploreAddress: () -> Unit + ) : 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 4c4675eb96..4021557fac 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 @@ -3,10 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.tap.common.redux.DebugErrorAction -import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.common.redux.ToastNotificationAction +import com.tangem.tap.common.redux.* import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -15,6 +12,7 @@ import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager +import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.Action @@ -23,16 +21,21 @@ sealed class GlobalAction : Action { // notifications data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction + data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction + data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction // dialogs data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() object HideDialog : GlobalAction() - data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction + sealed class Onboarding { + data class Activate(val onboardingService: ProductOnboardingService) : GlobalAction() + object Deactivate : GlobalAction() + } data class ReadCard( - val onSuccess: (suspend (ScanNoteResponse) -> Unit)? = null, - val onFailure: (suspend (TangemError) -> Unit)? = null, + val onSuccess: ((ScanNoteResponse) -> Unit)? = null, + val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, ) : 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 f6e8152a77..2f8cbfffc4 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 @@ -6,6 +6,7 @@ import com.tangem.common.services.Result import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager @@ -16,9 +17,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.Middleware class GlobalMiddleware { @@ -73,7 +72,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.UpdateFeedbackInfo -> { store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + ?.setWalletsInfo(action.walletManagers) } is GlobalAction.GetMoonPayUserStatus -> { val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey @@ -82,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) ) } } @@ -90,9 +89,9 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.ReadCard -> { scope.launch { - val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler, action.messageResId) - withContext(Dispatchers.Main) { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) + store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + withMainContext { when (result) { is CompletionResult.Success -> { tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) 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 e1f1fa2217..380fe6847f 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 @@ -10,6 +10,12 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { + is GlobalAction.Onboarding.Activate -> { + globalState.copy(onboardingService = action.onboardingService) + } + is GlobalAction.Onboarding.Deactivate -> { + globalState.copy(onboardingService = null) + } is GlobalAction.ScanFailsCounter.Increment -> { globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1) } 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 558dcaab95..0753b6d472 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 @@ -8,6 +8,7 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.feedback.FeedbackManager +import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.StateType @@ -24,7 +25,8 @@ data class GlobalState( val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val moonPayUserStatus: MoonPayUserStatus? = null + val moonPayUserStatus: MoonPayUserStatus? = null, + val onboardingService: ProductOnboardingService? = null, ) : StateType typealias CryptoCurrencyName = String 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 adc0404f24..4cc78b775b 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain +import CreateProductWalletAndRescanTask import android.content.Context import com.tangem.Message import com.tangem.TangemSdk @@ -22,6 +23,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask +import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers @@ -31,8 +33,10 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + @Deprecated("Use can product") suspend fun scanNote( - analyticsHandler: AnalyticsHandler, messageRes: Int? = null, + analyticsHandler: AnalyticsHandler, + messageRes: Int? = null, ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) @@ -42,6 +46,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ).also { sendScanFailuresToAnalytics(analyticsHandler, it) } } + suspend fun scanProduct( + analyticsHandler: AnalyticsHandler, + messageRes: Int? = null, + ): CompletionResult { + analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) + + val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) + return runTaskAsyncReturnOnMain(ScanProductTask(), null, message) + .also { sendScanFailuresToAnalytics(analyticsHandler, it) } + } + + suspend fun createProductWallet(cardId: String?): CompletionResult { + return runTaskAsync(CreateProductWalletAndRescanTask(), cardId, Message(context.getString(R.string.initial_message_create_wallet_body))) + } + private fun sendScanFailuresToAnalytics( analyticsHandler: AnalyticsHandler, result: CompletionResult 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 6e4dec24f5..507db2ad21 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -6,7 +6,7 @@ import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.loadWalletData +import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository @@ -40,22 +40,17 @@ class TapWalletManager { by lazy { WalletManagerFactory(blockchainSdkConfig) } suspend fun loadWalletData(walletManager: WalletManager) { - handleUpdateWalletResult(walletManager.loadWalletData(), walletManager) + handleUpdateWalletResult(walletManager.safeUpdate(), walletManager) } suspend fun updateWallet(walletManager: WalletManager) { - val result = try { - walletManager.update() - Result.Success(walletManager.wallet) - } catch (exception: Exception) { - Result.Failure(exception) - } + val result = walletManager.safeUpdate() withContext(Dispatchers.Main) { when (result) { is Result.Success -> store.dispatch(WalletAction.UpdateWallet.Success(result.data)) is Result.Failure -> - store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage)) + store.dispatch(WalletAction.UpdateWallet.Failure(result.error.localizedMessage)) } } @@ -262,11 +257,11 @@ class TapWalletManager { TapError.NoInternetConnection -> { store.dispatch(WalletAction.LoadData.Failure(error)) } - is TapError.NoAccount -> { + is TapError.WalletManagerUpdate.NoAccountError -> { store.dispatch(WalletAction.LoadWallet - .NoAccount(walletManager.wallet, error.amountToCreateAccount)) + .NoAccount(walletManager.wallet, error.customMessage)) } - is TapError.CustomError -> { + is TapError.WalletManagerUpdate.InternalError -> { store.dispatch(WalletAction.LoadWallet .Failure(walletManager.wallet, error.customMessage)) } 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 ffc138b170..9003c9f4fc 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -60,6 +60,8 @@ object TapWorkarounds { ) } +val DELAY_SDK_DIALOG_CLOSE = 1400L + val Card.isMultiwalletAllowed: Boolean get() { return !isTangemTwin() && !isStart2Coin && !isTangemNote() 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 510d2785d8..39f5d63af0 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 @@ -67,7 +67,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable= FirmwareVersion.MultiWalletAvailable) { + } else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) { createMissingWalletsIfNeeded(card, session, callback) } else { callback(CompletionResult.Success( 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 new file mode 100644 index 0000000000..6134e5de55 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt @@ -0,0 +1,139 @@ +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.card.EllipticCurve +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard +import com.tangem.operations.PreflightReadMode +import com.tangem.operations.PreflightReadTask +import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.tasks.product.ProductCommandProcessor +import com.tangem.tap.domain.twins.isTangemTwin + +/** +[REDACTED_AUTHOR] + */ +class CreateProductWalletAndRescanTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + CreateProductWalletTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} + +private class CreateProductWalletTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card.guard { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val commandProcessor = when { + card.isTangemNote() -> CreateWalletTangemNote() + card.isTangemTwin() -> CreateWalletTangemTwin() + card.isTangemWallet() -> CreateWalletTangemWallet() + else -> CreateWalletOtherCards() + } + commandProcessor.proceed(card, session, callback) + } +} + +private class CreateWalletTangemNote : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (card.supportedCurves.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val curvesSupportedByBlockchain = card.getTangemNoteBlockchain()?.getSupportedCurves() + if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val intersectCurves = card.supportedCurves.intersect(curvesSupportedByBlockchain).toList() + if (intersectCurves.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + } else { + val curve = if (intersectCurves.contains(EllipticCurve.Secp256k1)) { + EllipticCurve.Secp256k1 + } else { + intersectCurves[0] + } + CreateWalletCommand(curve).run(session, callback) + + } + } +} + +private class CreateWalletTangemWallet : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + throw UnsupportedOperationException() + } +} + +private class CreateWalletTangemTwin : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + throw UnsupportedOperationException() + } +} + +private class CreateWalletOtherCards : ProductCommandProcessor { + + private var index = 0 + + private val curves = EllipticCurve.values().toList() + + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val curve = curves[index] + createWallet(curve, session, callback) + } + + private fun createWallet( + curve: EllipticCurve, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + CreateWalletCommand(curve).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + if (index == curves.lastIndex) { + callback(CompletionResult.Success(result.data)) + return@run + } + index += 1 + createWallet(curves[index], session, callback) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt new file mode 100644 index 0000000000..c0810ff887 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.core.CardSession + +/** +[REDACTED_AUTHOR] + */ +interface ProductCommandProcessor { + fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt new file mode 100644 index 0000000000..710e6e99ff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -0,0 +1,141 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.* +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.TangemError +import com.tangem.common.extensions.toHexString +import com.tangem.operations.ScanTask +import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand +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.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tasks.CreateWalletsTask +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.domain.twins.isTangemTwin + +class ScanProductTask(val card: Card? = null) : CardSessionRunnable { + + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + ScanTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val card = this.card ?: result.data + + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return@run + } + + val commandProcessor = when { + card.isTangemNote() -> ScanNoteProcessor() + card.isTangemTwin() -> ScanTwinProcessor() + card.isTangemWallet() -> ScanWalletProcessor() + else -> ScanOtherCardsProcessor() + } + commandProcessor.proceed(card, session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun getErrorIfExcludedCard(card: Card): TangemError? { + if (card.isExcluded()) return TapSdkError.CardForDifferentApp + + return null + } +} + +private class ScanNoteProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + } +} + +private class ScanWalletProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + } +} + +private class ScanTwinProcessor : ProductCommandProcessor { + override fun proceed( + 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(ScanNoteResponse(card, 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( + ScanNoteResponse( + card = card, + walletData = session.environment.walletData, + secondTwinPublicKey = twinPublicKey.toHexString()) + )) + return@run + } else { + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + } + } + is CompletionResult.Failure -> + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + } + } + } + +} + +private class ScanOtherCardsProcessor : ProductCommandProcessor { + override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) { + val walletData = session.environment.walletData + if (card.wallets.isEmpty()) { + callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + return + } + + val curvesPresent = card.wallets.map { it.curve } + val curvesToCreate = card.supportedCurves.subtract(curvesPresent) + + if (curvesToCreate.isEmpty()) { + callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + return + } + + CreateWalletsTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> + callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData))) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ 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 7633a60893..71e0b7a83c 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 @@ -15,6 +15,7 @@ import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* import org.rekotlin.StoreSubscriber import java.lang.ref.WeakReference @@ -40,10 +41,15 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state -> store.dispatch(GlobalAction.GetMoonPayUserStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) } - is HomeAction.ReadCard -> { - if (!preferencesStorage.wasDisclaimerAccepted()) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) - } else { - launchOnboardingService() - } - } + is HomeAction.ReadCard -> handleReadCard() is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) } next(action) @@ -46,39 +47,59 @@ private val homeMiddleware: Middleware = { dispatch, state -> } } -private fun launchOnboardingService() { - fun changeButtonState(state: ButtonState) { - val btnState = IndeterminateProgressButton(state) - store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) +private fun handleReadCard() { + if (!preferencesStorage.wasDisclaimerAccepted()) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + } else { + fun changeButtonState(state: ButtonState) { + val btnState = IndeterminateProgressButton(state) + store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) + } + + fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanNoteResponse) { + changeButtonState(ButtonState.ENABLED) + scope.launch { + tapWalletManager.onCardScanned(scanResponse) + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } + changeButtonState(ButtonState.PROGRESS) + + store.dispatch(GlobalAction.ReadCard({ scanResponse -> + val tapWalletManager = store.state.globalState.tapWalletManager + if (!OnboardingHelper.isOnboardingCase(scanResponse)) { + proceedToWalletScreen(tapWalletManager, scanResponse) + return@ReadCard + } + + val service = OnboardingHelper.createService( + AppScreen.Home, + scanResponse, + tapWalletManager.walletManagerFactory + ) + if (service == null) { + proceedToWalletScreen(tapWalletManager, scanResponse) + return@ReadCard + } + + service.onInitializationProgress = { + when (it) { + ProgressState.Loading -> changeButtonState(ButtonState.PROGRESS) + ProgressState.Done -> changeButtonState(ButtonState.ENABLED) + ProgressState.Error -> changeButtonState(ButtonState.ENABLED) + } + } + service.onInitialized = { + store.dispatch(GlobalAction.Onboarding.Activate(service)) + store.dispatch(NavigationAction.NavigateTo(it, store.state.homeState.shareTransition)) + } + service.onError = { + store.dispatchErrorNotification(it) + } + post(DELAY_SDK_DIALOG_CLOSE) { service.initializeOnboarding() } + }, { + changeButtonState(ButtonState.ENABLED) + })) } - changeButtonState(ButtonState.PROGRESS) - - val walletManager = store.state.globalState.tapWalletManager - val service = OnboardingService( - walletManager, - preferencesStorage.usedCardsPrefStorage - ) - - service.onReadyToProceed = { - store.dispatch(OnboardingAction.SetData(it)) - changeButtonState(ButtonState.ENABLED) - val sharedTransition = store.state.homeState.shareTransition - store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen, fragmentShareTransition = sharedTransition)) - } - - service.onFailedToProceedToOnboardingCase = { - changeButtonState(ButtonState.ENABLED) - walletManager.onCardScanned(it) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - - service.onError = { - changeButtonState(ButtonState.ENABLED) - store.dispatch(GlobalAction.DebugShowError(TapError.CustomError(it))) - } - - store.dispatch(GlobalAction.ReadCard(service::onSuccess, { - changeButtonState(ButtonState.ENABLED) - store.dispatch(GlobalAction.DebugShowError(TapError.ScanCardError)) - })) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt new file mode 100644 index 0000000000..0b5b229bc4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.onboarding + +import android.content.Context +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.copyToClipboard +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_onboarding_address_info.* +import kotlinx.android.synthetic.main.layout_pseudo_toolbar.* + +/** +[REDACTED_AUTHOR] + */ +class AddressInfoBottomSheetDialog( + private val stateDialog: AppDialog.AddressInfoDialog, + context: Context +) : BottomSheetDialog(context) { + + init { + this.setContentView(R.layout.dialog_onboarding_address_info) + setOnCancelListener { store.dispatchDialogHide() } + } + + override fun show() { + super.show() + val data = stateDialog.addressData + + imv_close.setOnClickListener { + dismissWithAnimation = true + cancel() + } + imv_qr_code.setImageBitmap(data.qrCode) + tv_address.text = data.address + btn_fl_copy_address.setOnClickListener { + context.copyToClipboard(data.address) + stateDialog.onCopyAddress() + } + btn_fl_explore.setOnClickListener { + stateDialog.onExploreAddress() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt deleted file mode 100644 index 2fc0925f2e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.tangem.tap.features.onboarding - -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card -import com.tangem.common.extensions.guard -import com.tangem.common.services.Result -import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.domain.TapWorkarounds.isTangemNote -import com.tangem.tap.domain.TapWorkarounds.isTangemWallet -import com.tangem.tap.domain.UrlBitmapLoader -import com.tangem.tap.domain.extensions.getArtworkUrl -import com.tangem.tap.domain.extensions.hasWallets -import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.domain.twins.isTangemTwin -import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction -import com.tangem.tap.features.onboarding.redux.OnboardingData -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.persistence.UsedCardsPrefStorage -import com.tangem.tap.store -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -class OnboardingService( - private val tapWalletManager: TapWalletManager, - private val cardInfoStorage: UsedCardsPrefStorage -) { - - var onReadyToProceed: ((OnboardingData) -> Unit)? = null - set(value) { - field = value - onboardingDataLinker.onReady = { value?.invoke(it) } - } - var onFailedToProceedToOnboardingCase: (suspend (ScanNoteResponse) -> Unit)? = null - var onError: ((String) -> Unit)? = null - - private val onboardingDataLinker = OnboardingDataLinker() - - suspend fun onSuccess(response: ScanNoteResponse) { - onboardingDataLinker.scanNoteResponse = response - val card = response.card - val hasWallets = card.hasWallets() - - if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) { - if (hasWallets) { - cardInfoStorage.activate(card.cardId) - onFailedToProceedToOnboardingCase?.invoke(response) - return - } else { - cardInfoStorage.activate(card.cardId) // вызвать только после того, как был создан кошелек - onboardingDataLinker.navigateTo = AppScreen.OnboardingOther - } - } - if (cardInfoStorage.wasActivated(card.cardId)) { - onFailedToProceedToOnboardingCase?.invoke(response) - return - } - - loadCardImage(card) { onboardingDataLinker.cardArtwork = it } - - if (hasWallets) { - val walletManagerFactory = tapWalletManager.walletManagerFactory - when { - card.isTangemNote() -> loadBalanceNote(walletManagerFactory, response) - card.isTangemWallet() -> loadBalanceWallet(walletManagerFactory, response) - card.isTangemTwin() -> loadBalanceTwins(walletManagerFactory, response) - } - } else { - onboardingDataLinker.valueOfWalletAmount = BigDecimal.ZERO - } - - onboardingDataLinker.navigateTo = when { - card.isTangemNote() -> AppScreen.OnboardingNote - card.isTangemWallet() -> AppScreen.OnboardingWallet - card.isTangemTwin() -> AppScreen.OnboardingTwins - else -> AppScreen.OnboardingOther - } - } - - private suspend fun loadBalanceNote(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - val walletManager: WalletManager = walletManagerFactory.makePrimaryWalletManager(response).guard { - onError?.invoke("Can't proceed: primary wallet manager didn't create") - return - } - walletManager.update() - store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) - - val wallet = walletManager.wallet - val valueOfAmount = wallet.amounts[AmountType.Coin]?.value - if (valueOfAmount == null) { - onError?.invoke("Amount can't be NULL") - return - } - - onboardingDataLinker.valueOfWalletAmount = valueOfAmount - } - - private suspend fun loadBalanceWallet(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - - } - - private suspend fun loadBalanceTwins(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - - } - - private suspend fun loadCardImage(card: Card, callback: (Artwork?) -> Unit) { - fun loadBitmap(url: String, callback: (Artwork?) -> Unit) { - UrlBitmapLoader().loadBitmap(url) { - when (it) { - is Result.Success -> callback(Artwork(url, it.data)) - is Result.Failure -> callback(null) - } - } - } - - when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { - is Result.Success -> { - val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL - loadBitmap(artworkUrl, callback) - } - is Result.Failure -> loadBitmap(Artwork.DEFAULT_IMG_URL, callback) - } - } - -} - -class OnboardingDataLinker { - var onReady: ((OnboardingData) -> Unit)? = null - - internal var scanNoteResponse: ScanNoteResponse? = null - set(value) { - field = value - tryToProceed() - } - internal var cardArtwork: Artwork? = null - set(value) { - field = value - tryToProceed() - } - internal var valueOfWalletAmount: BigDecimal? = null - set(value) { - field = value - tryToProceed() - } - internal var navigateTo: AppScreen? = null - set(value) { - field = value - tryToProceed() - } - - private fun tryToProceed() { - if (!isReady()) return - - val data = OnboardingData(scanNoteResponse!!, cardArtwork!!, valueOfWalletAmount, navigateTo!!) - onReady?.invoke(data) - } - - private fun isReady(): Boolean { - val objList = mutableListOf(scanNoteResponse, cardArtwork, valueOfWalletAmount, navigateTo) - return !objList.any { it == null } - } -} \ 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 new file mode 100644 index 0000000000..b27d17e06a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt @@ -0,0 +1,80 @@ +package com.tangem.tap.features.onboarding.products + +import android.os.Bundle +import android.view.View +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.inflate +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.common.transitions.HomeToOnboardingTransition +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_onboarding_main.* +import kotlinx.android.synthetic.main.layout_pseudo_toolbar.* +import kotlinx.android.synthetic.main.view_confetti.* +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { + + protected var fromScreen: AppScreen? = null + + override fun onCreate(savedInstanceState: Bundle?) { + fromScreen = store.state.globalState.onboardingService?.fromScreen + + super.onCreate(savedInstanceState) + } + + override fun configureTransitions() { + val inflater = TransitionInflater.from(requireContext()) + when (fromScreen) { + AppScreen.Home -> { + enterTransition = HomeToOnboardingTransition() + exitTransition = HomeToOnboardingTransition() + } + AppScreen.Wallet -> { + enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) + exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + onboarding_top_container.inflate(getOnboardingTopContainerId(), true) + configureToolbar() + } + + private fun configureToolbar() { + when (fromScreen) { + AppScreen.Home -> { + toolbar.show(true) + pseudo_toolbar.visibility = View.GONE + } + AppScreen.Wallet -> { + toolbar.visibility = View.GONE + pseudo_toolbar.show(true) + imv_close.setOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + } + } + + abstract fun getOnboardingTopContainerId(): Int + + protected fun showConfetti(show: Boolean) { + lav_confetti.show(show) + + if (show) { + lav_confetti.playAnimation() + } else { + lav_confetti.cancelAnimation() + } + } +} \ No newline at end of file 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 730c8760fc..182f63d7bb 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 @@ -2,46 +2,46 @@ package com.tangem.tap.features.onboarding.products.note 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 com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -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.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep -import com.tangem.tap.features.send.BaseStoreFragment +import androidx.transition.TransitionManager +import com.tangem.blockchain.common.Blockchain +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.toggleWidget.RefreshBalanceWidget +import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.note.redux.* import com.tangem.tap.store import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_onboarding.* -import org.rekotlin.StoreSubscriber +import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* /** [REDACTED_AUTHOR] */ -class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { +class OnboardingNoteFragment : BaseOnboardingFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingNoteStep.None)) - } - - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.fade) - exitTransition = inflater.inflateTransition(R.transition.fade) - } + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - store.dispatch(OnboardingNoteAction.Init) + btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) + val resources = AndroidResources( + OnboardingStringResources( + R.string.copy_toast_msg + ) + ) - imv_front_card.setImageBitmap(store.state.onboardingState.onboardingData?.cardArtwork?.artwork) + store.dispatch(OnboardingNoteAction.SetResources(resources)) + store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } override fun subscribeToStore() { @@ -55,79 +55,107 @@ class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), override fun newState(state: OnboardingNoteState) { if (activity == null) return + state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) } pb_state.max = state.steps.size - 1 + pb_state.progress = state.progress - onboarding_main_container.beginDelayedTransition() when (state.currentStep) { OnboardingNoteStep.None -> setupNoneState(state) OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state) OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state) - OnboardingNoteStep.Done -> setupSuccessState(state) + OnboardingNoteStep.Done -> setupDoneState(state) } + setBalance(state) + showConfetti(state.showConfetti) + } + + private fun setBalance(state: OnboardingNoteState) { + if (state.balanceCurrency.blockchain == Blockchain.Unknown) return + + val balanceValue = state.balanceValue.stripZeroPlainString() + val currency = state.balanceCurrency.currencySymbol + tv_balance_value.text = "$balanceValue $currency" } private fun setupNoneState(state: OnboardingNoteState) { - pb_state.progress = state.progress + btn_main_action.isVisible = false + btn_alternative_action.isVisible = false + btnRefreshBalanceWidget.show(false) + tv_header.isVisible = false + tv_body.isVisible = false - btn_main_action.isVisible = true - btn_alternative_action.isVisible = true - btn_check_balance.isVisible = true - tv_header.isVisible = true - tv_body.isVisible = true - - btn_main_action.text = "NULL" - btn_alternative_action.text = "NULL" - btn_check_balance.text = "NULL balance" - tv_header.text = "NULL" - tv_body.text = "NULL" + btn_main_action.text = "" + btn_alternative_action.text = "" + tv_header.text = "" + tv_body.text = "" } private fun setupCreateWalletState(state: OnboardingNoteState) { - pb_state.progress = state.progress - btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) } - btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean) btn_alternative_action.setOnClickListener { } - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btnRefreshBalanceWidget.mainView.setOnClickListener(null) tv_header.setText(R.string.onboarding_create_wallet_header) tv_body.setText(R.string.onboarding_create_wallet_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle)) + updateConstraints(R.layout.lp_onboarding_create_wallet) } private fun setupTopUpWalletState(state: OnboardingNoteState) { - pb_state.progress = state.progress - btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) - btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.TopUp) } + btn_main_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.TopUp) + } btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) - btn_alternative_action.setOnClickListener { } - - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btn_alternative_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + } tv_header.setText(R.string.onboarding_top_up_header) tv_body.setText(R.string.onboarding_top_up_body) + + btnRefreshBalanceWidget.changeState(state.balanceState) + if (btnRefreshBalanceWidget.isShowing != true) { + postUi(300) { + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(OnboardingNoteAction.Balance.Update) + } + } + } + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(R.layout.lp_onboarding_topup_wallet) } - private fun setupSuccessState(state: OnboardingNoteState) { - pb_state.progress = state.progress - + private fun setupDoneState(state: OnboardingNoteState) { btn_main_action.setText(R.string.onboarding_done_button_continue) - btn_main_action.setOnClickListener { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } + btn_main_action.setOnClickListener { + showConfetti(false) + store.dispatch(OnboardingNoteAction.Done) + } btn_alternative_action.isVisible = false btn_alternative_action.setText("") btn_alternative_action.setOnClickListener { } - - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btnRefreshBalanceWidget.mainView.setOnClickListener(null) tv_header.setText(R.string.onboarding_done_header) tv_body.setText(R.string.onboarding_done_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(R.layout.lp_onboarding_done) + } + + private fun updateConstraints(@LayoutRes layoutId: Int) { + val constraintSet = ConstraintSet() + constraintSet.clone(requireContext(), layoutId) + constraintSet.applyTo(onboarding_main_container) + val transition = InternalNoteLayoutTransition() + transition.interpolator = OvershootInterpolator() + TransitionManager.beginDelayedTransition(onboarding_main_container, transition) } } \ No newline at end of file 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 bab556d68b..9c1644dda1 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,16 +1,31 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.service.OnboardingWalletBalance import org.rekotlin.Action /** [REDACTED_AUTHOR] */ sealed class OnboardingNoteAction : Action { - object Init : OnboardingNoteAction() + // from user, ui + data class SetResources(val resources: AndroidResources) : OnboardingNoteAction() object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() + object ShowAddressInfoDialog : OnboardingNoteAction() + + // from redux + object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() - data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() + data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction() + + sealed class Balance { + object Update : OnboardingNoteAction() + data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction() + } + + sealed class Confetti { + object Show : OnboardingNoteAction() + object Hide : OnboardingNoteAction() + } } \ No newline at end of file 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 7f5ee16c3f..2eab43bc1b 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 @@ -1,9 +1,15 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult +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.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +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.onboarding.service.balanceIsToppedUp +import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -13,8 +19,6 @@ import org.rekotlin.Middleware class OnboardingNoteMiddleware { companion object { - private const val CARD_SHOP_URI = - "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" val handler = onboardingNoteMiddleware } } @@ -22,51 +26,93 @@ class OnboardingNoteMiddleware { private val onboardingNoteMiddleware: Middleware = { dispatch, state -> { next -> { action -> - handleMainOnboardingAction(action, ::handleNoteAction) + handleNoteAction(action) next(action) } } } -private fun handleMainOnboardingAction(action: Action, isNotMainAction: (Action) -> Unit) { - val action = action as? OnboardingAction ?: isNotMainAction(action) - - when (action) { - } -} - private fun handleNoteAction(action: Action) { - val onboardingState = store.state.onboardingState + if (action !is OnboardingNoteAction) return + val service = store.state.onboardingNoteState.onboardingService ?: return val noteState = store.state.onboardingNoteState when (action) { - is OnboardingNoteAction.SetWalletManager -> { - } - is OnboardingNoteAction.Init -> { - val step = if (noteState.walletManager == null) { - OnboardingNoteStep.CreateWallet - } else { - OnboardingNoteStep.TopUpWallet + is OnboardingNoteAction.DetermineStepOfScreen -> { + val step = when { + service.walletManager == null -> OnboardingNoteStep.CreateWallet + service.balanceIsToppedUp() -> OnboardingNoteStep.Done + else -> OnboardingNoteStep.TopUpWallet + } + store.dispatch((OnboardingNoteAction.SetStepOfScreen(step))) + } + is OnboardingNoteAction.SetStepOfScreen -> { + if (action.step == OnboardingNoteStep.Done) { + service.activationFinished() + postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) } } - store.dispatch(OnboardingAction.SetInitialStepOfScreen(step)) } is OnboardingNoteAction.CreateWallet -> { scope.launch { - val scanNoteResponse = onboardingState.onboardingData?.scanNoteResponse - val result = tangemSdkManager.createWallet(scanNoteResponse?.card?.cardId) - when (result) { - is CompletionResult.Success -> { -// val scanNoteResponse = scanNoteResponse!!.copy(card = result.data) -// store.dispatch(OnboardingAction.SetScanNoteResponse(scanNoteResponse)) - } - is CompletionResult.Failure -> { - store.dispatch(OnboardingAction.Error("Failed to create wallet")) + val result = tangemSdkManager.createProductWallet(service.scanResponse.card.cardId) + withMainContext { + when (result) { + is CompletionResult.Success -> { + val updatedResponse = service.scanResponse.copy(card = result.data) + service.scanResponse = updatedResponse + service.activationStarted() + + store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet)) + store.dispatch(OnboardingNoteAction.Balance.Update) + } + is CompletionResult.Failure -> { +// do nothing + } } } } } - is OnboardingNoteAction.TopUp -> { + is OnboardingNoteAction.Balance.Update -> { + val loadingBalance = service.getBalance().copy( + state = ProgressState.Loading, + error = null, + criticalError = null + ) + store.dispatch(OnboardingNoteAction.Balance.Set(loadingBalance)) + scope.launch { + val loadedBalance = service.updateBalance() + if (loadedBalance.criticalError != null) { + store.dispatchErrorNotification(loadedBalance.criticalError) + } + withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } + } + } + is OnboardingNoteAction.Balance.Set -> { + if (service.balanceIsToppedUp()) { + store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) + } + } + is OnboardingNoteAction.ShowAddressInfoDialog -> { + val addressData = noteState.onboardingService?.getAddressData() ?: return + val addressWasCopied = noteState.resources.strings.addressWasCopied + val appDialog = AppDialog.AddressInfoDialog( + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + ) + store.dispatchDialogShow(appDialog) + } + is OnboardingNoteAction.TopUp -> { + val topUpUrl = noteState.onboardingService?.getToUpUrl() ?: return + store.dispatchOpenUrl(topUpUrl) + } + OnboardingNoteAction.Done -> { + store.dispatch(GlobalAction.Onboarding.Deactivate) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } } } \ No newline at end of file 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 b6a50be846..efe2540b66 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 @@ -1,8 +1,8 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.service.OnboardingNoteService import org.rekotlin.Action class OnboardingNoteReducer { @@ -15,23 +15,46 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt var state = appState.onboardingNoteState when (action) { - is OnboardingAction.SetInitialStepOfScreen -> { - (action.step as? OnboardingNoteStep)?.let { - if (it != state.currentStep && state.steps.contains(it)) { - state = state.copy(currentStep = it) - } + is GlobalAction.Onboarding.Activate -> { + if (action.onboardingService is OnboardingNoteService) { + val service = action.onboardingService + val balance = service.getBalance() + state = state.copy( + onboardingService = service, + artworkBitmap = service.getArtwork().value?.artwork, + balanceValue = balance.value, + balanceCurrency = balance.currency, + balanceState = balance.state, + amountToCreateAccount = balance.amountToCreateAccount, + showConfetti = false + ) } } - OnboardingAction.SwitchToNextStep -> { - val progress = state.progress - if (progress >= 0 && progress != state.steps.size - 1) { - state = state.copy(currentStep = state.steps[progress + 1]) + GlobalAction.Onboarding.Deactivate -> { + state = OnboardingNoteState() + } + is OnboardingNoteAction.SetResources -> { + state = state.copy(resources = action.resources) + } + is OnboardingNoteAction.Balance.Set -> { + state = state.copy( + balanceValue = action.balance.value, + balanceCurrency = action.balance.currency, + balanceState = action.balance.state, + amountToCreateAccount = action.balance.amountToCreateAccount, + ) + } + is OnboardingNoteAction.SetStepOfScreen -> { + if (action.step != state.currentStep && state.steps.contains(action.step)) { + state = state.copy(currentStep = action.step) } } - is OnboardingNoteAction.SetWalletManager -> { - state = state.copy(walletManager = action.walletManager) + is OnboardingNoteAction.Confetti.Show -> { + state = state.copy(showConfetti = true) + } + is OnboardingNoteAction.Confetti.Hide -> { + state = state.copy(showConfetti = false) } - } return state 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 ae5677779f..b95c58510d 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,18 +1,42 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.blockchain.common.WalletManager -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import android.graphics.Bitmap +import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.features.onboarding.service.OnboardingNoteService +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState import org.rekotlin.StateType +import java.math.BigDecimal /** [REDACTED_AUTHOR] */ data class OnboardingNoteState( - val walletManager: WalletManager? = null, + val onboardingService: OnboardingNoteService? = null, + val resources: AndroidResources = AndroidResources(), + // UI + val showConfetti: Boolean = false, + val artworkBitmap: Bitmap? = null, + val balanceValue: BigDecimal = BigDecimal.ZERO, + val balanceCurrency: Currency = Currency.Blockchain(Blockchain.Unknown), + val balanceState: ProgressState = ProgressState.Done, + val amountToCreateAccount: String? = null, val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), ) : StateType { val progress: Int get() = steps.indexOf(currentStep) -} \ No newline at end of file +} + +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 diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt similarity index 62% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index cada557578..b0de1e9949 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -1,31 +1,20 @@ -package com.tangem.tap.features.onboarding.products.old +package com.tangem.tap.features.onboarding.products.otherCards import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingOtherCardsState -import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep import com.tangem.tap.features.send.BaseStoreFragment -import com.tangem.tap.store import com.tangem.wallet.R import org.rekotlin.StoreSubscriber /** [REDACTED_AUTHOR] */ -class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { +class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) - } - val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -47,4 +36,6 @@ class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboard if (activity == null) return } -} \ No newline at end of file +} + +class OnboardingOtherCardsState {} \ 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 5b2eba2423..c947f32ab9 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,11 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep import com.tangem.tap.features.send.BaseStoreFragment import com.tangem.tap.store import com.tangem.wallet.R @@ -21,11 +17,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) - } - val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -33,8 +24,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - store.dispatch(OnboardingAction.OnboardingOtherAction.Init) } override fun subscribeToStore() { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 9b8579171a..5207a85b3d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.onboarding.products.wallet.redux -import com.tangem.tap.features.onboarding.redux.OnboardingStep import org.rekotlin.StateType /** @@ -10,6 +9,6 @@ data class OnboardingWalletState( val any: String? = null ) : StateType -enum class OnboardingWalletStep : OnboardingStep { +enum class OnboardingWalletStep { None, CreateWallet, TopUpWallet, Backup, Done } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt deleted file mode 100644 index 7a904b47ee..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import org.rekotlin.Action - -/** -[REDACTED_AUTHOR] - */ -sealed class OnboardingAction : Action { - - data class SetInitialStepOfScreen(val step: OnboardingStep) : OnboardingAction() - object SwitchToNextStep : OnboardingAction() - - data class SetData(val data: OnboardingData) : OnboardingAction() - - sealed class OnboardingOtherAction : Action { - object Init : OnboardingOtherAction() - object CreateWallet : OnboardingOtherAction() - object Done : OnboardingOtherAction() - } - - class Error(val message: String) : OnboardingAction() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt deleted file mode 100644 index ea373a5f41..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.store -import org.rekotlin.Middleware - -class OnboardingMiddleware { - companion object { - val handler = onboardingMiddleware - } -} - -private val onboardingMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - val onboardingState = store.state.onboardingState - when (action) { - } - next(action) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt deleted file mode 100644 index 5ee024922d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.AppState -import org.rekotlin.Action - -class OnboardingReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingState = internalReduce(action, state) - } -} - -private fun internalReduce(action: Action, appState: AppState): OnboardingState { - - if (action !is OnboardingAction) return appState.onboardingState - - var state = appState.onboardingState - when (action) { - is OnboardingAction.SetData -> { - state = state.copy(onboardingData = action.data) - } - } - - return state -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt deleted file mode 100644 index 2a4909de43..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import org.rekotlin.StateType - -data class OnboardingState( - val onboardingData: OnboardingData? = null, -) : StateType - -data class OnboardingOtherCardsState( - val any: String? = null -) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt deleted file mode 100644 index cc422410a5..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.features.wallet.redux.Artwork -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class OnboardingData( - val scanNoteResponse: ScanNoteResponse, - val cardArtwork: Artwork, - val balance: BigDecimal?, - val onboardingScreen: AppScreen -) - -interface OnboardingStep - -enum class OnboardingNoteStep : OnboardingStep { - None, CreateWallet, TopUpWallet, Done -} - -enum class OnboardingOtherStep : OnboardingStep { - CreateWallet, Done -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt new file mode 100644 index 0000000000..7cf88da9c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.onboarding.service + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.preferencesStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingHelper { + companion object { + + fun isOnboardingCase(response: ScanNoteResponse): Boolean = if (response.card.hasWallets()) + preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true + + fun createService( + fromScreen: AppScreen, + scanResponse: ScanNoteResponse, + walletManagerFactory: WalletManagerFactory, + ): ProductOnboardingService? { + val card = scanResponse.card + val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + return when { + card.isTangemNote() -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, + walletManagerFactory) + else -> null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt new file mode 100644 index 0000000000..4404a8e35b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.onboarding.service + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.persistence.UsedCardsPrefStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingNoteService( + override val fromScreen: AppScreen, + override var scanResponse: ScanNoteResponse, + cardInfoStorage: UsedCardsPrefStorage, + walletManagerFactory: WalletManagerFactory, +) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { + + override fun navigateToScreen(): AppScreen { + return AppScreen.OnboardingNote + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt new file mode 100644 index 0000000000..44c4c99980 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt @@ -0,0 +1,295 @@ +package com.tangem.tap.features.onboarding.service + +import android.util.Log +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.common.card.Card +import com.tangem.common.extensions.guard +import com.tangem.common.extensions.isZero +import com.tangem.common.services.Result +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.isPositive +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.withMainContext +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.UrlBitmapLoader +import com.tangem.tap.domain.extensions.getArtworkUrl +import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.topup.TradeCryptoHelper +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.reducers.createAddressesData +import com.tangem.tap.persistence.UsedCardsPrefStorage +import com.tangem.tap.store +import kotlinx.coroutines.* +import java.io.PrintWriter +import java.io.StringWriter +import java.math.BigDecimal +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** +[REDACTED_AUTHOR] + */ +interface OnboardingService { + val fromScreen: AppScreen + val scanResponse: ScanNoteResponse + + // val cardInfoStorage: UsedCardsPrefStorage + var walletManager: WalletManager? + + var onInitializationProgress: ((ProgressState) -> Unit)? + var onInitialized: ((AppScreen) -> Unit)? + var onError: ((TapError) -> Unit)? + + fun initializeOnboarding() + fun getArtwork(): OnboardingArtwork + fun getBalance(): OnboardingWalletBalance + suspend fun updateBalance(): OnboardingWalletBalance + fun getToUpUrl(): String? + fun getAddressData(): AddressData? + + fun activationStarted() + fun activationFinished() +} + +abstract class ProductOnboardingService( + private val cardInfoStorage: UsedCardsPrefStorage, + private val walletManagerFactory: WalletManagerFactory, +) : OnboardingService { + + override var walletManager: WalletManager? = null + + override var onInitializationProgress: ((ProgressState) -> Unit)? = null + override var onInitialized: ((AppScreen) -> Unit)? = null + override var onError: ((TapError) -> Unit)? = null + + protected var isProceeded = false + + protected var loadedCardArtwork: OnboardingArtwork = OnboardingArtwork.loading() + set(value) { + field = value + tryToProceed() + } + + protected var loadedBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading() + set(value) { + field = value + tryToProceed() + } + + protected val internalScope = CoroutineScope(Job() + Dispatchers.IO + coroutineExceptionHandler()) + + override fun initializeOnboarding() { + val card = scanResponse.card + onInitializationProgress?.invoke(ProgressState.Loading) + + internalScope.launch { + val artwork = loadCardArtwork(card) + withMainContext { + loadedCardArtwork = if (artwork == null) OnboardingArtwork.error() else OnboardingArtwork.done(artwork) + } + } + internalScope.launch { updateBalance() } + } + + override fun getArtwork(): OnboardingArtwork = loadedCardArtwork + + override fun getBalance(): OnboardingWalletBalance = loadedBalance + + override fun getToUpUrl(): String? { + val wallet = walletManager?.wallet ?: return null + val config = store.state.globalState.configManager?.config ?: return null + val defaultAddress = wallet.address + + return TradeCryptoHelper.getUrl( + TradeCryptoHelper.Action.Buy, + wallet.blockchain, + wallet.blockchain.currency, + defaultAddress, + config.moonPayApiKey, + config.moonPayApiSecretKey + ) + } + + override fun getAddressData(): AddressData? { + val wallet = walletManager?.wallet ?: return null + + val addressDataList = wallet.createAddressesData() + return if (addressDataList.isEmpty()) null + else addressDataList[0] + } + + protected suspend fun loadCardArtwork(card: Card): Artwork? { + return when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { + is Result.Success -> { + val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL + loadBitmap(artworkUrl) + } + is Result.Failure -> { + loadBitmap(Artwork.DEFAULT_IMG_URL) + } + } + } + + override suspend fun updateBalance(): OnboardingWalletBalance { + withMainContext { loadedBalance = OnboardingWalletBalance.loading(loadedBalance.value) } + + val walletManager = walletManager ?: walletManagerFactory.makePrimaryWalletManager(scanResponse).guard { + val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") + loadedBalance = OnboardingWalletBalance.error(customError) + return loadedBalance + } + + this.walletManager = walletManager + val currency = Currency.Blockchain(walletManager.wallet.blockchain) + val updatedBalance = when (val result = walletManager.safeUpdate()) { + is Result.Success -> { + val wallet = walletManager.wallet + val valueOfAmount = wallet.amounts[AmountType.Coin]?.value + if (valueOfAmount == null) { + val customError = TapError.CustomError("Amount is NULL") + OnboardingWalletBalance.criticalError(customError) + } else { + val balance = if (valueOfAmount.isZero()) { + OnboardingWalletBalance.done(valueOfAmount, hasForIncomingTransactions(), currency) + } else { + OnboardingWalletBalance.done(valueOfAmount, false, currency) + } + balance + } + } + is Result.Failure -> { + val error = (result.error as? TapError) ?: TapError.UnknownError + when (error) { + is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error) +// NoInternetConnection, WalletManagerUpdate.InternalError + else -> OnboardingWalletBalance.criticalError(error) + } + } + } + withMainContext { loadedBalance = updatedBalance } + return loadedBalance + } + + override fun activationStarted() { + cardInfoStorage.activationStarted(scanResponse.card.cardId) + + } + + override fun activationFinished() { + cardInfoStorage.activationFinished(scanResponse.card.cardId) + } + + protected open suspend fun hasForIncomingTransactions(): Boolean { + return false + } + + protected open fun tryToProceed() { + if (isProceeded) return + + loadedBalance.criticalError?.let { + sendError(it) + return + } + if (!isReadyToProceed()) return + + isProceeded = true + onInitializationProgress?.invoke(ProgressState.Done) + onInitialized?.invoke(navigateToScreen()) + } + + protected open fun isReadyToProceed(): Boolean { + return loadedBalance.state != ProgressState.Loading && loadedCardArtwork.state != ProgressState.Loading + } + + protected fun sendError(error: TapError) { + onInitializationProgress?.invoke(ProgressState.Error) + onError?.invoke(error) + } + + protected abstract fun navigateToScreen(): AppScreen + + private suspend fun loadBitmap(url: String): Artwork? { + return withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + UrlBitmapLoader().loadBitmap(url) { + val result = when (it) { + is Result.Success -> Artwork(url, it.data) + is Result.Failure -> null + } + continuation.resume(result) + } + } + } + } + + private fun coroutineExceptionHandler(): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> + StringWriter().apply { + throwable.printStackTrace(PrintWriter(this)) + Log.e("Coroutine", this.toString()) + store.dispatchDebugErrorNotification(this.toString()) + } + } +} + +data class OnboardingArtwork( + val value: Artwork? = null, + val state: ProgressState +) { + companion object { + // balance was not loaded at all when any error occurred + fun error(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Error) + + // balance is loading + fun loading(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Loading) + + // balance was loaded + fun done(artwork: Artwork): OnboardingArtwork = OnboardingArtwork(artwork, ProgressState.Done) + } +} + +data class OnboardingWalletBalance( + val value: BigDecimal = BigDecimal.ZERO, + val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown), + val hasIncomingTransaction: Boolean = false, + val state: ProgressState, + val error: TapError? = null, + val criticalError: TapError? = null, +) { + + val amountToCreateAccount: String? + get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null + + companion object { + fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( + state = ProgressState.Error, + error = error + ) + + fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( + state = ProgressState.Error, + criticalError = error + ) + + fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance( + value, + state = ProgressState.Loading + ) + + fun done(value: BigDecimal, hasTransactions: Boolean, currency: Currency.Blockchain): OnboardingWalletBalance = + OnboardingWalletBalance(value, currency, hasTransactions, ProgressState.Done) + } +} + +fun ProductOnboardingService.balanceIsToppedUp(): Boolean { + val balance = getBalance() + return balance.value.isPositive() || balance.hasIncomingTransaction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt index 9d19112f8a..75a2a5ad80 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -24,7 +24,10 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + configureTransitions() + } + protected open fun configureTransitions() { val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) exitTransition = inflater.inflateTransition(R.transition.fade) From a4e7c2d6270f8ed20bc0562d494d6781774f12bf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Oct 2021 16:00:46 +0300 Subject: [PATCH 33/55] Updated on 2026-08-14 --- .../tap/common/leapfrogWidget/Animations.kt | 154 +++++++++++++ .../tap/common/leapfrogWidget/LeapView.kt | 59 +++++ .../common/leapfrogWidget/LeapfrogWidget.kt | 204 ++++++++++++++++++ app/src/main/res/layout/leapfrog_widget.xml | 26 +++ 4 files changed, 443 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt create mode 100644 app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt create mode 100644 app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt create mode 100644 app/src/main/res/layout/leapfrog_widget.xml diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt new file mode 100644 index 0000000000..b89618ee5f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt @@ -0,0 +1,154 @@ +package com.tangem.tap.common.leapfrogWidget + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.view.View +import android.view.animation.DecelerateInterpolator +import android.view.animation.LinearInterpolator + +/** +[REDACTED_AUTHOR] + */ +//fun View.setForeground(hasForegroundAtEnd: Boolean) { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +// foreground = getForegroundDrawable() +// } +//} + +//fun View.getForegroundDrawable(): Drawable? { +// return AppCompatResources.getDrawable(context, R.drawable.shape_rectangle_rounded_8_op_20)?.also { +// it.alpha = 60 +// } +//} + +typealias ProgressListener = (Int) -> Unit + +fun AnimatorSet.createProgressListener(duration: Long, listener: ProgressListener?): Animator { + val valueAnimator = ValueAnimator.ofInt(0, 100) + valueAnimator.duration = duration + valueAnimator.addUpdateListener { listener?.invoke((it.animatedValue as? Int) ?: 0) } + return valueAnimator +} + +fun View.initAnimation(animDuration: Long, properties: Properties): AnimatorSet { + elevation = properties.elevationEnd + scaleX = properties.scaleEnd + scaleY = properties.scaleEnd + + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) + translate.duration = animDuration + return AnimatorSet().apply { playTogether(translate) } +} + +fun View.leapAnimation(animDuration: Long, properties: Properties, overLift: Float): AnimatorSet { + val halfDuration = animDuration / 2 + + val upTo = (height.toFloat() + properties.yTranslationStart + overLift) * -1 + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, upTo) + translateUp.duration = halfDuration + translateUp.interpolator = LinearInterpolator() + + val scaleDuration = halfDuration - halfDuration / 5 + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val scale = AnimatorSet().apply { + startDelay = scaleDuration + duration = scaleDuration + interpolator = LinearInterpolator() + playTogether(scaleX, scaleY) + } + + val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) + elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + elevation.startDelay = halfDuration + elevation.duration = 100 + + val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo, properties.yTranslationEnd) + translateDown.startDelay = halfDuration + translateDown.duration = halfDuration + translateDown.interpolator = LinearInterpolator() + + return AnimatorSet().apply { + playTogether( + translateUp, + scale, + elevation, + translateDown + ) + } +} + +fun View.leapBackAnimation(animDuration: Long, properties: Properties, calculator: PropertyCalculator): AnimatorSet { + val halfDuration = animDuration / 2 + + val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1 + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, upTo) + translateUp.duration = halfDuration + translateUp.interpolator = LinearInterpolator() + + val scaleDuration = halfDuration - halfDuration / 5 + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val scale = AnimatorSet().apply { + startDelay = scaleDuration + duration = scaleDuration + interpolator = LinearInterpolator() + playTogether(scaleX, scaleY) + } + + val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) + elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + elevation.startDelay = halfDuration + elevation.duration = 100 + + val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo, properties.yTranslationEnd) + translateDown.startDelay = halfDuration + translateDown.duration = halfDuration + + return AnimatorSet().apply { + playTogether( + translateUp, + scale, + elevation, + translateDown, + ) + } +} + +fun View.pullUpAnimation(leapDuration: Long, properties: Properties): AnimatorSet { + val pullDuration = leapDuration / 2 + val delayDuration = leapDuration / 2 + + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) + elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + + return AnimatorSet().apply { + startDelay = delayDuration + duration = pullDuration + interpolator = DecelerateInterpolator() + playTogether(translateUp, scaleX, scaleY, elevation) + } +} + +fun View.pullDownAnimation(leapDuration: Long, properties: Properties): AnimatorSet { + val pullDuration = leapDuration / 2 + val delayDuration = leapDuration / 4 + + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) + elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + + return AnimatorSet().apply { + startDelay = delayDuration + duration = pullDuration + interpolator = DecelerateInterpolator() + playTogether(translate, scaleX, scaleY, elevation) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt new file mode 100644 index 0000000000..b3a115f964 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.common.leapfrogWidget + +import android.view.View + +/** +[REDACTED_AUTHOR] + */ +class LeapView( + val view: View, + val index: Int, + position: Int, + val maximalPosition: Int, + val initialProperties: Properties +) { + var currentPosition: Int = position + private set + var previousPosition: Int = position + private set + + fun changePositionByLeap() { + previousPosition = currentPosition + currentPosition = when (previousPosition) { + 0 -> maximalPosition + else -> currentPosition - 1 + } + } + + fun changePositionByLeapBack() { + previousPosition = currentPosition + currentPosition = when (previousPosition) { + maximalPosition -> 0 + else -> currentPosition + 1 + } + } + + override fun toString(): String = "index: $index, position: $currentPosition, code: ${view.hashCode()}" +} + +data class Properties( + val startPosition: Int, + val endPosition: Int, + val scaleStart: Float, + val scaleEnd: Float, + val elevationStart: Float, + val elevationEnd: Float, + val yTranslationStart: Float, + val yTranslationEnd: Float, + val hasForegroundStart: Boolean, + val hasForegroundEnd: Boolean, +) { + override fun toString(): String { + return """ + scaleStart: $scaleStart, scaleEnd: $scaleEnd, + elevationStart: $elevationStart, elevationEnd: $elevationEnd, + yTranslationStart: $yTranslationStart, yTranslationEnd: $yTranslationEnd, + hasForegroundStart : $hasForegroundStart, hasForegroundEnd: $hasForegroundEnd, + """.trimIndent() + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..e88e1cf49a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt @@ -0,0 +1,204 @@ +package com.tangem.tap.common.leapfrogWidget + +import android.animation.Animator +import android.animation.AnimatorSet +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import android.widget.FrameLayout +import androidx.core.view.children +import com.tangem.tangem_sdk_new.extensions.dpToPx + +/** +[REDACTED_AUTHOR] + */ +class LeapfrogWidget( + private val parentContainer: FrameLayout, + private val calculator: PropertyCalculator = PropertyCalculator(), +) { + private val initAnimationDuration = 500L + private val leapAnimationDuration = 800L + private val leapBackAnimationDuration = 800L + + private val leapViews = mutableListOf() + + private var leapProgress: Int = 100 + private var leapBackProgress: Int = 100 + + init { + calculator.setTranslationConverter { parentContainer.dpToPx(it) } + init() + } + + private fun init() { + if (!viewIsFullFledged()) return + + val maxPosition = parentContainer.childCount - 1 + parentContainer.children.forEachIndexed { index, view -> + val initialPosition = maxPosition - index + val initialProperties = createViewProperty(0, initialPosition) + leapViews.add(LeapView(view, index, initialPosition, maxPosition, initialProperties)) + } + resetToInitialState() + } + + private fun resetToInitialState(withAnimation: Boolean = true, duration: Long = initAnimationDuration) { + if (!viewIsFullFledged()) return + + if (withAnimation) { + val animatorsList = mutableListOf() + leapViews.forEach { animatorsList.add(it.view.initAnimation(duration, it.initialProperties)) } + val animator = AnimatorSet() + animator.interpolator = AccelerateDecelerateInterpolator() + animator.playTogether(animatorsList.toList()) + animator.start() + } else { + leapViews.forEach { + it.view.scaleX = it.initialProperties.scaleEnd + it.view.scaleY = it.initialProperties.scaleEnd + it.view.translationY = it.initialProperties.yTranslationEnd + it.view.elevation = it.initialProperties.elevationEnd + } + } + } + + fun leap(listener: ProgressListener? = null) { + if (!canLeap()) return + + // if not - then it's ready for pullUp + fun isReadyForLeap(leapView: LeapView): Boolean { + return leapView.previousPosition == 0 && leapView.currentPosition == leapView.maximalPosition + } + + val animatorsList = mutableListOf() + leapViews.forEach { + it.changePositionByLeap() + val properties = createViewProperty(it.previousPosition, it.currentPosition) + if (isReadyForLeap(it)) { + val overLift = calculator.overLift(leapViews.size) + animatorsList.add(it.view.leapAnimation(leapAnimationDuration, properties, overLift)) + } else { + animatorsList.add(it.view.pullUpAnimation(leapAnimationDuration, properties)) + } + } + val animator = AnimatorSet() + animatorsList.add(animator.createProgressListener(leapAnimationDuration) { leapProgress = it }) + animatorsList.add(animator.createProgressListener(leapAnimationDuration, listener)) + animator.playTogether(animatorsList.toList()) + animator.start() + } + + fun leapBack(listener: ProgressListener? = null) { + if (!canLeapBack()) return + + // if not - then it's ready for pullDown + fun isReadyForLeapBack(leapView: LeapView): Boolean { + return leapView.previousPosition == leapView.maximalPosition && leapView.currentPosition == 0 + } + + val animatorsList = mutableListOf() + leapViews.forEach { + it.changePositionByLeapBack() + val properties = createViewProperty(it.previousPosition, it.currentPosition) + if (isReadyForLeapBack(it)) { + val overLift = calculator.overLift(leapViews.size) + animatorsList.add(it.view.leapBackAnimation(leapBackAnimationDuration, properties, calculator)) + } else { + animatorsList.add(it.view.pullDownAnimation(leapBackAnimationDuration, properties)) + } + } + val animator = AnimatorSet() + animatorsList.add(animator.createProgressListener(leapBackAnimationDuration) { leapBackProgress = it }) + animatorsList.add(animator.createProgressListener(leapBackAnimationDuration, listener)) + animator.playTogether(animatorsList.toList()) + animator.start() + } + + fun getViewByPosition(position: Int): View? { + return leapViews.firstOrNull { it.currentPosition == position }?.view + } + + private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1 + + private fun canLeap(): Boolean { + return when { + !viewIsFullFledged() -> false + leapBackInProgress() -> false + leapProgress < 70 -> false + else -> true + } + } + + private fun canLeapBack(): Boolean { + return when { + !viewIsFullFledged() -> false + leapInProgress() -> false + leapBackProgress < 70 -> false + else -> true + } + } + + private fun leapInProgress(): Boolean = leapProgress != 100 + private fun leapBackInProgress(): Boolean = leapBackProgress != 100 + + private fun createViewProperty(startPosition: Int, endPosition: Int): Properties { + return Properties( + startPosition, + endPosition, + calculator.scale(startPosition), + calculator.scale(endPosition), + calculator.elevation(startPosition, parentContainer.childCount), + calculator.elevation(endPosition, parentContainer.childCount), + calculator.yTranslation(startPosition), + calculator.yTranslation(endPosition), + calculator.hasForeground(startPosition), + calculator.hasForeground(endPosition), + ) + } +} + +class PropertyCalculator( + val elevationFactor: Float = 1f, + val decreaseScaleFactor: Float = 0.1f, + val yTranslationFactor: Float = 20f, + val decreaseOverLiftingFactor: Float = 0.85f, +) { + // 1st view it is view on the top of stack, but it is last in parent.children + + private var translationValueConverter: ((Float) -> Float)? = null + fun setTranslationConverter(converter: (Float) -> Float) { + translationValueConverter = converter + } + + fun scale(position: Int): Float { + return 1f - decreaseScaleFactor * position + } + + fun elevation(position: Int, count: Int): Float { + return elevationFactor * (count - 1 - position) + } + + fun yTranslation(position: Int): Float { + val yTranslation = yTranslationFactor * position + return convertTranslation(yTranslation) + } + + fun hasForeground(position: Int): Boolean { + return position != 0 + } + + fun overLift(viewCount: Int): Float { + val initialOverLift = when { + yTranslationFactor < 0f -> yTranslationFactor * decreaseOverLiftingFactor * viewCount * -1 + else -> 10f + } + val scaleOverLift = when { + decreaseScaleFactor == 0f -> initialOverLift + else -> initialOverLift * decreaseOverLiftingFactor - initialOverLift * decreaseScaleFactor + } + return convertTranslation(scaleOverLift) + } + + private fun convertTranslation(value: Float): Float { + return translationValueConverter?.invoke(value) ?: value + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/leapfrog_widget.xml b/app/src/main/res/layout/leapfrog_widget.xml new file mode 100644 index 0000000000..13a8e61d88 --- /dev/null +++ b/app/src/main/res/layout/leapfrog_widget.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file From 75a66a1b5b1dc2b036cdec673bed56a73d55112d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Oct 2021 18:25:19 +0300 Subject: [PATCH 34/55] Updated on 2026-08-14 --- .../onboarding/service/ProductOnboardingService.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt index 44c4c99980..cf9276dd5a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt @@ -1,10 +1,7 @@ package com.tangem.tap.features.onboarding.service import android.util.Log -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.* import com.tangem.common.card.Card import com.tangem.common.extensions.guard import com.tangem.common.extensions.isZero @@ -159,7 +156,7 @@ abstract class ProductOnboardingService( OnboardingWalletBalance.criticalError(customError) } else { val balance = if (valueOfAmount.isZero()) { - OnboardingWalletBalance.done(valueOfAmount, hasForIncomingTransactions(), currency) + OnboardingWalletBalance.done(valueOfAmount, hasForIncomingTransactions(wallet), currency) } else { OnboardingWalletBalance.done(valueOfAmount, false, currency) } @@ -188,8 +185,8 @@ abstract class ProductOnboardingService( cardInfoStorage.activationFinished(scanResponse.card.cardId) } - protected open suspend fun hasForIncomingTransactions(): Boolean { - return false + protected open suspend fun hasForIncomingTransactions(wallet: Wallet): Boolean { + return wallet.recentTransactions.isNotEmpty() } protected open fun tryToProceed() { From 8ed876086db7f7242dfc690ec13367e786d21002 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Oct 2021 23:44:29 +0300 Subject: [PATCH 35/55] Updated on 2026-08-14 --- .../tap/common/leapfrogWidget/Animations.kt | 25 ++-- .../tap/common/leapfrogWidget/LeapView.kt | 113 ++++++++++++++- .../common/leapfrogWidget/LeapfrogWidget.kt | 131 +++++++++--------- 3 files changed, 191 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt index b89618ee5f..77c9f1fcbb 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt @@ -25,24 +25,29 @@ import android.view.animation.LinearInterpolator typealias ProgressListener = (Int) -> Unit -fun AnimatorSet.createProgressListener(duration: Long, listener: ProgressListener?): Animator { +fun createProgressListener(duration: Long, listener: ProgressListener?): Animator { val valueAnimator = ValueAnimator.ofInt(0, 100) valueAnimator.duration = duration valueAnimator.addUpdateListener { listener?.invoke((it.animatedValue as? Int) ?: 0) } return valueAnimator } -fun View.initAnimation(animDuration: Long, properties: Properties): AnimatorSet { - elevation = properties.elevationEnd - scaleX = properties.scaleEnd - scaleY = properties.scaleEnd - +fun View.unfoldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) translate.duration = animDuration return AnimatorSet().apply { playTogether(translate) } } -fun View.leapAnimation(animDuration: Long, properties: Properties, overLift: Float): AnimatorSet { +fun View.foldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) + translate.duration = animDuration + + return AnimatorSet().apply { + playSequentially(translate) + } +} + +fun View.leapAnimation(animDuration: Long, properties: AnimationProperties, overLift: Float): AnimatorSet { val halfDuration = animDuration / 2 val upTo = (height.toFloat() + properties.yTranslationStart + overLift) * -1 @@ -80,7 +85,7 @@ fun View.leapAnimation(animDuration: Long, properties: Properties, overLift: Flo } } -fun View.leapBackAnimation(animDuration: Long, properties: Properties, calculator: PropertyCalculator): AnimatorSet { +fun View.leapBackAnimation(animDuration: Long, properties: AnimationProperties, calculator: PropertyCalculator): AnimatorSet { val halfDuration = animDuration / 2 val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1 @@ -117,7 +122,7 @@ fun View.leapBackAnimation(animDuration: Long, properties: Properties, calculato } } -fun View.pullUpAnimation(leapDuration: Long, properties: Properties): AnimatorSet { +fun View.pullUpAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 2 @@ -135,7 +140,7 @@ fun View.pullUpAnimation(leapDuration: Long, properties: Properties): AnimatorSe } } -fun View.pullDownAnimation(leapDuration: Long, properties: Properties): AnimatorSet { +fun View.pullDownAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 4 diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt index b3a115f964..a9d5212f3e 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt @@ -9,36 +9,124 @@ class LeapView( val view: View, val index: Int, position: Int, - val maximalPosition: Int, - val initialProperties: Properties + private val maximalPosition: Int, + private val calculator: PropertyCalculator, ) { + var currentProperties: AnimationProperties + private set + var currentPosition: Int = position private set var previousPosition: Int = position private set - fun changePositionByLeap() { + init { + val initialProperties = createInitialProperty(position) + currentProperties = AnimationProperties.from(initialProperties, initialProperties) + + view.elevation = initialProperties.elevation + view.translationY = initialProperties.yTranslation + view.scaleX = initialProperties.scale + view.scaleY = initialProperties.scale +// initialProperties.hasForeground + } + + fun leap(): LeapFrogAnimation { previousPosition = currentPosition currentPosition = when (previousPosition) { 0 -> maximalPosition else -> currentPosition - 1 } + updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + + return when { + previousPosition == 0 && currentPosition == maximalPosition -> LeapFrogAnimation.LEAP + else -> LeapFrogAnimation.PULL + } } - fun changePositionByLeapBack() { + fun leapBack(): LeapFrogAnimation { previousPosition = currentPosition currentPosition = when (previousPosition) { maximalPosition -> 0 else -> currentPosition + 1 } + updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + + return when { + previousPosition == maximalPosition && currentPosition == 0 -> LeapFrogAnimation.LEAP + else -> LeapFrogAnimation.PULL + } + } + + fun fold() { + updateProperties(currentProperties.toFold()) + } + + fun unfold() { + updateProperties(currentProperties.toUnfold(calculator)) + } + + private fun updateProperties(properties: AnimationProperties) { + currentProperties = properties + } + + private fun createInitialProperty(endPosition: Int): Properties { + return Properties( + endPosition, + calculator.scale(endPosition), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(0), + calculator.hasForeground(endPosition), + ) + } + + private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): AnimationProperties { + return AnimationProperties( + startPosition, + endPosition, + calculator.scale(startPosition), + calculator.scale(endPosition), + calculator.elevation(startPosition, maximalPosition + 1), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(startPosition), + calculator.yTranslation(endPosition), + calculator.hasForeground(startPosition), + calculator.hasForeground(endPosition), + ) + } + + private fun AnimationProperties.toUnfold(calculator: PropertyCalculator): AnimationProperties { + val start = this.yTranslationEnd + val end = calculator.yTranslation(this.positionEnd) + return this.copy(yTranslationStart = start, yTranslationEnd = end) + } + + private fun AnimationProperties.toFold(): AnimationProperties { + val start = this.yTranslationEnd + val end = 0f + return this.copy(yTranslationStart = start, yTranslationEnd = end) } override fun toString(): String = "index: $index, position: $currentPosition, code: ${view.hashCode()}" + +} + +enum class LeapFrogAnimation { + LEAP, PULL } data class Properties( - val startPosition: Int, - val endPosition: Int, + val position: Int, + val scale: Float, + val elevation: Float, + val yTranslation: Float, + val hasForeground: Boolean, +) + +data class AnimationProperties( + val positionStart: Int, + val positionEnd: Int, val scaleStart: Float, val scaleEnd: Float, val elevationStart: Float, @@ -48,6 +136,7 @@ data class Properties( val hasForegroundStart: Boolean, val hasForegroundEnd: Boolean, ) { + override fun toString(): String { return """ scaleStart: $scaleStart, scaleEnd: $scaleEnd, @@ -56,4 +145,16 @@ data class Properties( hasForegroundStart : $hasForegroundStart, hasForegroundEnd: $hasForegroundEnd, """.trimIndent() } + + companion object { + fun from(start: Properties, end: Properties): AnimationProperties { + return AnimationProperties( + start.position, end.position, + start.scale, end.scale, + start.elevation, end.elevation, + start.yTranslation, end.yTranslation, + start.hasForeground, end.hasForeground + ) + } + } } \ No newline at end of file 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 e88e1cf49a..423be3b97b 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 @@ -15,15 +15,18 @@ class LeapfrogWidget( private val parentContainer: FrameLayout, private val calculator: PropertyCalculator = PropertyCalculator(), ) { - private val initAnimationDuration = 500L + private val foldAnimationDuration = 500L private val leapAnimationDuration = 800L private val leapBackAnimationDuration = 800L private val leapViews = mutableListOf() + private var foldUnfoldProgress: Int = 100 private var leapProgress: Int = 100 private var leapBackProgress: Int = 100 + private var isFolded: Boolean = true + init { calculator.setTranslationConverter { parentContainer.dpToPx(it) } init() @@ -35,54 +38,65 @@ class LeapfrogWidget( val maxPosition = parentContainer.childCount - 1 parentContainer.children.forEachIndexed { index, view -> val initialPosition = maxPosition - index - val initialProperties = createViewProperty(0, initialPosition) - leapViews.add(LeapView(view, index, initialPosition, maxPosition, initialProperties)) + leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator)) } - resetToInitialState() } - private fun resetToInitialState(withAnimation: Boolean = true, duration: Long = initAnimationDuration) { - if (!viewIsFullFledged()) return + fun fold(listener: ProgressListener? = null) { + if (!canFoldUnfold() || isFolded) return - if (withAnimation) { - val animatorsList = mutableListOf() - leapViews.forEach { animatorsList.add(it.view.initAnimation(duration, it.initialProperties)) } - val animator = AnimatorSet() - animator.interpolator = AccelerateDecelerateInterpolator() - animator.playTogether(animatorsList.toList()) - animator.start() - } else { - leapViews.forEach { - it.view.scaleX = it.initialProperties.scaleEnd - it.view.scaleY = it.initialProperties.scaleEnd - it.view.translationY = it.initialProperties.yTranslationEnd - it.view.elevation = it.initialProperties.elevationEnd + createFoldUnfoldAnimation(true, foldAnimationDuration, listener) + } + + fun unfold(listener: ProgressListener? = null) { + if (!canFoldUnfold() || !isFolded) return + + createFoldUnfoldAnimation(false, foldAnimationDuration, listener) + } + + private fun createFoldUnfoldAnimation(isFoldAnimation: Boolean, duration: Long, listener: ProgressListener?) { + val animatorsList = mutableListOf() + leapViews.forEach { + if (isFoldAnimation) { + it.fold() + animatorsList.add(it.view.foldAnimation(duration, it.currentProperties)) + } else { + it.unfold() + animatorsList.add(it.view.unfoldAnimation(duration, it.currentProperties)) } } + animatorsList.add(createProgressListener(duration) { + foldUnfoldProgress = it + if (it == 100) isFolded = isFoldAnimation + }) + animatorsList.add(createProgressListener(duration, listener)) + + val animator = AnimatorSet() + animator.interpolator = AccelerateDecelerateInterpolator() + animator.playTogether(animatorsList.toList()) + animator.start() } fun leap(listener: ProgressListener? = null) { if (!canLeap()) return - // if not - then it's ready for pullUp - fun isReadyForLeap(leapView: LeapView): Boolean { - return leapView.previousPosition == 0 && leapView.currentPosition == leapView.maximalPosition - } - + val duration = leapAnimationDuration val animatorsList = mutableListOf() leapViews.forEach { - it.changePositionByLeap() - val properties = createViewProperty(it.previousPosition, it.currentPosition) - if (isReadyForLeap(it)) { - val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapAnimation(leapAnimationDuration, properties, overLift)) - } else { - animatorsList.add(it.view.pullUpAnimation(leapAnimationDuration, properties)) + when (it.leap()) { + LeapFrogAnimation.LEAP -> { + val overLift = calculator.overLift(leapViews.size) + animatorsList.add(it.view.leapAnimation(duration, it.currentProperties, overLift)) + } + LeapFrogAnimation.PULL -> { + animatorsList.add(it.view.pullUpAnimation(duration, it.currentProperties)) + } } } + animatorsList.add(createProgressListener(duration) { leapProgress = it }) + animatorsList.add(createProgressListener(duration, listener)) + val animator = AnimatorSet() - animatorsList.add(animator.createProgressListener(leapAnimationDuration) { leapProgress = it }) - animatorsList.add(animator.createProgressListener(leapAnimationDuration, listener)) animator.playTogether(animatorsList.toList()) animator.start() } @@ -90,25 +104,21 @@ class LeapfrogWidget( fun leapBack(listener: ProgressListener? = null) { if (!canLeapBack()) return - // if not - then it's ready for pullDown - fun isReadyForLeapBack(leapView: LeapView): Boolean { - return leapView.previousPosition == leapView.maximalPosition && leapView.currentPosition == 0 - } - + val duration = leapBackAnimationDuration val animatorsList = mutableListOf() leapViews.forEach { - it.changePositionByLeapBack() - val properties = createViewProperty(it.previousPosition, it.currentPosition) - if (isReadyForLeapBack(it)) { - val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapBackAnimation(leapBackAnimationDuration, properties, calculator)) - } else { - animatorsList.add(it.view.pullDownAnimation(leapBackAnimationDuration, properties)) + when (it.leapBack()) { + LeapFrogAnimation.LEAP -> { + animatorsList.add(it.view.leapBackAnimation(duration, it.currentProperties, calculator)) + } + LeapFrogAnimation.PULL -> { + animatorsList.add(it.view.pullDownAnimation(duration, it.currentProperties)) + } } } + animatorsList.add(createProgressListener(duration) { leapBackProgress = it }) + animatorsList.add(createProgressListener(duration, listener)) val animator = AnimatorSet() - animatorsList.add(animator.createProgressListener(leapBackAnimationDuration) { leapBackProgress = it }) - animatorsList.add(animator.createProgressListener(leapBackAnimationDuration, listener)) animator.playTogether(animatorsList.toList()) animator.start() } @@ -119,9 +129,19 @@ class LeapfrogWidget( private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1 + private fun canFoldUnfold(): Boolean { + return when { + !viewIsFullFledged() -> false + foldUnfoldInProgress() -> false + leapInProgress() || leapBackInProgress() -> false + else -> true + } + } + private fun canLeap(): Boolean { return when { !viewIsFullFledged() -> false + isFolded -> false leapBackInProgress() -> false leapProgress < 70 -> false else -> true @@ -131,29 +151,16 @@ class LeapfrogWidget( private fun canLeapBack(): Boolean { return when { !viewIsFullFledged() -> false + isFolded -> false leapInProgress() -> false leapBackProgress < 70 -> false else -> true } } + private fun foldUnfoldInProgress(): Boolean = foldUnfoldProgress != 100 private fun leapInProgress(): Boolean = leapProgress != 100 private fun leapBackInProgress(): Boolean = leapBackProgress != 100 - - private fun createViewProperty(startPosition: Int, endPosition: Int): Properties { - return Properties( - startPosition, - endPosition, - calculator.scale(startPosition), - calculator.scale(endPosition), - calculator.elevation(startPosition, parentContainer.childCount), - calculator.elevation(endPosition, parentContainer.childCount), - calculator.yTranslation(startPosition), - calculator.yTranslation(endPosition), - calculator.hasForeground(startPosition), - calculator.hasForeground(endPosition), - ) - } } class PropertyCalculator( From 033028510647d1db2c5590e44cd7ae4f1819dc0d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Oct 2021 00:11:31 +0300 Subject: [PATCH 36/55] Updated on 2026-08-14 --- .../tap/common/leapfrogWidget/LeapView.kt | 39 +++++++++++++++++-- .../common/leapfrogWidget/LeapfrogWidget.kt | 14 +++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt index a9d5212f3e..0c4d37a066 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt @@ -23,11 +23,14 @@ class LeapView( init { val initialProperties = createInitialProperty(position) currentProperties = AnimationProperties.from(initialProperties, initialProperties) + initView(initialProperties) + } - view.elevation = initialProperties.elevation - view.translationY = initialProperties.yTranslation - view.scaleX = initialProperties.scale - view.scaleY = initialProperties.scale + private fun initView(properties: Properties) { + view.elevation = properties.elevation + view.translationY = properties.yTranslation + view.scaleX = properties.scale + view.scaleY = properties.scale // initialProperties.hasForeground } @@ -67,6 +70,17 @@ class LeapView( updateProperties(currentProperties.toUnfold(calculator)) } + fun getState(): LeapViewState { + return LeapViewState(index, currentPosition, previousPosition, currentProperties) + } + + fun applyState(state: LeapViewState) { + previousPosition = state.previousPosition + currentPosition = state.currentPosition + updateProperties(state.properties) + initView(state.properties.endProperties()) + } + private fun updateProperties(properties: AnimationProperties) { currentProperties = properties } @@ -112,6 +126,13 @@ class LeapView( } +data class LeapViewState( + val index: Int, + val currentPosition: Int, + val previousPosition: Int, + val properties: AnimationProperties +) + enum class LeapFrogAnimation { LEAP, PULL } @@ -137,6 +158,16 @@ data class AnimationProperties( val hasForegroundEnd: Boolean, ) { + fun endProperties(): Properties { + return Properties( + positionEnd, + scaleEnd, + elevationEnd, + yTranslationEnd, + hasForegroundEnd, + ) + } + override fun toString(): String { return """ scaleStart: $scaleStart, scaleEnd: $scaleEnd, 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 423be3b97b..37b04d4f2a 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 @@ -127,6 +127,15 @@ class LeapfrogWidget( return leapViews.firstOrNull { it.currentPosition == position }?.view } + fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(isFolded, leapViews.map { it.getState() }) + + fun applyState(state: LeapfrogWidgetState) { + isFolded = state.isFolded + state.leapViewStates.forEach { state -> + leapViews.firstOrNull { it.index == state.index }?.applyState(state) + } + } + private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1 private fun canFoldUnfold(): Boolean { @@ -163,6 +172,11 @@ class LeapfrogWidget( private fun leapBackInProgress(): Boolean = leapBackProgress != 100 } +data class LeapfrogWidgetState( + val isFolded: Boolean, + val leapViewStates: List +) + class PropertyCalculator( val elevationFactor: Float = 1f, val decreaseScaleFactor: Float = 0.1f, From fd214bffd818a7d1b88a1dbfd666ed99134fa717 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 18:54:49 +0300 Subject: [PATCH 37/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 2 +- .../com/tangem/tap/common/redux/AppReducer.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 4 + .../tap/common/redux/global/GlobalAction.kt | 8 +- .../tap/common/redux/global/GlobalReducer.kt | 10 +- .../tap/common/redux/global/GlobalState.kt | 28 ++--- .../java/com/tangem/tap/domain/ProductType.kt | 8 ++ .../com/tangem/tap/domain/TangemSdkManager.kt | 28 ++--- .../com/tangem/tap/domain/TapWalletManager.kt | 10 +- .../domain/extensions/WalletManagerFactory.kt | 4 +- .../domain/tasks/CreateWalletAndRescanTask.kt | 1 + .../tap/domain/tasks/CreateWalletsTask.kt | 13 +-- .../tangem/tap/domain/tasks/ScanNoteTask.kt | 64 +++------- .../CreateProductWalletAndRescanTask.kt | 80 +++++-------- .../tasks/product/CreateProductWalletsTask.kt | 49 ++++++++ .../domain/tasks/product/ScanProductTask.kt | 109 +++++++++++++----- .../tap/domain/twins/FinalizeTwinTask.kt | 10 +- .../tap/domain/twins/TwinCardsManager.kt | 16 +-- .../features/details/redux/DetailsAction.kt | 18 +-- .../features/details/redux/DetailsReducer.kt | 2 +- .../redux/twins/CreateTwinWalletMiddleware.kt | 4 +- .../redux/twins/CreateTwinWalletState.kt | 14 +-- .../walletconnect/WalletConnectMiddleware.kt | 4 +- .../tap/features/home/redux/HomeMiddleware.kt | 7 +- .../products/note/OnboardingNoteFragment.kt | 4 +- .../note/redux/OnboardingNoteMiddleware.kt | 2 +- .../wallet/OnboardingWalletFragment.kt | 1 + .../onboarding/service/OnboardingHelper.kt | 18 +-- .../service/OnboardingNoteService.kt | 4 +- .../service/ProductOnboardingService.kt | 7 +- .../send/redux/middlewares/SendMiddleware.kt | 4 +- .../features/tokens/redux/TokensMiddleware.kt | 2 +- .../middlewares/MultiWalletMiddleware.kt | 17 ++- .../redux/middlewares/WalletMiddleware.kt | 12 +- .../redux/middlewares/WarningsMiddleware.kt | 6 +- .../tap/features/wallet/ui/WalletFragment.kt | 2 +- 36 files changed, 313 insertions(+), 261 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/ProductType.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 49856db42b..dbef280f6f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -74,7 +74,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { notificationsHandler = NotificationsHandler(fragment_container) val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 - val isScannedBefore = store.state.globalState.scanNoteResponse != null + val isScannedBefore = store.state.globalState.scanResponse != null val isOnboardingServiceActive = store.state.globalState.onboardingService != null if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) 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 4de2f24938..38c76819b3 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 @@ -7,6 +7,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer 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.wallet.redux.OnboardingWalletReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.tokens.redux.TokensReducer @@ -23,6 +24,7 @@ fun appReducer(action: Action, state: AppState?): AppState { 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), 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 ba9aa021d1..40e0c80077 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 @@ -14,6 +14,8 @@ import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware 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.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware @@ -31,6 +33,7 @@ data class AppState( val homeState: HomeState = HomeState(), val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(), val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(), + val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(), val walletState: WalletState = WalletState(), val sendState: SendState = SendState(), val detailsState: DetailsState = DetailsState(), @@ -47,6 +50,7 @@ data class AppState( HomeMiddleware.handler, OnboardingNoteMiddleware.handler, OnboardingWalletMiddleware.handler, + OnboardingOtherCardsMiddleware.handler, WalletMiddleware().walletMiddleware, SendMiddleware().sendMiddleware, DetailsMiddleware().detailsMiddleware, 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 4021557fac..50efb26994 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 @@ -8,7 +8,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager @@ -34,18 +34,18 @@ sealed class GlobalAction : Action { } data class ReadCard( - val onSuccess: ((ScanNoteResponse) -> Unit)? = null, + val onSuccess: ((ScanResponse) -> Unit)? = null, val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, ) : GlobalAction() object ScanFailsCounter { - data class ChooseBehavior(val result: CompletionResult) : GlobalAction() + data class ChooseBehavior(val result: CompletionResult) : GlobalAction() object Reset : GlobalAction() object Increment : GlobalAction() } - data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction() + data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction() data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 380fe6847f..30eabac6e4 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 @@ -23,7 +23,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(scanCardFailsCounter = 0) } is GlobalAction.SaveScanNoteResponse -> - globalState.copy(scanNoteResponse = action.scanNoteResponse) + globalState.copy(scanResponse = action.scanResponse) is GlobalAction.ChangeAppCurrency -> { globalState.copy(appCurrency = action.appCurrency) } @@ -35,17 +35,17 @@ fun globalReducer(action: Action, state: AppState): GlobalState { } is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) is GlobalAction.UpdateWalletSignedHashes -> { - val wallet = globalState.scanNoteResponse?.card + val wallet = globalState.scanResponse?.card ?.wallet(action.walletPublicKey) ?.copy( totalSignedHashes = action.walletSignedHashes, remainingSignatures = action.remainingSignatures ) - val card = globalState.scanNoteResponse?.card - wallet?.let { globalState.scanNoteResponse.card.updateWallet(wallet) } + val card = globalState.scanResponse?.card + wallet?.let { globalState.scanResponse.card.updateWallet(wallet) } if (card != null) { - globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) + globalState.copy(scanResponse = globalState.scanResponse.copy(card = card)) } else { globalState } 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 0753b6d472..94eb578fc4 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 @@ -6,7 +6,7 @@ import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.feedback.FeedbackManager import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.coinmarketcap.CoinMarketCapService @@ -14,19 +14,19 @@ import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.StateType data class GlobalState( - val scanNoteResponse: ScanNoteResponse? = null, - val cardVerifiedOnline: Boolean = false, - val tapWalletManager: TapWalletManager = TapWalletManager(), - val payIdManager: PayIdManager = PayIdManager(), - val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(), - val configManager: ConfigManager? = null, - val warningManager: WarningMessagesManager? = null, - val feedbackManager: FeedbackManager? = null, - val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, - val scanCardFailsCounter: Int = 0, - val dialog: StateDialog? = null, - val moonPayUserStatus: MoonPayUserStatus? = null, - val onboardingService: ProductOnboardingService? = null, + val scanResponse: ScanResponse? = null, + val cardVerifiedOnline: Boolean = false, + val tapWalletManager: TapWalletManager = TapWalletManager(), + val payIdManager: PayIdManager = PayIdManager(), + val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(), + val configManager: ConfigManager? = null, + val warningManager: WarningMessagesManager? = null, + val feedbackManager: FeedbackManager? = null, + val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, + val scanCardFailsCounter: Int = 0, + val dialog: StateDialog? = null, + val moonPayUserStatus: MoonPayUserStatus? = null, + val onboardingService: ProductOnboardingService? = null, ) : StateType typealias CryptoCurrencyName = String diff --git a/app/src/main/java/com/tangem/tap/domain/ProductType.kt b/app/src/main/java/com/tangem/tap/domain/ProductType.kt new file mode 100644 index 0000000000..02f0246c82 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/ProductType.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain + +/** +[REDACTED_AUTHOR] + */ +enum class ProductType { + Note, Wallet, Twin, 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 4cc78b775b..5253a9f91c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -21,9 +21,8 @@ import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.domain.tasks.ScanNoteTask 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 @@ -33,23 +32,10 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { - @Deprecated("Use can product") - suspend fun scanNote( - analyticsHandler: AnalyticsHandler, - messageRes: Int? = null, - ): CompletionResult { - analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) - - return runTaskAsyncReturnOnMain( - ScanNoteTask(), - initialMessage = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) - ).also { sendScanFailuresToAnalytics(analyticsHandler, it) } - } - suspend fun scanProduct( analyticsHandler: AnalyticsHandler, messageRes: Int? = null, - ): CompletionResult { + ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) @@ -57,13 +43,17 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .also { sendScanFailuresToAnalytics(analyticsHandler, it) } } - suspend fun createProductWallet(cardId: String?): CompletionResult { - return runTaskAsync(CreateProductWalletAndRescanTask(), cardId, Message(context.getString(R.string.initial_message_create_wallet_body))) + suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult { + return runTaskAsync( + CreateProductWalletAndRescanTask(scanResponse.productType), + scanResponse.card.cardId, + Message(context.getString(R.string.initial_message_create_wallet_body)) + ) } private fun sendScanFailuresToAnalytics( analyticsHandler: AnalyticsHandler, - result: CompletionResult + result: CompletionResult ) { if (result is CompletionResult.Failure && result.error is TangemSdkError) { (result.error as? TangemSdkError)?.let { error -> 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 507db2ad21..8a592c14ad 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -14,7 +14,7 @@ import com.tangem.tap.domain.TapWorkarounds.isStart2Coin import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.* -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.domain.twins.isTangemTwin @@ -77,7 +77,7 @@ class TapWalletManager { handleFiatRatesResult(results) } - suspend fun onCardScanned(data: ScanNoteResponse, addAnalyticsEvent: Boolean = false) { + suspend fun onCardScanned(data: ScanResponse, addAnalyticsEvent: Boolean = false) { if (addAnalyticsEvent) { FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain) } @@ -108,7 +108,7 @@ class TapWalletManager { } } - private fun updateConfigManager(data: ScanNoteResponse) { + private fun updateConfigManager(data: ScanResponse) { val configManager = store.state.globalState.configManager val blockchain = data.getBlockchain() if (data.card.isStart2Coin) { @@ -124,7 +124,7 @@ class TapWalletManager { } } - suspend fun loadData(data: ScanNoteResponse) { + suspend fun loadData(data: ScanResponse) { withContext(Dispatchers.Main) { store.dispatch(WalletAction.LoadCardInfo(data.card)) @@ -220,7 +220,7 @@ class TapWalletManager { } } - suspend fun reloadData(data: ScanNoteResponse) { + suspend fun reloadData(data: ScanResponse) { withContext(Dispatchers.Main) { when { data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { 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 0ab0ec0293..99ed8231c4 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 @@ -8,7 +8,7 @@ import com.tangem.common.card.CardWallet 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.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.twins.isTangemTwin fun WalletManagerFactory.makeWalletManagerForApp( @@ -50,7 +50,7 @@ fun WalletManagerFactory.makeWalletManagersForApp( } fun WalletManagerFactory.makePrimaryWalletManager( - data: ScanNoteResponse, + data: ScanResponse, ): WalletManager? { val card = data.card val blockchain = if (card.isTestCard) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt index c75a37a709..1487f07e37 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt @@ -11,6 +11,7 @@ import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.wallet.CreateWalletCommand +@Deprecated("Use CreateProductWalletAndRescanTask instead") class CreateWalletAndRescanTask : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt index 34d203eac6..6de34f651a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -9,6 +9,7 @@ import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.wallet.CreateWalletCommand +@Deprecated("Use CreateProductWalletsResponse instead") class CreateWalletsTask(curves: List? = null) : CardSessionRunnable { private val curves = curves ?: listOf( @@ -25,8 +26,8 @@ class CreateWalletsTask(curves: List? = null) : CardSessionRunnab } private fun createWallet( - curve: EllipticCurve, session: CardSession, - callback: (result: CompletionResult) -> Unit + curve: EllipticCurve, session: CardSession, + callback: (result: CompletionResult) -> Unit ) { CreateWalletCommand(curve).run(session) { result -> @@ -45,12 +46,4 @@ class CreateWalletsTask(curves: List? = null) : CardSessionRunnab } } } -} - -private fun EllipticCurve.toWalletIndex(): Int { - return when (this) { - EllipticCurve.Secp256k1 -> 0 - EllipticCurve.Ed25519 -> 1 - EllipticCurve.Secp256r1 -> 2 - } } \ No newline at end of file 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 39f5d63af0..b2b415dc15 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 @@ -1,56 +1,29 @@ package com.tangem.tap.domain.tasks import com.tangem.* -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion -import com.tangem.common.card.WalletData 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.CommandResponse 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.getTangemNoteBlockchain 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 -data class ScanNoteResponse( - val card: Card, - val walletData: WalletData?, - val secondTwinPublicKey: String? = null, -) : CommandResponse { - - fun getBlockchain(): Blockchain { - if (card.isTangemNote()) return card.getTangemNoteBlockchain() ?: return Blockchain.Unknown - val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown - return Blockchain.fromId(blockchainName) - } - - fun getPrimaryToken(): Token? { - val cardToken = walletData?.token ?: return null - return Token( - cardToken.name, - cardToken.symbol, - cardToken.contractAddress, - cardToken.decimals, - Blockchain.fromId(walletData.blockchain) - ) - } -} - -class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { +class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { override fun run( session: CardSession, - callback: (result: CompletionResult) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { ScanTask().run(session) { result -> when (result) { @@ -71,7 +44,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { val walletData = session.environment.walletData if (card.wallets.isEmpty()) { - callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) return } @@ -93,14 +66,14 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable when (result) { is CompletionResult.Success -> - callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData))) + callback(CompletionResult.Success(ScanResponse(result.data, ProductType.Other, walletData))) is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } @@ -109,34 +82,35 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { ReadIssuerDataCommand().run(session) { readDataResult -> when (readDataResult) { is CompletionResult.Success -> { val publicKey = card.getSingleWallet()?.publicKey if (publicKey == null) { - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) return@run } val verified = TwinCardsManager.verifyTwinPublicKey( - readDataResult.data.issuerData, publicKey + readDataResult.data.issuerData, publicKey ) if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) callback(CompletionResult.Success( - ScanNoteResponse( - card = card, - walletData = session.environment.walletData, - secondTwinPublicKey = twinPublicKey.toHexString()) + ScanResponse( + card = card, + ProductType.Other, + walletData = session.environment.walletData, + secondTwinPublicKey = twinPublicKey.toHexString()) )) return@run } else { - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) } } is CompletionResult.Failure -> - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) } } } 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 6134e5de55..3b3c6ea2ae 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 @@ -1,6 +1,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemSdkError @@ -9,19 +10,25 @@ import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain -import com.tangem.tap.domain.TapWorkarounds.isTangemNote -import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.tasks.product.CreateProductWalletsTask import com.tangem.tap.domain.tasks.product.ProductCommandProcessor -import com.tangem.tap.domain.twins.isTangemTwin +import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets /** [REDACTED_AUTHOR] */ -class CreateProductWalletAndRescanTask : CardSessionRunnable { +class CreateProductWalletAndRescanTask( + private val type: ProductType, +) : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - CreateProductWalletTask().run(session) { result -> + val card = session.environment.card.guard { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + CreateProductWalletTask(card, type).run(session) { result -> when (result) { is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) @@ -30,18 +37,16 @@ class CreateProductWalletAndRescanTask : CardSessionRunnable { } } -private class CreateProductWalletTask : CardSessionRunnable { +private class CreateProductWalletTask( + private val card: Card, + private val type: ProductType, +) : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card.guard { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - return - } - - val commandProcessor = when { - card.isTangemNote() -> CreateWalletTangemNote() - card.isTangemTwin() -> CreateWalletTangemTwin() - card.isTangemWallet() -> CreateWalletTangemWallet() + val commandProcessor = when (type) { + ProductType.Note -> CreateWalletTangemNote() + ProductType.Twin -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") + ProductType.Wallet -> CreateWalletTangemWallet() else -> CreateWalletOtherCards() } commandProcessor.proceed(card, session, callback) @@ -86,53 +91,28 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit ) { - throw UnsupportedOperationException() - } -} - -private class CreateWalletTangemTwin : ProductCommandProcessor { - override fun proceed( - card: Card, - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - throw UnsupportedOperationException() + //TODO: } } private class CreateWalletOtherCards : ProductCommandProcessor { - private var index = 0 - - private val curves = EllipticCurve.values().toList() - override fun proceed( card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit ) { - val curve = curves[index] - createWallet(curve, session, callback) - } + val firmwareVersion = card.firmwareVersion + val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) { + CreateProductWalletsTask(listOf(card.supportedCurves.first())) + } else { + CreateProductWalletsTask(card.getCurvesForNonCreatedWallets()) + } - private fun createWallet( - curve: EllipticCurve, - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - CreateWalletCommand(curve).run(session) { result -> + task.run(session) { result -> when (result) { - is CompletionResult.Success -> { - if (index == curves.lastIndex) { - callback(CompletionResult.Success(result.data)) - return@run - } - index += 1 - createWallet(curves[index], session, callback) - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } + is CompletionResult.Success -> callback(CompletionResult.Success(result.data.createWalletResponses[0])) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt new file mode 100644 index 0000000000..fc00138a57 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.common.CompletionResult +import com.tangem.common.card.EllipticCurve +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.operations.CommandResponse +import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.wallet.CreateWalletResponse + +/** +[REDACTED_AUTHOR] + */ +class CreateProductWalletsResponse( + val createWalletResponses: List +) : CommandResponse + +class CreateProductWalletsTask( + private val curves: List, +) : CardSessionRunnable { + + private val createdWalletsResponses = mutableListOf() + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val curve = curves[createdWalletsResponses.size] + createWallet(curve, session, callback) + } + + private fun createWallet( + curve: EllipticCurve, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + + CreateWalletCommand(curve).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + createdWalletsResponses.add(result.data) + if (createdWalletsResponses.size == curves.size) { + callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses))) + return@run + } + createWallet(curves[createdWalletsResponses.size], session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 710e6e99ff..3493d1c322 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -1,29 +1,62 @@ package com.tangem.tap.domain.tasks.product import com.tangem.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.Card +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion +import com.tangem.common.card.WalletData 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.CommandResponse +import com.tangem.operations.PreflightReadMode +import com.tangem.operations.PreflightReadTask 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.getTangemNoteBlockchain import com.tangem.tap.domain.TapWorkarounds.isExcluded import com.tangem.tap.domain.TapWorkarounds.isTangemNote import com.tangem.tap.domain.TapWorkarounds.isTangemWallet import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.tap.domain.tasks.CreateWalletsTask -import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.twins.isTangemTwin -class ScanProductTask(val card: Card? = null) : CardSessionRunnable { +data class ScanResponse( + val card: Card, + val productType: ProductType, + val walletData: WalletData?, + val secondTwinPublicKey: String? = null, +) : CommandResponse { + + fun getBlockchain(): Blockchain { + if (productType == ProductType.Note) return card.getTangemNoteBlockchain() ?: return Blockchain.Unknown + val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown + return Blockchain.fromId(blockchainName) + } + + fun getPrimaryToken(): Token? { + val cardToken = walletData?.token ?: return null + return Token( + cardToken.name, + cardToken.symbol, + cardToken.contractAddress, + cardToken.decimals, + Blockchain.fromId(walletData.blockchain) + ) + } +} + +class ScanProductTask(val card: Card? = null) : CardSessionRunnable { override fun run( session: CardSession, - callback: (result: CompletionResult) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { ScanTask().run(session) { result -> when (result) { @@ -56,38 +89,38 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable { +private class ScanNoteProcessor : ProductCommandProcessor { override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit ) { - callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Note, session.environment.walletData))) } } -private class ScanWalletProcessor : ProductCommandProcessor { +private class ScanWalletProcessor : ProductCommandProcessor { override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit ) { - callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData))) } } -private class ScanTwinProcessor : ProductCommandProcessor { +private class ScanTwinProcessor : ProductCommandProcessor { override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { ReadIssuerDataCommand().run(session) { readDataResult -> when (readDataResult) { is CompletionResult.Success -> { val publicKey = card.getSingleWallet()?.publicKey if (publicKey == null) { - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) return@run } val verified = TwinCardsManager.verifyTwinPublicKey( @@ -96,46 +129,62 @@ private class ScanTwinProcessor : ProductCommandProcessor { if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) callback(CompletionResult.Success( - ScanNoteResponse( - card = card, - walletData = session.environment.walletData, - secondTwinPublicKey = twinPublicKey.toHexString()) + ScanResponse( + card, + ProductType.Twin, + session.environment.walletData, + twinPublicKey.toHexString()) )) return@run } else { - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) } } is CompletionResult.Failure -> - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) } } } } -private class ScanOtherCardsProcessor : ProductCommandProcessor { - override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) { +private class ScanOtherCardsProcessor : ProductCommandProcessor { + + override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) { val walletData = session.environment.walletData - if (card.wallets.isEmpty()) { - callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + + if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) { + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) return } - val curvesPresent = card.wallets.map { it.curve } - val curvesToCreate = card.supportedCurves.subtract(curvesPresent) - + val curvesToCreate = card.getCurvesForNonCreatedWallets() if (curvesToCreate.isEmpty()) { - callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) return } - CreateWalletsTask().run(session) { result -> + CreateProductWalletsTask(curvesToCreate).run(session) { result -> when (result) { - is CompletionResult.Success -> - callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData))) + is CompletionResult.Success -> { + PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> { + val response = ScanResponse(readResult.data, ProductType.Other, walletData) + callback(CompletionResult.Success(response)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } +} + +fun Card.getCurvesForNonCreatedWallets(): List { + val curvesPresent = wallets.map { it.curve } + val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent) + return curvesForNonCreatedWallets.toList() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index aeef1b9481..c035973ac7 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -6,16 +6,16 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.domain.tasks.ScanNoteTask +import com.tangem.tap.domain.tasks.product.ScanProductTask +import com.tangem.tap.domain.tasks.product.ScanResponse class FinalizeTwinTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, -) : CardSessionRunnable { +) : CardSessionRunnable { override fun run( session: CardSession, - callback: (result: CompletionResult) -> Unit, + callback: (result: CompletionResult) -> Unit, ) { WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result -> when (result) { @@ -23,7 +23,7 @@ class FinalizeTwinTask( PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> - ScanNoteTask(readResult.data).run(session, callback) + ScanProductTask(readResult.data).run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) } 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 ad753690fe..d879826f12 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 @@ -14,20 +14,20 @@ import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.readAssetAsString -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.network.createMoshi import com.tangem.tap.tangemSdkManager -class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: Context) { +class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) { - private val currentCardId: String = scanNoteResponse.card.cardId + private val currentCardId: String = scanResponse.card.cardId private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null private val issuerKeyPair: KeyPair = getIssuerKeys( - context, scanNoteResponse.card.issuer.publicKey.toHexString() + context, scanResponse.card.issuer.publicKey.toHexString() ) suspend fun createFirstWallet(message: Message): SimpleResult { @@ -44,7 +44,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = scanNoteResponse.card + card = scanResponse.card ) } return SimpleResult.failure(response.error) @@ -77,7 +77,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = scanNoteResponse.card + card = scanResponse.card ) } return SimpleResult.failure(response.error) @@ -86,7 +86,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: } - suspend fun complete(message: Message): Result { + suspend fun complete(message: Message): Result { val response = tangemSdkManager.runTaskAsync( FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair), currentCardId, message @@ -98,7 +98,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData, - card = scanNoteResponse.card + card = scanResponse.card ) } Result.failure(response.error) 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 4d3882a2a7..74b37f90f4 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 @@ -7,7 +7,7 @@ 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.ScanNoteResponse +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 @@ -18,13 +18,13 @@ import org.rekotlin.Action sealed class DetailsAction : Action { data class PrepareScreen( - val card: Card, - val scanNoteResponse: ScanNoteResponse, - val wallets: List, - val isCreatingTwinWalletAllowed: Boolean?, - val cardTou: CardTou, - val fiatCurrencyName: FiatCurrencyName, - val fiatCurrencies: List? = null, + 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() @@ -81,7 +81,7 @@ sealed class DetailsAction : Action { } data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() { - data class Success(val scanNoteResponse: ScanNoteResponse) : CreateTwinWalletAction() + data class Success(val scanResponse: ScanResponse) : CreateTwinWalletAction() object Failure : CreateTwinWalletAction() } } 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 61b94447a1..04bcd51f32 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 @@ -48,7 +48,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { val twinsState = if (action.card.isTangemTwin()) { CreateTwinWalletState( - scanResponse = action.scanNoteResponse, + scanResponse = action.scanResponse, twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), createTwinWallet = null, showAlert = false, 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 index fa81ea9a18..1a6c05c2b1 100644 --- 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 @@ -48,7 +48,7 @@ class CreateTwinWalletMiddleware { store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) } is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> { - store.state.globalState.scanNoteResponse?.let { + store.state.globalState.scanResponse?.let { twinsManager = TwinCardsManager(it, action.context) } scope.launch { @@ -106,7 +106,7 @@ class CreateTwinWalletMiddleware { } is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> { scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(action.scanNoteResponse) + store.state.globalState.tapWalletManager.onCardScanned(action.scanResponse) } store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) 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 index 488ac431bc..22c96dd0bf 100644 --- 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 @@ -1,15 +1,15 @@ package com.tangem.tap.features.details.redux.twins -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.twins.TwinCardNumber data class CreateTwinWalletState( - val scanResponse: ScanNoteResponse?, - val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep, - val twinCardNumber: TwinCardNumber?, - val createTwinWallet: CreateTwinWallet?, - val showAlert: Boolean, - val allowRecreatingWallet: Boolean? = null + 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 } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 813dba5494..d316ca59ec 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -133,7 +133,7 @@ class WalletConnectMiddleware { private fun handleScanCard(wcUri: String) { scope.launch { - val result = tangemSdkManager.scanNote( + val result = tangemSdkManager.scanProduct( FirebaseAnalyticsHandler, R.string.wallet_connect_scan_card_message ) @@ -174,7 +174,7 @@ class WalletConnectMiddleware { private fun getWalletManager(card: Card): WalletManager? { val factory = store.state.globalState.tapWalletManager.walletManagerFactory - return if (store.state.globalState.scanNoteResponse?.card?.cardId == card.cardId) { + return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { store.state.walletState.getWalletManager(Blockchain.Ethereum) ?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum) ?.also { walletManager -> 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 03cdd9c2c2..3e8ccb008b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.post import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -10,7 +11,7 @@ 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.TapWalletManager -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.onboarding.service.OnboardingHelper import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.wallet.redux.ProgressState @@ -56,11 +57,11 @@ private fun handleReadCard() { store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) } - fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanNoteResponse) { - changeButtonState(ButtonState.ENABLED) + fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanResponse) { scope.launch { tapWalletManager.onCardScanned(scanResponse) delay(DELAY_SDK_DIALOG_CLOSE) + withMainContext { changeButtonState(ButtonState.ENABLED) } store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } 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 182f63d7bb..7712bbc3da 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 @@ -19,6 +19,7 @@ import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* /** @@ -50,6 +51,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { oldState.onboardingNoteState == newState.onboardingNoteState }.select { it.onboardingNoteState } } + storeSubscribersList.add(this) } override fun newState(state: OnboardingNoteState) { @@ -147,7 +149,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tv_body.setText(R.string.onboarding_done_body) imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - updateConstraints(R.layout.lp_onboarding_done) + updateConstraints(R.layout.lp_onboarding_done_activation) } private fun updateConstraints(@LayoutRes layoutId: Int) { 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 2eab43bc1b..83ea98d0d0 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 @@ -54,7 +54,7 @@ private fun handleNoteAction(action: Action) { } is OnboardingNoteAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createProductWallet(service.scanResponse.card.cardId) + val result = tangemSdkManager.createProductWallet(service.scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { 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 c947f32ab9..d0463040bd 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 @@ -32,6 +32,7 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_ oldState.onboardingWalletState == newState.onboardingWalletState }.select { it.onboardingWalletState } } + storeSubscribersList.add(this) } override fun newState(state: OnboardingWalletState) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt index 7cf88da9c5..8f7eb6f726 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.onboarding.service import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.extensions.hasWallets -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.preferencesStorage /** @@ -13,20 +13,20 @@ import com.tangem.tap.preferencesStorage class OnboardingHelper { companion object { - fun isOnboardingCase(response: ScanNoteResponse): Boolean = if (response.card.hasWallets()) + fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true fun createService( fromScreen: AppScreen, - scanResponse: ScanNoteResponse, + scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory, ): ProductOnboardingService? { - val card = scanResponse.card val cardInfoStorage = preferencesStorage.usedCardsPrefStorage - return when { - card.isTangemNote() -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, - walletManagerFactory) - else -> null + return when(scanResponse.productType) { + ProductType.Note -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) + ProductType.Wallet -> null + ProductType.Twin -> null + ProductType.Other -> OnboardingOtherCardsService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt index 4404a8e35b..feaa76eb5c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.service import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.persistence.UsedCardsPrefStorage /** @@ -10,7 +10,7 @@ import com.tangem.tap.persistence.UsedCardsPrefStorage */ class OnboardingNoteService( override val fromScreen: AppScreen, - override var scanResponse: ScanNoteResponse, + override var scanResponse: ScanResponse, cardInfoStorage: UsedCardsPrefStorage, walletManagerFactory: WalletManagerFactory, ) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt index cf9276dd5a..6faff4073e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt @@ -16,7 +16,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.UrlBitmapLoader import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork @@ -37,7 +37,7 @@ import kotlin.coroutines.suspendCoroutine */ interface OnboardingService { val fromScreen: AppScreen - val scanResponse: ScanNoteResponse + val scanResponse: ScanResponse // val cardInfoStorage: UsedCardsPrefStorage var walletManager: WalletManager? @@ -141,7 +141,7 @@ abstract class ProductOnboardingService( val walletManager = walletManager ?: walletManagerFactory.makePrimaryWalletManager(scanResponse).guard { val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") - loadedBalance = OnboardingWalletBalance.error(customError) + withMainContext { loadedBalance = OnboardingWalletBalance.error(customError) } return loadedBalance } @@ -178,7 +178,6 @@ abstract class ProductOnboardingService( override fun activationStarted() { cardInfoStorage.activationStarted(scanResponse.card.cardId) - } override fun activationFinished() { 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 c6fb811fba..26d2f8dd27 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 @@ -21,9 +21,9 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee +import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope @@ -77,7 +77,7 @@ private fun verifyAndSendTransaction( ) { val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return - val card = appState.globalState.scanNoteResponse?.card ?: return + val card = appState.globalState.scanResponse?.card ?: return val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return val typedAmount = sendState.amountState.amountToExtract ?: return val feeAmount = sendState.feeState.currentFee ?: return diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index f1bcb72e51..e8afbe2d62 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -14,7 +14,7 @@ class TokensMiddleware { { action -> when (action) { is TokensAction.LoadCurrencies -> { - val card = state()?.globalState?.scanNoteResponse?.card + val card = state()?.globalState?.scanResponse?.card val isTestcard = card?.isTestCard ?: false val tokens = currenciesRepository.getPopularTokens(isTestcard) val blockchains = currenciesRepository.getBlockchains( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index b5c15c945c..026cc26e4d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.currenciesRepository -import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.features.wallet.redux.Currency @@ -36,7 +35,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.AddToken -> { - globalState?.scanNoteResponse?.card?.cardId?.let { + globalState?.scanResponse?.card?.cardId?.let { currenciesRepository.saveAddedToken(it, action.token) } addToken(action.token, walletState, globalState) @@ -45,7 +44,7 @@ class MultiWalletMiddleware { addTokens(action.tokens, walletState, globalState) } is WalletAction.MultiWallet.AddBlockchain -> { - globalState?.scanNoteResponse?.card?.let { card -> + globalState?.scanResponse?.card?.let { card -> currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain) if (walletState?.blockchains?.contains(action.blockchain) != true) { globalState.tapWalletManager.walletManagerFactory @@ -58,11 +57,11 @@ class MultiWalletMiddleware { store.dispatch(WalletAction.LoadWallet(blockchain = action.blockchain)) } is WalletAction.MultiWallet.SaveCurrencies -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId + val cardId = globalState?.scanResponse?.card?.cardId cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) } } is WalletAction.MultiWallet.RemoveWallet -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId + val cardId = globalState?.scanResponse?.card?.cardId when (val currency = action.walletData.currency) { is Currency.Blockchain -> { cardId?.let { @@ -80,7 +79,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindBlockchainsInUse -> { - val cardFirmware = globalState?.scanNoteResponse?.card?.firmwareVersion + val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion val blockchains = currenciesRepository.getBlockchains(cardFirmware) .filterNot { walletState?.blockchains?.contains(it) == true } val walletManagers = @@ -117,7 +116,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindTokensInUse -> { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( card = card, @@ -156,7 +155,7 @@ class MultiWalletMiddleware { } private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( card = card, @@ -186,7 +185,7 @@ class MultiWalletMiddleware { walletState: WalletState?, globalState: GlobalState?, ) { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val tokensWithManagers = tokens.map { token -> val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( 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 8c0b638587..ea654a6777 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 @@ -96,7 +96,7 @@ class WalletMiddleware { is WalletAction.CreateWallet -> { if (walletState?.twinCardsState != null) { store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning( - globalState?.scanNoteResponse?.card?.cardId?.let { + globalState?.scanResponse?.card?.cardId?.let { TwinsHelper.getTwinCardNumber(it) }, CreateTwinWallet.CreateWallet @@ -104,12 +104,12 @@ class WalletMiddleware { } else { scope.launch { val result = tangemSdkManager.createWallet( - globalState?.scanNoteResponse?.card?.cardId + globalState?.scanResponse?.card?.cardId ) when (result) { is CompletionResult.Success -> { val scanNoteResponse = - globalState?.scanNoteResponse?.copy(card = result.data) + globalState?.scanResponse?.copy(card = result.data) scanNoteResponse?.let { globalState.tapWalletManager.onCardScanned(scanNoteResponse) } @@ -146,7 +146,7 @@ class WalletMiddleware { } is WalletAction.Scan -> { scope.launch { - val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler) + val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler) scope.launch(Dispatchers.Main) { store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) when (result) { @@ -186,7 +186,7 @@ class WalletMiddleware { } is WalletAction.LoadData -> { scope.launch { - val scanNoteResponse = globalState?.scanNoteResponse + val scanNoteResponse = globalState?.scanResponse ?: return@launch if (!walletState?.wallets.isNullOrEmpty()) { globalState.tapWalletManager.reloadData(scanNoteResponse) @@ -196,7 +196,7 @@ class WalletMiddleware { } } is NetworkStateChanged -> { - globalState?.scanNoteResponse?.let { scanNoteResponse -> + globalState?.scanResponse?.let { scanNoteResponse -> store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) 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 d69b213ece..39f84c1b09 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 @@ -40,7 +40,7 @@ class WarningsMiddleware { } is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline() is WalletAction.Warnings.CheckHashesCount.SaveCardId -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId + val cardId = globalState?.scanResponse?.card?.cardId cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) } } is WalletAction.Warnings.AppRating.RemindLater -> { @@ -81,7 +81,7 @@ class WarningsMiddleware { } private fun showCardWarningsIfNeeded(globalState: GlobalState?) { - globalState?.scanNoteResponse?.card?.let { card -> + globalState?.scanResponse?.card?.let { card -> globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) if (card.isTestCard) { addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true) @@ -149,7 +149,7 @@ class WarningsMiddleware { if (store.state.walletState.hashesCountVerified != false) return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return - val card = store.state.globalState.scanNoteResponse?.card + val card = store.state.globalState.scanResponse?.card if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return if (card.isTangemTwin() || card.isMultiwalletAllowed) return 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 f5f61139b1..e8f2a250fd 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 @@ -156,7 +156,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers)) - store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> + store.state.globalState.scanResponse?.let { scanNoteResponse -> store.dispatch(DetailsAction.PrepareScreen( scanNoteResponse.card, scanNoteResponse, store.state.walletState.walletManagers.map { it.wallet }, From 4de7334b8926a940ec091a599f5b36072d74e343 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 18:55:38 +0300 Subject: [PATCH 38/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/features/send/BaseStoreFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt index 75a2a5ad80..bb3046db5e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -51,6 +51,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { override fun onStop() { storeSubscribersList.forEach { store.unsubscribe(it) } + storeSubscribersList.clear() super.onStop() } From ccb8afe104fea8e5a3b647797b840be5cb98d625 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 18:57:11 +0300 Subject: [PATCH 39/55] Updated on 2026-08-14 --- .../res/layout/layout_onboarding_note.xml | 5 +- .../layout/lp_onboarding_create_wallet.xml | 5 +- .../main/res/layout/lp_onboarding_done.xml | 23 +++--- .../layout/lp_onboarding_done_activation.xml | 82 +++++++++++++++++++ .../res/layout/lp_onboarding_topup_wallet.xml | 5 +- .../res/layout/view_onboarding_progress.xml | 10 +++ 6 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/layout/lp_onboarding_done_activation.xml create mode 100644 app/src/main/res/layout/view_onboarding_progress.xml diff --git a/app/src/main/res/layout/layout_onboarding_note.xml b/app/src/main/res/layout/layout_onboarding_note.xml index 9804063722..e2cb262a46 100644 --- a/app/src/main/res/layout/layout_onboarding_note.xml +++ b/app/src/main/res/layout/layout_onboarding_note.xml @@ -6,9 +6,8 @@ android:layout_height="match_parent" android:visibility="visible"> - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/pb_state" /> \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done_activation.xml b/app/src/main/res/layout/lp_onboarding_done_activation.xml new file mode 100644 index 0000000000..a3e9722587 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + \ 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 ab70711896..2783b41196 100644 --- a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -6,9 +6,8 @@ android:layout_height="match_parent" android:visibility="visible"> - + From fe42b821db1999558a8c858399c9352634219011 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 18:57:29 +0300 Subject: [PATCH 40/55] Updated on 2026-08-14 --- .../OnboardingOtherCardsFragment.kt | 93 +++++++++++++++---- .../redux/OnboardingOtherCardsAction.kt | 22 +++++ .../redux/OnboardingOtherCardsMiddleware.kt | 81 ++++++++++++++++ .../redux/OnboardingOtherCardsReducer.kt | 45 +++++++++ .../redux/OnboardingOtherCardsState.kt | 25 +++++ .../service/OnboardingOtherCardsService.kt | 39 ++++++++ 6 files changed, 287 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt 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 b0de1e9949..99ce6f4280 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 @@ -2,40 +2,97 @@ package com.tangem.tap.features.onboarding.products.otherCards import android.os.Bundle import android.view.View -import androidx.transition.TransitionInflater -import com.tangem.tap.features.send.BaseStoreFragment +import android.view.animation.OvershootInterpolator +import androidx.annotation.LayoutRes +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.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction +import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState +import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep +import com.tangem.tap.store import com.tangem.wallet.R -import org.rekotlin.StoreSubscriber +import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* /** [REDACTED_AUTHOR] */ -class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { +class OnboardingOtherCardsFragment : BaseOnboardingFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.fade) - exitTransition = inflater.inflateTransition(R.transition.fade) - } + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen) } override fun subscribeToStore() { -// store.subscribe(this) { state -> -// state.skipRepeats { oldState, newState -> -// oldState.onboardingOtherState == newState.onboardingOtherState -// }.select { it.onboardingOtherState } -// } + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.onboardingOtherCardsState == newState.onboardingOtherCardsState + }.select { it.onboardingOtherCardsState } + } + storeSubscribersList.add(this) } override fun newState(state: OnboardingOtherCardsState) { if (activity == null) return + if (state.currentStep == OnboardingOtherCardsStep.None) return + state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) } + pb_state.max = state.steps.size - 1 + pb_state.progress = state.progress + + when (state.currentStep) { + OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState(state) + OnboardingOtherCardsStep.Done -> setupDoneState(state) + } + showConfetti(state.showConfetti) } -} -class OnboardingOtherCardsState {} \ No newline at end of file + private fun setupCreateWalletState(state: OnboardingOtherCardsState) { + btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) + btn_main_action.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) } + btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean) + btn_alternative_action.setOnClickListener { } + + tv_header.setText(R.string.onboarding_create_wallet_header) + tv_body.setText(R.string.onboarding_create_wallet_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle)) + updateConstraints(R.layout.lp_onboarding_create_wallet) + } + + private fun setupDoneState(state: OnboardingOtherCardsState) { + btn_main_action.setText(R.string.onboarding_done_button_continue) + btn_main_action.setOnClickListener { + showConfetti(false) + store.dispatch(OnboardingOtherCardsAction.Done) + } + + btn_alternative_action.isVisible = false + btn_alternative_action.setText("") + btn_alternative_action.setOnClickListener { } + + tv_header.setText(R.string.onboarding_done_header) + tv_body.setText(R.string.onboarding_done_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(R.layout.lp_onboarding_done) + } + + private fun updateConstraints(@LayoutRes layoutId: Int) { + val constraintSet = ConstraintSet() + constraintSet.clone(requireContext(), layoutId) + constraintSet.applyTo(onboarding_main_container) + val transition = InternalNoteLayoutTransition() + transition.interpolator = OvershootInterpolator() + TransitionManager.beginDelayedTransition(onboarding_main_container, transition) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..effdecff73 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingOtherCardsAction : Action { + // from user, ui + object CreateWallet : OnboardingOtherCardsAction() + + // from redux + object DetermineStepOfScreen : OnboardingOtherCardsAction() + object Done : OnboardingOtherCardsAction() + + data class SetStepOfScreen(val step: OnboardingOtherCardsStep) : OnboardingOtherCardsAction() + + sealed class Confetti { + object Show : OnboardingOtherCardsAction() + object Hide : OnboardingOtherCardsAction() + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..d781c5b1cb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -0,0 +1,81 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import com.tangem.common.CompletionResult +import com.tangem.tap.common.extensions.withMainContext +import com.tangem.tap.common.postUi +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.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.rekotlin.Action +import org.rekotlin.Middleware + +class OnboardingOtherCardsMiddleware { + companion object { + val handler = onboardingOtherCardsMiddleware + } +} + +private val onboardingOtherCardsMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + handleOtherCardsAction(action) + next(action) + } + } +} + +private fun handleOtherCardsAction(action: Action) { + if (action !is OnboardingOtherCardsAction) return + val service = store.state.onboardingOtherCardsState.onboardingService ?: return + val noteState = store.state.onboardingOtherCardsState + + when (action) { + is OnboardingOtherCardsAction.DetermineStepOfScreen -> { + val step = when { + service.walletManager == null -> OnboardingOtherCardsStep.CreateWallet + else -> OnboardingOtherCardsStep.Done + } + store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) + } + is OnboardingOtherCardsAction.SetStepOfScreen -> { + if (action.step == OnboardingOtherCardsStep.Done) { + service.activationFinished() + postUi(500) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } + } + } + is OnboardingOtherCardsAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createProductWallet(service.scanResponse) + withMainContext { + when (result) { + is CompletionResult.Success -> { + val updatedResponse = service.scanResponse.copy(card = result.data) + service.scanResponse = updatedResponse + service.activationStarted() + + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) + } + is CompletionResult.Failure -> { +// do nothing + } + } + } + } + } + OnboardingOtherCardsAction.Done -> { + store.dispatch(GlobalAction.Onboarding.Deactivate) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..316f97f636 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt @@ -0,0 +1,45 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService +import org.rekotlin.Action + +class OnboardingOtherCardsReducer { + companion object { + fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState { + var state = appState.onboardingOtherCardsState + + when (action) { + is GlobalAction.Onboarding.Activate -> { + if (action.onboardingService is OnboardingOtherCardsService) { + val service = action.onboardingService + state = state.copy( + onboardingService = service, + artworkBitmap = service.getArtwork().value?.artwork, + showConfetti = false + ) + } + } + GlobalAction.Onboarding.Deactivate -> { + state = OnboardingOtherCardsState() + } + is OnboardingOtherCardsAction.SetStepOfScreen -> { + if (action.step != state.currentStep && state.steps.contains(action.step)) { + state = state.copy(currentStep = action.step) + } + } + is OnboardingOtherCardsAction.Confetti.Show -> { + state = state.copy(showConfetti = true) + } + is OnboardingOtherCardsAction.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/otherCards/redux/OnboardingOtherCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt new file mode 100644 index 0000000000..bcf232bd85 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import android.graphics.Bitmap +import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingOtherCardsState( + val onboardingService: OnboardingOtherCardsService? = null, + // UI + val showConfetti: Boolean = false, + val artworkBitmap: Bitmap? = null, + val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None, + val steps: List = OnboardingOtherCardsStep.values().toList(), +) : StateType { + + val progress: Int + get() = steps.indexOf(currentStep) +} + +enum class OnboardingOtherCardsStep { + None, CreateWallet, Done +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt new file mode 100644 index 0000000000..5e81842312 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.features.onboarding.service + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.tap.common.extensions.withMainContext +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.persistence.UsedCardsPrefStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingOtherCardsService( + override val fromScreen: AppScreen, + override var scanResponse: ScanResponse, + cardInfoStorage: UsedCardsPrefStorage, + walletManagerFactory: WalletManagerFactory, +) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { + + override fun navigateToScreen(): AppScreen { + return AppScreen.OnboardingOther + } + + // Return any value and any state. The balance of the card is not important in this service + override suspend fun updateBalance(): OnboardingWalletBalance { + val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") + withMainContext { loadedBalance = OnboardingWalletBalance.error(customError) } + return loadedBalance + } + + override fun getToUpUrl(): String? { + throw UnsupportedOperationException() + } + + override fun getAddressData(): AddressData? { + throw UnsupportedOperationException() + } +} \ No newline at end of file From 013f80ef6390c5273e33a4d9c58f127d64d9ad77 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 22:57:44 +0300 Subject: [PATCH 41/55] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TapWalletManager.kt | 7 +++-- .../domain/tasks/product/ScanProductTask.kt | 15 +++------ .../tangem/tap/domain/twins/TwinsHelper.kt | 31 ++++++++++++------- .../features/details/redux/DetailsReducer.kt | 4 +-- 4 files changed, 29 insertions(+), 28 deletions(-) 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 8a592c14ad..76a9bc46f1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -16,7 +16,8 @@ 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.TwinsHelper +import com.tangem.tap.domain.twins.getTwinCardNumber +import com.tangem.tap.domain.twins.getTwinsCardId import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency @@ -90,8 +91,8 @@ class TapWalletManager { store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) if (data.card.isTangemTwin()) { - val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId) - val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId) + val secondCardId = data.card.getTwinsCardId() + val cardNumber = data.card.getTwinCardNumber() if (secondCardId != null && cardNumber != null) { store.dispatch(WalletAction.TwinsAction.SetTwinCard( secondCardId, cardNumber, isCreatingTwinCardsAllowed = true diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 3493d1c322..cdb63d456b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -123,19 +123,12 @@ private class ScanTwinProcessor : ProductCommandProcessor { callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) return@run } - val verified = TwinCardsManager.verifyTwinPublicKey( - readDataResult.data.issuerData, publicKey - ) + val verified = TwinCardsManager.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) - callback(CompletionResult.Success( - ScanResponse( - card, - ProductType.Twin, - session.environment.walletData, - twinPublicKey.toHexString()) - )) - return@run + val walletData = session.environment.walletData + val response = ScanResponse(card, ProductType.Twin, walletData, twinPublicKey.toHexString()) + callback(CompletionResult.Success(response)) } else { callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) } 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 f8e5de93fe..8c66c05bfa 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 @@ -2,7 +2,6 @@ package com.tangem.tap.domain.twins import com.tangem.common.card.Card import com.tangem.tap.common.extensions.isEven -import com.tangem.tap.domain.twins.TwinsHelper.Companion.getTwinCardNumber class TwinsHelper { companion object { @@ -53,16 +52,16 @@ class TwinsHelper { private fun String.calculateLuhn(): Int { val checksum = 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() - .rem(10) + .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() + .rem(10) return (10 - checksum) % 10 } @@ -76,7 +75,15 @@ enum class TwinCardNumber(val number: Int) { } fun Card.isTangemTwin(): Boolean { - return getTwinCardNumber(cardId) != null + return TwinsHelper.getTwinCardNumber(cardId) != null +} + +fun Card.getTwinCardNumber(): TwinCardNumber? { + return TwinsHelper.getTwinCardNumber(this.cardId) +} + +fun Card.getTwinsCardId(): String? { + return TwinsHelper.getTwinsCardId(this.cardId) } fun Card.getTwinCardIdForUser(): String { 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 04bcd51f32..4e45d765d7 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,7 +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.TwinsHelper +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 @@ -49,7 +49,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta val twinsState = if (action.card.isTangemTwin()) { CreateTwinWalletState( scanResponse = action.scanResponse, - twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), + twinCardNumber = action.card.getTwinCardNumber(), createTwinWallet = null, showAlert = false, allowRecreatingWallet = action.isCreatingTwinWalletAllowed From 72e282956c5db89b1c64cc01372c838e698f16e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Oct 2021 22:58:02 +0300 Subject: [PATCH 42/55] Updated on 2026-08-14 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 69493ced41..2d9e53e7e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,8 +77,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'com.tangem:blockchain:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-80' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-80' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-88' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-88' // WebView implementation "androidx.browser:browser:1.3.0" From b327109ca8613a624412d35191d60db6649b40b1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Oct 2021 22:11:44 +0300 Subject: [PATCH 43/55] Updated on 2026-08-14 --- .../tap/common/leapfrogWidget/Animations.kt | 86 ++++----- .../tap/common/leapfrogWidget/LeapView.kt | 173 +++++++----------- .../common/leapfrogWidget/LeapfrogWidget.kt | 117 +++++++----- 3 files changed, 175 insertions(+), 201 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt index 77c9f1fcbb..a95488d44f 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt @@ -32,14 +32,14 @@ fun createProgressListener(duration: Long, listener: ProgressListener?): Animato return valueAnimator } -fun View.unfoldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) +fun View.unfoldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet { + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) translate.duration = animDuration return AnimatorSet().apply { playTogether(translate) } } -fun View.foldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) +fun View.foldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet { + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) translate.duration = animDuration return AnimatorSet().apply { @@ -47,17 +47,17 @@ fun View.foldAnimation(animDuration: Long, properties: AnimationProperties): Ani } } -fun View.leapAnimation(animDuration: Long, properties: AnimationProperties, overLift: Float): AnimatorSet { +fun View.leapAnimation(animDuration: Long, properties: LeapViewProperties, overLift: Float): AnimatorSet { val halfDuration = animDuration / 2 - val upTo = (height.toFloat() + properties.yTranslationStart + overLift) * -1 - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, upTo) + val upTo = (height.toFloat() + overLift) * -1 + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo) translateUp.duration = halfDuration translateUp.interpolator = LinearInterpolator() val scaleDuration = halfDuration - halfDuration / 5 - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) val scale = AnimatorSet().apply { startDelay = scaleDuration duration = scaleDuration @@ -65,37 +65,28 @@ fun View.leapAnimation(animDuration: Long, properties: AnimationProperties, over playTogether(scaleX, scaleY) } - val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) - elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } - elevation.startDelay = halfDuration - elevation.duration = 100 - - val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo, properties.yTranslationEnd) + val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100) + val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) translateDown.startDelay = halfDuration translateDown.duration = halfDuration translateDown.interpolator = LinearInterpolator() return AnimatorSet().apply { - playTogether( - translateUp, - scale, - elevation, - translateDown - ) + playTogether(translateUp, scale, elevation, translateDown) } } -fun View.leapBackAnimation(animDuration: Long, properties: AnimationProperties, calculator: PropertyCalculator): AnimatorSet { +fun View.leapBackAnimation(animDuration: Long, properties: LeapViewProperties, calculator: PropertyCalculator): AnimatorSet { val halfDuration = animDuration / 2 val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1 - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, upTo) + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo) translateUp.duration = halfDuration translateUp.interpolator = LinearInterpolator() val scaleDuration = halfDuration - halfDuration / 5 - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) val scale = AnimatorSet().apply { startDelay = scaleDuration duration = scaleDuration @@ -103,34 +94,24 @@ fun View.leapBackAnimation(animDuration: Long, properties: AnimationProperties, playTogether(scaleX, scaleY) } - val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) - elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } - elevation.startDelay = halfDuration - elevation.duration = 100 - - val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo, properties.yTranslationEnd) + val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100) + val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) translateDown.startDelay = halfDuration translateDown.duration = halfDuration return AnimatorSet().apply { - playTogether( - translateUp, - scale, - elevation, - translateDown, - ) + playTogether(translateUp, scale, elevation, translateDown) } } -fun View.pullUpAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { +fun View.pullUpAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 2 - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) - val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) - elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) + val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd) return AnimatorSet().apply { startDelay = delayDuration @@ -140,15 +121,14 @@ fun View.pullUpAnimation(leapDuration: Long, properties: AnimationProperties): A } } -fun View.pullDownAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { +fun View.pullDownAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 4 - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scaleStart, properties.scaleEnd) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scaleStart, properties.scaleEnd) - val elevation = ValueAnimator.ofFloat(properties.elevationStart, properties.elevationEnd) - elevation.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: properties.elevationStart } + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) + val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) + val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) + val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd) return AnimatorSet().apply { startDelay = delayDuration @@ -156,4 +136,12 @@ fun View.pullDownAnimation(leapDuration: Long, properties: AnimationProperties): interpolator = DecelerateInterpolator() playTogether(translate, scaleX, scaleY, elevation) } +} + +fun View.elevationAnimator(start: Float, end: Float, startDelay: Long? = null, duration: Long? = null): Animator { + val elevationAnimator = ValueAnimator.ofFloat(start, end) + elevationAnimator.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: start } + startDelay?.let { elevationAnimator.startDelay = it } + duration?.let { elevationAnimator.duration = it } + return elevationAnimator } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt index 0c4d37a066..be2d63b664 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt @@ -12,35 +12,33 @@ class LeapView( private val maximalPosition: Int, private val calculator: PropertyCalculator, ) { - var currentProperties: AnimationProperties + var state: LeapViewState private set - var currentPosition: Int = position - private set - var previousPosition: Int = position - private set + val initialState: LeapViewState init { val initialProperties = createInitialProperty(position) - currentProperties = AnimationProperties.from(initialProperties, initialProperties) - initView(initialProperties) + state = LeapViewState(index, position, position, initialProperties) + initialState = state.copy() } - private fun initView(properties: Properties) { - view.elevation = properties.elevation - view.translationY = properties.yTranslation - view.scaleX = properties.scale - view.scaleY = properties.scale -// initialProperties.hasForeground + fun initView() { + applyState(initialState) } fun leap(): LeapFrogAnimation { - previousPosition = currentPosition - currentPosition = when (previousPosition) { + val previousPosition = state.currentPosition + val currentPosition = when (previousPosition) { 0 -> maximalPosition - else -> currentPosition - 1 + else -> state.currentPosition - 1 } - updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + + state = state.copy( + currentPosition = currentPosition, + previousPosition = previousPosition, + properties = createLeapAnimationProperty(previousPosition, currentPosition) + ) return when { previousPosition == 0 && currentPosition == maximalPosition -> LeapFrogAnimation.LEAP @@ -49,12 +47,16 @@ class LeapView( } fun leapBack(): LeapFrogAnimation { - previousPosition = currentPosition - currentPosition = when (previousPosition) { + val previousPosition = state.currentPosition + val currentPosition = when (previousPosition) { maximalPosition -> 0 - else -> currentPosition + 1 + else -> state.currentPosition + 1 } - updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + state = state.copy( + currentPosition = currentPosition, + previousPosition = previousPosition, + properties = createLeapAnimationProperty(previousPosition, currentPosition) + ) return when { previousPosition == maximalPosition && currentPosition == 0 -> LeapFrogAnimation.LEAP @@ -63,66 +65,60 @@ class LeapView( } fun fold() { - updateProperties(currentProperties.toFold()) + state = state.copy(properties = state.properties.toFold() + ) } fun unfold() { - updateProperties(currentProperties.toUnfold(calculator)) - } - - fun getState(): LeapViewState { - return LeapViewState(index, currentPosition, previousPosition, currentProperties) + state = state.copy(properties = state.properties.toUnfold(calculator)) } fun applyState(state: LeapViewState) { - previousPosition = state.previousPosition - currentPosition = state.currentPosition - updateProperties(state.properties) - initView(state.properties.endProperties()) + this.state = state + setProperties(this.state.properties) } - private fun updateProperties(properties: AnimationProperties) { - currentProperties = properties + private fun setProperties(properties: LeapViewProperties) { + view.elevation = properties.elevationEnd + view.translationY = properties.yTranslation + view.scaleX = properties.scale + view.scaleY = properties.scale +// initialProperties.hasForeground } - private fun createInitialProperty(endPosition: Int): Properties { - return Properties( - endPosition, - calculator.scale(endPosition), - calculator.elevation(endPosition, maximalPosition + 1), - calculator.yTranslation(0), - calculator.hasForeground(endPosition), + private fun createInitialProperty(endPosition: Int): LeapViewProperties { + return LeapViewProperties( + endPosition, + endPosition, + calculator.scale(endPosition), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(0), + calculator.hasForeground(endPosition), ) } - private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): AnimationProperties { - return AnimationProperties( - startPosition, - endPosition, - calculator.scale(startPosition), - calculator.scale(endPosition), - calculator.elevation(startPosition, maximalPosition + 1), - calculator.elevation(endPosition, maximalPosition + 1), - calculator.yTranslation(startPosition), - calculator.yTranslation(endPosition), - calculator.hasForeground(startPosition), - calculator.hasForeground(endPosition), + private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): LeapViewProperties { + return LeapViewProperties( + startPosition, + endPosition, + calculator.scale(endPosition), + calculator.elevation(startPosition, maximalPosition + 1), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(endPosition), + calculator.hasForeground(endPosition), ) } - private fun AnimationProperties.toUnfold(calculator: PropertyCalculator): AnimationProperties { - val start = this.yTranslationEnd - val end = calculator.yTranslation(this.positionEnd) - return this.copy(yTranslationStart = start, yTranslationEnd = end) + private fun LeapViewProperties.toUnfold(calculator: PropertyCalculator): LeapViewProperties { + return this.copy(yTranslation = calculator.yTranslation(this.positionEnd)) } - private fun AnimationProperties.toFold(): AnimationProperties { - val start = this.yTranslationEnd - val end = 0f - return this.copy(yTranslationStart = start, yTranslationEnd = end) + private fun LeapViewProperties.toFold(): LeapViewProperties { + return this.copy(yTranslation = 0f) } - override fun toString(): String = "index: $index, position: $currentPosition, code: ${view.hashCode()}" + override fun toString(): String = "index: $index, position: ${state.currentPosition}, code: ${view.hashCode()}" } @@ -130,62 +126,19 @@ data class LeapViewState( val index: Int, val currentPosition: Int, val previousPosition: Int, - val properties: AnimationProperties + val properties: LeapViewProperties ) enum class LeapFrogAnimation { LEAP, PULL } -data class Properties( - val position: Int, - val scale: Float, - val elevation: Float, - val yTranslation: Float, - val hasForeground: Boolean, -) - -data class AnimationProperties( +data class LeapViewProperties( val positionStart: Int, val positionEnd: Int, - val scaleStart: Float, - val scaleEnd: Float, + val scale: Float, val elevationStart: Float, val elevationEnd: Float, - val yTranslationStart: Float, - val yTranslationEnd: Float, - val hasForegroundStart: Boolean, - val hasForegroundEnd: Boolean, -) { - - fun endProperties(): Properties { - return Properties( - positionEnd, - scaleEnd, - elevationEnd, - yTranslationEnd, - hasForegroundEnd, - ) - } - - override fun toString(): String { - return """ - scaleStart: $scaleStart, scaleEnd: $scaleEnd, - elevationStart: $elevationStart, elevationEnd: $elevationEnd, - yTranslationStart: $yTranslationStart, yTranslationEnd: $yTranslationEnd, - hasForegroundStart : $hasForegroundStart, hasForegroundEnd: $hasForegroundEnd, - """.trimIndent() - } - - companion object { - fun from(start: Properties, end: Properties): AnimationProperties { - return AnimationProperties( - start.position, end.position, - start.scale, end.scale, - start.elevation, end.elevation, - start.yTranslation, end.yTranslation, - start.hasForeground, end.hasForeground - ) - } - } -} \ No newline at end of file + val yTranslation: Float, + val hasForeground: Boolean, +) \ No newline at end of file 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 37b04d4f2a..11c0b11e63 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 @@ -2,9 +2,9 @@ package com.tangem.tap.common.leapfrogWidget import android.animation.Animator import android.animation.AnimatorSet -import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import android.widget.FrameLayout +import androidx.core.animation.doOnEnd import androidx.core.view.children import com.tangem.tangem_sdk_new.extensions.dpToPx @@ -25,60 +25,87 @@ class LeapfrogWidget( private var leapProgress: Int = 100 private var leapBackProgress: Int = 100 - private var isFolded: Boolean = true + var isFolded: Boolean = true + private set init { calculator.setTranslationConverter { parentContainer.dpToPx(it) } - init() - } - - private fun init() { - if (!viewIsFullFledged()) return - - val maxPosition = parentContainer.childCount - 1 - parentContainer.children.forEachIndexed { index, view -> - val initialPosition = maxPosition - index - leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator)) + if (viewIsFullFledged()) { + val maxPosition = parentContainer.childCount - 1 + parentContainer.children.forEachIndexed { index, view -> + val initialPosition = maxPosition - index + leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator)) + } } } - fun fold(listener: ProgressListener? = null) { - if (!canFoldUnfold() || isFolded) return - - createFoldUnfoldAnimation(true, foldAnimationDuration, listener) + /** + * Apply initial properties to the views + */ + fun initViews() { + isFolded = true + leapViews.forEach { it.initView() } } - fun unfold(listener: ProgressListener? = null) { - if (!canFoldUnfold() || !isFolded) return - - createFoldUnfoldAnimation(false, foldAnimationDuration, listener) + fun fold(onEnd: () -> Unit = {}) { + val animator = foldAnimator() + if (animator == null) { + onEnd() + } else { + animator.doOnEnd { onEnd() } + animator.start() + } } - private fun createFoldUnfoldAnimation(isFoldAnimation: Boolean, duration: Long, listener: ProgressListener?) { + fun unfold(onEnd: () -> Unit = {}) { + val animator = unfoldAnimator() + if (animator == null) { + onEnd() + } else { + animator.doOnEnd { onEnd() } + animator.start() + } + } + + fun foldAnimator(): Animator? { + if (!canFoldUnfold() || isFolded) return null + + return createFoldUnfoldAnimator(true, foldAnimationDuration) + } + + fun unfoldAnimator(): Animator? { + if (!canFoldUnfold() || !isFolded) return null + + return createFoldUnfoldAnimator(false, foldAnimationDuration) + } + + private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, duration: Long): Animator { val animatorsList = mutableListOf() leapViews.forEach { if (isFoldAnimation) { it.fold() - animatorsList.add(it.view.foldAnimation(duration, it.currentProperties)) + animatorsList.add(it.view.foldAnimation(duration, it.state.properties)) } else { it.unfold() - animatorsList.add(it.view.unfoldAnimation(duration, it.currentProperties)) + animatorsList.add(it.view.unfoldAnimation(duration, it.state.properties)) } } animatorsList.add(createProgressListener(duration) { foldUnfoldProgress = it if (it == 100) isFolded = isFoldAnimation }) - animatorsList.add(createProgressListener(duration, listener)) val animator = AnimatorSet() animator.interpolator = AccelerateDecelerateInterpolator() animator.playTogether(animatorsList.toList()) - animator.start() + return animator } - fun leap(listener: ProgressListener? = null) { - if (!canLeap()) return + fun leap(onEnd: () -> Unit = {}) { + if (!canLeap()) { + onEnd() + return + } val duration = leapAnimationDuration val animatorsList = mutableListOf() @@ -86,48 +113,54 @@ class LeapfrogWidget( when (it.leap()) { LeapFrogAnimation.LEAP -> { val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapAnimation(duration, it.currentProperties, overLift)) + animatorsList.add(it.view.leapAnimation(duration, it.state.properties, overLift)) } LeapFrogAnimation.PULL -> { - animatorsList.add(it.view.pullUpAnimation(duration, it.currentProperties)) + animatorsList.add(it.view.pullUpAnimation(duration, it.state.properties)) } } } animatorsList.add(createProgressListener(duration) { leapProgress = it }) - animatorsList.add(createProgressListener(duration, listener)) val animator = AnimatorSet() animator.playTogether(animatorsList.toList()) + animator.doOnEnd { onEnd() } animator.start() } - fun leapBack(listener: ProgressListener? = null) { - if (!canLeapBack()) return + fun leapBack(onEnd: () -> Unit = {}) { + if (!canLeapBack()) { + onEnd() + return + } val duration = leapBackAnimationDuration val animatorsList = mutableListOf() leapViews.forEach { when (it.leapBack()) { LeapFrogAnimation.LEAP -> { - animatorsList.add(it.view.leapBackAnimation(duration, it.currentProperties, calculator)) + animatorsList.add(it.view.leapBackAnimation(duration, it.state.properties, calculator)) } LeapFrogAnimation.PULL -> { - animatorsList.add(it.view.pullDownAnimation(duration, it.currentProperties)) + animatorsList.add(it.view.pullDownAnimation(duration, it.state.properties)) } } } animatorsList.add(createProgressListener(duration) { leapBackProgress = it }) - animatorsList.add(createProgressListener(duration, listener)) + val animator = AnimatorSet() animator.playTogether(animatorsList.toList()) + animator.doOnEnd { onEnd() } animator.start() } - fun getViewByPosition(position: Int): View? { - return leapViews.firstOrNull { it.currentPosition == position }?.view - } + fun getViewsCount(): Int = parentContainer.childCount - fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(isFolded, leapViews.map { it.getState() }) + fun getViewPositionByIndex(index: Int): Int = leapViews.first { it.index == index }.state.currentPosition + + fun getViewByPosition(position: Int): LeapView = leapViews.first { it.state.currentPosition == position } + + fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(isFolded, leapViews.map { it.state }) fun applyState(state: LeapfrogWidgetState) { isFolded = state.isFolded @@ -179,11 +212,11 @@ data class LeapfrogWidgetState( class PropertyCalculator( val elevationFactor: Float = 1f, - val decreaseScaleFactor: Float = 0.1f, - val yTranslationFactor: Float = 20f, + val decreaseScaleFactor: Float = 0.2f, + val yTranslationFactor: Float = -40f, val decreaseOverLiftingFactor: Float = 0.85f, ) { - // 1st view it is view on the top of stack, but it is last in parent.children + // 0 view it is view on the top of stack, but it is last in parent.children private var translationValueConverter: ((Float) -> Float)? = null fun setTranslationConverter(converter: (Float) -> Float) { From ea82db240b8d6d0d63b53b1a7e5155057c34dff7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Oct 2021 22:12:02 +0300 Subject: [PATCH 44/55] Updated on 2026-08-14 --- .../tap/domain/twins/TwinsCardWidget.kt | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt new file mode 100644 index 0000000000..63f891eb4d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt @@ -0,0 +1,152 @@ +package com.tangem.tap.domain.twins + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder +import android.view.View +import androidx.core.animation.doOnEnd +import com.tangem.tap.common.leapfrogWidget.LeapView +import com.tangem.tap.common.leapfrogWidget.LeapViewState +import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget + +/** +[REDACTED_AUTHOR] + */ +class TwinsCardWidget( + val leapfrogWidget: LeapfrogWidget, + val getTopOfAnchorViewForActivateState: () -> Float +) { + + init { + if (leapfrogWidget.getViewsCount() != 2) throw UnsupportedOperationException() + } + + fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)) + ) + leapfrogWidget.fold { animator.start() } + } + + fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)) + ) + leapfrogWidget.fold { + animator.doOnEnd { + leapfrogWidget.initViews() + leapfrogWidget.unfold() + } + animator.start() + } + } + + fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) + ) + animator.start() + } + + private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { + return AnimatorSet().apply { + duration = if (animate) 400 else 0 + doOnEnd { onEnd() } + } + } + + private fun createAnimator(cardNumber: TwinCardNumber, properties: TwinsCardProperties): ObjectAnimator { + val view = getLeapViewByCardNumber(cardNumber).view + val animator = ObjectAnimator.ofPropertyValuesHolder(view, *properties.createValuesHolders().toTypedArray()) + view.elevation = properties.elevation + return animator + } + + private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties { + return when (cardNumber) { + TwinCardNumber.First -> { + TwinsCardProperties( + xTranslation = -180f, + yTranslation = -350f, + rotation = -3f, + elevation = 1f, + scale = 1.1f, + ) + } + TwinCardNumber.Second -> { + TwinsCardProperties( + xTranslation = 180f, + yTranslation = 10f, + rotation = -3f, + elevation = 0f, + scale = 1.1f, + ) + } + } + } + + private fun createLeapfrogProperties(cardNumber: TwinCardNumber): TwinsCardProperties { + val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) + return twinProperties.copy( + xTranslation = 0f, + yTranslation = 0f, + rotation = 0f, + scale = 1f, + ) + } + + private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties { + val topOfAnchorView = getTopOfAnchorViewForActivateState() + val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) + return twinProperties.copy( + xTranslation = twinProperties.xTranslation, + yTranslation = twinProperties.yTranslation - topOfAnchorView, + rotation = 0f, + scale = twinProperties.scale - 0.4f, + ) + } + + private fun getLeapViewByCardNumber(cardNumber: TwinCardNumber): LeapView { + return when (cardNumber) { + TwinCardNumber.First -> leapfrogWidget.getViewByPosition(0) + TwinCardNumber.Second -> leapfrogWidget.getViewByPosition(1) + } + } +} + +private data class TwinsCardProperties( + val xTranslation: Float = 0f, + val yTranslation: Float = 0f, + val rotation: Float = 0f, + val elevation: Float = 0f, + val scale: Float = 1.1f, +) { + + fun createValuesHolders(): List { + return listOf( + PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation), + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation), + PropertyValuesHolder.ofFloat(View.ROTATION, rotation), + PropertyValuesHolder.ofFloat(View.SCALE_X, scale), + PropertyValuesHolder.ofFloat(View.SCALE_Y, scale), + ) + } + + companion object { + fun from(leapViewState: LeapViewState): TwinsCardProperties { + val leapViewProperties = leapViewState.properties + + return TwinsCardProperties( + yTranslation = leapViewProperties.yTranslation, + elevation = leapViewProperties.elevationEnd, + scale = leapViewProperties.scale, + ) + } + } +} \ No newline at end of file From b40dc0a04a7d367f8f1fa20aec55d21eaf92a973 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Oct 2021 22:18:38 +0300 Subject: [PATCH 45/55] Updated on 2026-08-14 --- .../com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 11c0b11e63..884cef3f94 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 @@ -212,8 +212,8 @@ data class LeapfrogWidgetState( class PropertyCalculator( val elevationFactor: Float = 1f, - val decreaseScaleFactor: Float = 0.2f, - val yTranslationFactor: Float = -40f, + val decreaseScaleFactor: Float = 0.1f, + val yTranslationFactor: Float = 20f, val decreaseOverLiftingFactor: Float = 0.85f, ) { // 0 view it is view on the top of stack, but it is last in parent.children From 11823643255408948be255196fe7774b1fff167e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Oct 2021 22:19:23 +0300 Subject: [PATCH 46/55] Updated on 2026-08-14 --- .../leapfrogWidget/TestLeapfrogFragment.kt | 68 +++++++++ .../res/layout/test_leapfrog_fragment.xml | 133 ++++++++++++++++++ ...og_widget.xml => test_leapfrog_widget.xml} | 11 +- 3 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt create mode 100644 app/src/main/res/layout/test_leapfrog_fragment.xml rename app/src/main/res/layout/{leapfrog_widget.xml => test_leapfrog_widget.xml} (69%) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt new file mode 100644 index 0000000000..1a41ff35be --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt @@ -0,0 +1,68 @@ +package com.tangem.tap.common.leapfrogWidget + +import android.os.Bundle +import android.view.View +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.domain.twins.TwinsCardWidget +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.test_leapfrog_fragment.* + +class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) { + + private lateinit var twinsCardWidget: TwinsCardWidget + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val leapfrogContainer: FrameLayout = view.findViewById(R.id.leapfrog_views_container) + val leapfrog = LeapfrogWidget(leapfrogContainer) + twinsCardWidget = TwinsCardWidget(leapfrog) { 200f } + + btn_twin_welcome.setOnClickListener { + twinsCardWidget.toWelcome() + } + btn_twin_to_leapfrog.setOnClickListener { + twinsCardWidget.toLeapfrog() + } + btn_twin_activate.setOnClickListener { + twinsCardWidget.toActivate() + } + + + btn_lp_init.setOnClickListener { + leapfrog.initViews() + } + btn_lp_unfold.setOnClickListener { + leapfrog.unfold() + } + btn_lp_fold.setOnClickListener { + leapfrog.fold() + } + btn_lp_leap.setOnClickListener { + leapfrog.leap() + } + btn_lp_leap_back.setOnClickListener { + leapfrog.leapBack() + } + } + + override fun onStart() { + super.onStart() + leapfrogWidgetState?.let { twinsCardWidget.leapfrogWidget.applyState(it) } + } + + override fun onStop() { + super.onStop() + leapfrogWidgetState = twinsCardWidget.leapfrogWidget.getState() + } +} + +private var leapfrogWidgetState: LeapfrogWidgetState? = null \ No newline at end of file diff --git a/app/src/main/res/layout/test_leapfrog_fragment.xml b/app/src/main/res/layout/test_leapfrog_fragment.xml new file mode 100644 index 0000000000..52b720ad27 --- /dev/null +++ b/app/src/main/res/layout/test_leapfrog_fragment.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/leapfrog_widget.xml b/app/src/main/res/layout/test_leapfrog_widget.xml similarity index 69% rename from app/src/main/res/layout/leapfrog_widget.xml rename to app/src/main/res/layout/test_leapfrog_widget.xml index 13a8e61d88..3fbf992180 100644 --- a/app/src/main/res/layout/leapfrog_widget.xml +++ b/app/src/main/res/layout/test_leapfrog_widget.xml @@ -11,16 +11,13 @@ + android:tint="@color/colorPrimary" + android:src="@drawable/card_placeholder" /> - - + android:tint="@color/accent" + android:src="@drawable/card_placeholder" /> \ No newline at end of file From 7e821a934c42d4e46ce91ac83888b39583a62db1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 17 Oct 2021 22:40:55 +0300 Subject: [PATCH 47/55] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 2 +- .../tap/common/extensions/Transition.kt | 24 ++ .../tap/common/extensions/WalletManager.kt | 27 ++ .../tap/common/redux/global/GlobalAction.kt | 8 +- .../common/redux/global/GlobalMidlleware.kt | 2 +- .../tap/common/redux/global/GlobalReducer.kt | 22 +- .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TapWalletManager.kt | 9 +- .../com/tangem/tap/domain/extensions/Card.kt | 33 ++ .../tap/domain/twins/TwinCardsManager.kt | 3 +- .../tangem/tap/domain/twins/TwinsHelper.kt | 36 --- .../tap/features/home/redux/HomeMiddleware.kt | 74 ++--- .../features/onboarding/OnboardingHelper.kt | 27 ++ .../features/onboarding/OnboardingManager.kt | 129 ++++++++ .../products/BaseOnboardingFragment.kt | 2 +- .../products/note/OnboardingNoteFragment.kt | 43 ++- .../note/redux/OnboardingNoteAction.kt | 7 +- .../note/redux/OnboardingNoteMiddleware.kt | 94 ++++-- .../note/redux/OnboardingNoteReducer.kt | 31 +- .../note/redux/OnboardingNoteState.kt | 22 +- .../OnboardingOtherCardsFragment.kt | 4 +- .../redux/OnboardingOtherCardsAction.kt | 3 + .../redux/OnboardingOtherCardsMiddleware.kt | 34 +- .../redux/OnboardingOtherCardsReducer.kt | 16 +- .../redux/OnboardingOtherCardsState.kt | 8 +- .../onboarding/service/OnboardingHelper.kt | 33 -- .../service/OnboardingNoteService.kt | 21 -- .../service/OnboardingOtherCardsService.kt | 39 --- .../service/ProductOnboardingService.kt | 291 ------------------ .../tap/features/wallet/redux/WalletAction.kt | 2 +- .../res/layout/layout_onboarding_note.xml | 2 +- .../layout/lp_onboarding_create_wallet.xml | 2 +- .../layout/lp_onboarding_done_activation.xml | 2 +- .../res/layout/lp_onboarding_topup_wallet.xml | 2 +- 34 files changed, 443 insertions(+), 615 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/Transition.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index dbef280f6f..41b89b88e3 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -75,7 +75,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanResponse != null - val isOnboardingServiceActive = store.state.globalState.onboardingService != null + val isOnboardingServiceActive = store.state.globalState.onboardingManager != null if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt new file mode 100644 index 0000000000..69fbba97b6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.common.extensions + +import androidx.transition.Transition + +/** +[REDACTED_AUTHOR] + */ +inline fun Transition.addListener( + crossinline onStart: (animator: Transition) -> Unit = {}, + crossinline onEnd: (animator: Transition) -> Unit = {}, + crossinline onCancel: (animator: Transition) -> Unit = {}, + crossinline onPause: (animator: Transition) -> Unit = {}, + crossinline onRepeat: (animator: Transition) -> Unit = {} +): Transition.TransitionListener { + val listener = object : Transition.TransitionListener { + override fun onTransitionStart(transition: Transition) = onStart(transition) + override fun onTransitionEnd(transition: Transition) = onEnd(transition) + override fun onTransitionCancel(transition: Transition) = onCancel(transition) + override fun onTransitionPause(transition: Transition) = onPause(transition) + override fun onTransitionResume(transition: Transition) = onRepeat(transition) + } + addListener(listener) + return listener +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 683f1574a3..8f7402ff1f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -7,7 +7,11 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.extensions.isNoAccountError import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.domain.topup.TradeCryptoHelper +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.reducers.createAddressesData import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.store /** [REDACTED_AUTHOR] @@ -29,4 +33,27 @@ suspend fun WalletManager.safeUpdate(): Result = try { Result.Failure(TapError.WalletManagerUpdate.InternalError(message)) } } +} + +fun WalletManager?.getToUpUrl(): String? { + val wallet = this?.wallet ?: return null + val config = store.state.globalState.configManager?.config ?: return null + val defaultAddress = wallet.address + + return TradeCryptoHelper.getUrl( + TradeCryptoHelper.Action.Buy, + wallet.blockchain, + wallet.blockchain.currency, + defaultAddress, + config.moonPayApiKey, + config.moonPayApiSecretKey + ) +} + +fun WalletManager?.getAddressData(): AddressData? { + val wallet = this?.wallet ?: return null + + val addressDataList = wallet.createAddressesData() + return if (addressDataList.isEmpty()) null + else addressDataList[0] } \ 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 50efb26994..b29536f6d3 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 @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.tap.common.redux.* +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -12,7 +13,6 @@ import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager -import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.Action @@ -29,11 +29,11 @@ sealed class GlobalAction : Action { object HideDialog : GlobalAction() sealed class Onboarding { - data class Activate(val onboardingService: ProductOnboardingService) : GlobalAction() - object Deactivate : GlobalAction() + data class Start(val scanResponse: ScanResponse, val fromScreen: AppScreen) : GlobalAction() + object Stop : GlobalAction() } - data class ReadCard( + data class ScanCard( val onSuccess: ((ScanResponse) -> Unit)? = null, val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, 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 2f8cbfffc4..ea80ab463c 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 @@ -87,7 +87,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } } } - is GlobalAction.ReadCard -> { + is GlobalAction.ScanCard -> { scope.launch { val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) 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 30eabac6e4..0d1894f736 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 @@ -1,6 +1,8 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.onboarding.OnboardingManager +import com.tangem.tap.preferencesStorage import org.rekotlin.Action fun globalReducer(action: Action, state: AppState): GlobalState { @@ -10,11 +12,13 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { - is GlobalAction.Onboarding.Activate -> { - globalState.copy(onboardingService = action.onboardingService) + is GlobalAction.Onboarding.Start -> { + val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage + val onboardingState = OnboardingManager(action.scanResponse, action.fromScreen, usedCardsPrefStorage) + globalState.copy(onboardingManager = onboardingState) } - is GlobalAction.Onboarding.Deactivate -> { - globalState.copy(onboardingService = null) + is GlobalAction.Onboarding.Stop -> { + globalState.copy(onboardingManager = null) } is GlobalAction.ScanFailsCounter.Increment -> { globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1) @@ -36,11 +40,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) is GlobalAction.UpdateWalletSignedHashes -> { val wallet = globalState.scanResponse?.card - ?.wallet(action.walletPublicKey) - ?.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures - ) + ?.wallet(action.walletPublicKey) + ?.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + ) val card = globalState.scanResponse?.card wallet?.let { globalState.scanResponse.card.updateWallet(wallet) } 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 94eb578fc4..e27641b854 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 @@ -8,13 +8,14 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.feedback.FeedbackManager -import com.tangem.tap.features.onboarding.service.ProductOnboardingService +import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.StateType data class GlobalState( val scanResponse: ScanResponse? = null, + val onboardingManager: OnboardingManager? = null, val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), val payIdManager: PayIdManager = PayIdManager(), @@ -26,7 +27,6 @@ data class GlobalState( val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, val moonPayUserStatus: MoonPayUserStatus? = null, - val onboardingService: ProductOnboardingService? = null, ) : StateType typealias CryptoCurrencyName = String 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 76a9bc46f1..32403ae363 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -17,7 +17,6 @@ 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.getTwinsCardId import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency @@ -91,12 +90,8 @@ class TapWalletManager { store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) if (data.card.isTangemTwin()) { - val secondCardId = data.card.getTwinsCardId() - val cardNumber = data.card.getTwinCardNumber() - if (secondCardId != null && cardNumber != null) { - store.dispatch(WalletAction.TwinsAction.SetTwinCard( - secondCardId, cardNumber, isCreatingTwinCardsAllowed = true - )) + data.card.getTwinCardNumber()?.let { + store.dispatch(WalletAction.TwinsAction.SetTwinCard(null, it, true)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index dc3fc1a618..124ae27387 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -3,7 +3,11 @@ package com.tangem.tap.domain.extensions import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.domain.twins.getTwinCardNumber import com.tangem.tap.features.wallet.redux.Artwork fun Card.getSingleWallet(): CardWallet? { @@ -24,6 +28,35 @@ fun Card.signedHashesCount(): Int { return wallets.map { it.totalSignedHashes ?: 0 }.sum() } +suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String { + fun ifAnyError(): String { + return when { + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> { + when (getTwinCardNumber()) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } + } + } + } + + val cardInfoResult = cardInfo ?: OnlineCardVerifier().getCardInfo(cardId, cardPublicKey) + return when (cardInfoResult) { + is Result.Success -> { + val artworkId = cardInfoResult.data.artwork?.id + if (artworkId == null || artworkId.isEmpty()) { + ifAnyError() + } else { + OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + } + is Result.Failure -> ifAnyError() + } +} + fun Card.getArtworkUrl(artworkId: String?): String? { return when { artworkId != 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 d879826f12..d2d8aa143c 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 @@ -21,7 +21,8 @@ import com.tangem.tap.tangemSdkManager class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) { private val currentCardId: String = scanResponse.card.cardId - private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) +// private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) + private val secondCardId: String? = null private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null 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 8c66c05bfa..65c4d7cb53 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,7 +1,6 @@ package com.tangem.tap.domain.twins import com.tangem.common.card.Card -import com.tangem.tap.common.extensions.isEven class TwinsHelper { companion object { @@ -21,22 +20,6 @@ class TwinsHelper { } } - fun getTwinsCardId(cardId: String): String? { - val cardIdWithNewSeries = when (getTwinCardNumber(cardId) ?: return null) { - TwinCardNumber.First -> { - val index = if (cardId.startsWith(firstCardSeries[0])) 0 else 1 - cardId.replace(firstCardSeries[index], secondCardSeries[index]) - } - TwinCardNumber.Second -> { - val index = if (cardId.startsWith(secondCardSeries[0])) 0 else 1 - cardId.replace(secondCardSeries[index], firstCardSeries[index]) - } - } - val cardIdWithoutChecksum = cardIdWithNewSeries.dropLast(1) - val checkSum = cardIdWithoutChecksum.calculateLuhn() - return cardIdWithoutChecksum + checkSum - } - fun getTwinCardIdForUser(cardId: String): String { if (cardId.length < 16) return cardId @@ -50,21 +33,6 @@ class TwinsHelper { } } -private fun String.calculateLuhn(): Int { - val checksum = 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() - .rem(10) - return (10 - checksum) % 10 -} - enum class TwinCardNumber(val number: Int) { First(1), Second(2); @@ -82,10 +50,6 @@ fun Card.getTwinCardNumber(): TwinCardNumber? { return TwinsHelper.getTwinCardNumber(this.cardId) } -fun Card.getTwinsCardId(): String? { - return TwinsHelper.getTwinsCardId(this.cardId) -} - fun Card.getTwinCardIdForUser(): String { return TwinsHelper.getTwinCardIdForUser(this.cardId) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 3e8ccb008b..4eb68d750c 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 @@ -1,24 +1,19 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.post 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.FragmentShareTransition import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.features.onboarding.service.OnboardingHelper +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.send.redux.states.ButtonState -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.Middleware @@ -52,55 +47,30 @@ private fun handleReadCard() { if (!preferencesStorage.wasDisclaimerAccepted()) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } else { - fun changeButtonState(state: ButtonState) { - val btnState = IndeterminateProgressButton(state) - store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) - } - - fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanResponse) { - scope.launch { - tapWalletManager.onCardScanned(scanResponse) - delay(DELAY_SDK_DIALOG_CLOSE) - withMainContext { changeButtonState(ButtonState.ENABLED) } - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - } changeButtonState(ButtonState.PROGRESS) - - store.dispatch(GlobalAction.ReadCard({ scanResponse -> - val tapWalletManager = store.state.globalState.tapWalletManager - if (!OnboardingHelper.isOnboardingCase(scanResponse)) { - proceedToWalletScreen(tapWalletManager, scanResponse) - return@ReadCard + store.dispatch(GlobalAction.ScanCard({ scanResponse -> + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, AppScreen.Home)) + navigateTo(navigateTo, store.state.homeState.shareTransition) + } else { + scope.launch { store.state.globalState.tapWalletManager.onCardScanned(scanResponse) } + navigateTo(AppScreen.Wallet, null) } - - val service = OnboardingHelper.createService( - AppScreen.Home, - scanResponse, - tapWalletManager.walletManagerFactory - ) - if (service == null) { - proceedToWalletScreen(tapWalletManager, scanResponse) - return@ReadCard - } - - service.onInitializationProgress = { - when (it) { - ProgressState.Loading -> changeButtonState(ButtonState.PROGRESS) - ProgressState.Done -> changeButtonState(ButtonState.ENABLED) - ProgressState.Error -> changeButtonState(ButtonState.ENABLED) - } - } - service.onInitialized = { - store.dispatch(GlobalAction.Onboarding.Activate(service)) - store.dispatch(NavigationAction.NavigateTo(it, store.state.homeState.shareTransition)) - } - service.onError = { - store.dispatchErrorNotification(it) - } - post(DELAY_SDK_DIALOG_CLOSE) { service.initializeOnboarding() } }, { changeButtonState(ButtonState.ENABLED) })) } +} + +private fun changeButtonState(state: ButtonState) { + val btnState = IndeterminateProgressButton(state) + store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) +} + +private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) { + post(DELAY_SDK_DIALOG_CLOSE) { + changeButtonState(ButtonState.ENABLED) + store.dispatch(NavigationAction.NavigateTo(screen, transition)) + } } \ No newline at end of file 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 new file mode 100644 index 0000000000..38b2517ad1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.ProductType +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.preferencesStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingHelper { + companion object { + + fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) + preferencesStorage.usedCardsPrefStorage.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.Other -> AppScreen.OnboardingOther + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..62358e80c0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -0,0 +1,129 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.isZero +import com.tangem.common.services.Result +import com.tangem.operations.attestation.CardVerifyAndGetInfo +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.extensions.isPositive +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.UrlBitmapLoader +import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.persistence.UsedCardsPrefStorage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** +[REDACTED_AUTHOR] + */ +class OnboardingManager( + var scanResponse: ScanResponse, + val fromScreen: AppScreen, + val usedCardsPrefStorage: UsedCardsPrefStorage, +) { + + var cardInfo: Result? = null + private set + + suspend fun loadArtwork(): Artwork { + cardInfo = OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey) + return loadArtwork(scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)) + } + + suspend fun loadArtwork(url: String): Artwork { + return withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + UrlBitmapLoader().loadBitmap(url) { + val result = when (it) { + is Result.Success -> Artwork(url, it.data) + is Result.Failure -> Artwork(url, null) + } + continuation.resume(result) + } + } + } + } + + suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance { + return when (val result = walletManager.safeUpdate()) { + is Result.Success -> { + val wallet = walletManager.wallet + val valueOfAmount = wallet.amounts[AmountType.Coin]?.value + if (valueOfAmount == null) { + 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.recentTransactions.isNotEmpty(), currency) + } else { + OnboardingWalletBalance.done(valueOfAmount, false, currency) + } + balance + } + } + is Result.Failure -> { + val error = (result.error as? TapError) ?: TapError.UnknownError + when (error) { + is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error) + // NoInternetConnection, WalletManagerUpdate.InternalError + else -> OnboardingWalletBalance.criticalError(error) + } + } + } + } + + fun activationStarted(cardId: String) { + usedCardsPrefStorage.activationStarted(cardId) + } + + fun activationFinished(cardId: String) { + usedCardsPrefStorage.activationFinished(cardId) + } +} + +data class OnboardingWalletBalance( + val value: BigDecimal = BigDecimal.ZERO, + val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown), + val hasIncomingTransaction: Boolean = false, + val state: ProgressState, + val error: TapError? = null, + val criticalError: TapError? = null, +) { + + fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction + + val amountToCreateAccount: String? + get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null + + companion object { + fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( + state = ProgressState.Error, + error = error + ) + + fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( + state = ProgressState.Error, + criticalError = error + ) + + fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance( + value, + state = ProgressState.Loading + ) + + fun done(value: BigDecimal, hasTransactions: Boolean, currency: Currency.Blockchain): OnboardingWalletBalance = + OnboardingWalletBalance(value, currency, hasTransactions, 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 b27d17e06a..dde273aa2f 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 @@ -24,7 +24,7 @@ abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_o protected var fromScreen: AppScreen? = null override fun onCreate(savedInstanceState: Bundle?) { - fromScreen = store.state.globalState.onboardingService?.fromScreen + fromScreen = store.state.globalState.onboardingManager?.fromScreen super.onCreate(savedInstanceState) } 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 7712bbc3da..97d31afd41 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 @@ -1,13 +1,18 @@ package com.tangem.tap.features.onboarding.products.note +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.os.Bundle import android.view.View import android.view.animation.OvershootInterpolator +import android.widget.ImageView import androidx.annotation.LayoutRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import com.tangem.blockchain.common.Blockchain +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 @@ -28,6 +33,7 @@ import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* class OnboardingNoteFragment : BaseOnboardingFragment() { private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget + private var currentStep: OnboardingNoteStep = OnboardingNoteStep.None override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note @@ -36,11 +42,12 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) val resources = AndroidResources( - OnboardingStringResources( - R.string.copy_toast_msg - ) + OnboardingStringResources( + R.string.copy_toast_msg + ) ) + store.dispatch(OnboardingNoteAction.LoadCardArtwork) store.dispatch(OnboardingNoteAction.SetResources(resources)) store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } @@ -57,7 +64,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { override fun newState(state: OnboardingNoteState) { if (activity == null) return - state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) } + state.cardArtwork?.let { imv_front_card.swapToBitmapDrawable(it.artwork) } pb_state.max = state.steps.size - 1 pb_state.progress = state.progress @@ -69,13 +76,14 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } setBalance(state) showConfetti(state.showConfetti) + currentStep = state.currentStep } private fun setBalance(state: OnboardingNoteState) { - if (state.balanceCurrency.blockchain == Blockchain.Unknown) return + if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return - val balanceValue = state.balanceValue.stripZeroPlainString() - val currency = state.balanceCurrency.currencySymbol + val balanceValue = state.walletBalance.value.stripZeroPlainString() + val currency = state.walletBalance.currency.currencySymbol tv_balance_value.text = "$balanceValue $currency" } @@ -104,7 +112,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tv_body.setText(R.string.onboarding_create_wallet_body) imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle)) - updateConstraints(R.layout.lp_onboarding_create_wallet) + updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet) } private fun setupTopUpWalletState(state: OnboardingNoteState) { @@ -121,7 +129,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tv_header.setText(R.string.onboarding_top_up_header) tv_body.setText(R.string.onboarding_top_up_body) - btnRefreshBalanceWidget.changeState(state.balanceState) + btnRefreshBalanceWidget.changeState(state.walletBalance.state) if (btnRefreshBalanceWidget.isShowing != true) { postUi(300) { btnRefreshBalanceWidget.mainView.setOnClickListener { @@ -130,7 +138,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } } imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - updateConstraints(R.layout.lp_onboarding_topup_wallet) + updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet) } private fun setupDoneState(state: OnboardingNoteState) { @@ -149,10 +157,12 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tv_body.setText(R.string.onboarding_done_body) imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - updateConstraints(R.layout.lp_onboarding_done_activation) + updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation) } - private fun updateConstraints(@LayoutRes layoutId: Int) { + private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) { + if (this.currentStep == currentStep) return + val constraintSet = ConstraintSet() constraintSet.clone(requireContext(), layoutId) constraintSet.applyTo(onboarding_main_container) @@ -160,4 +170,13 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { transition.interpolator = OvershootInterpolator() TransitionManager.beginDelayedTransition(onboarding_main_container, transition) } +} + +fun ImageView.swapToBitmapDrawable(bitmap: Bitmap?, duration: Long = 200) { + if (drawable is BitmapDrawable && (drawable as BitmapDrawable).bitmap == bitmap) return + + fadeOut(duration) { + setImageBitmap(bitmap) + fadeIn(duration) + } } \ No newline at end of file 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 9c1644dda1..798f8f4136 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,8 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.tap.features.onboarding.service.OnboardingWalletBalance +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.Action /** @@ -9,11 +11,14 @@ 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() + data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() 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 83ea98d0d0..e63b32b9b4 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.note.redux 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 @@ -8,13 +9,16 @@ 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.onboarding.service.balanceIsToppedUp +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.ProgressState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.launch import org.rekotlin.Action +import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class OnboardingNoteMiddleware { @@ -26,44 +30,57 @@ class OnboardingNoteMiddleware { private val onboardingNoteMiddleware: Middleware = { dispatch, state -> { next -> { action -> - handleNoteAction(action) + handleNoteAction(action, dispatch) next(action) } } } -private fun handleNoteAction(action: Action) { +private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingNoteAction) return - val service = store.state.onboardingNoteState.onboardingService ?: return + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager ?: return + + val scanResponse = onboardingManager.scanResponse + val card = onboardingManager.scanResponse.card val noteState = store.state.onboardingNoteState when (action) { + is OnboardingNoteAction.LoadCardArtwork -> { + scope.launch { + val artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artwork)) } + } + } is OnboardingNoteAction.DetermineStepOfScreen -> { val step = when { - service.walletManager == null -> OnboardingNoteStep.CreateWallet - service.balanceIsToppedUp() -> OnboardingNoteStep.Done + !card.hasWallets() -> OnboardingNoteStep.CreateWallet + noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done else -> OnboardingNoteStep.TopUpWallet } store.dispatch((OnboardingNoteAction.SetStepOfScreen(step))) } is OnboardingNoteAction.SetStepOfScreen -> { - if (action.step == OnboardingNoteStep.Done) { - service.activationFinished() - postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) } + when (action.step) { + OnboardingNoteStep.TopUpWallet -> { + store.dispatch(OnboardingNoteAction.Balance.Update) + } + OnboardingNoteStep.Done -> { + onboardingManager.activationFinished(card.cardId) + postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) } + } } } is OnboardingNoteAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createProductWallet(service.scanResponse) + val result = tangemSdkManager.createProductWallet(scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = service.scanResponse.copy(card = result.data) - service.scanResponse = updatedResponse - service.activationStarted() - + val updatedResponse = scanResponse.copy(card = result.data) + onboardingManager.scanResponse = updatedResponse + onboardingManager.activationStarted(updatedResponse.card.cardId) store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet)) - store.dispatch(OnboardingNoteAction.Balance.Update) } is CompletionResult.Failure -> { // do nothing @@ -73,44 +90,57 @@ private fun handleNoteAction(action: Action) { } } is OnboardingNoteAction.Balance.Update -> { - val loadingBalance = service.getBalance().copy( - state = ProgressState.Loading, - error = null, - criticalError = null + val walletManager = if (noteState.walletManager != null) { + noteState.walletManager + } else { + val wmFactory = globalState.tapWalletManager.walletManagerFactory + val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard { + val message = "Loading cancelled. Cause: wallet manager didn't created" + val customError = TapError.CustomError(message) + store.dispatchErrorNotification(customError) + return + } + dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) + walletManager + } + + val balanceIsLoading = noteState.walletBalance.copy( + state = ProgressState.Loading, + error = null, + criticalError = null ) - store.dispatch(OnboardingNoteAction.Balance.Set(loadingBalance)) + store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) scope.launch { - val loadedBalance = service.updateBalance() - if (loadedBalance.criticalError != null) { - store.dispatchErrorNotification(loadedBalance.criticalError) - } + val loadedBalance = onboardingManager.updateBalance(walletManager) + loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } } + } is OnboardingNoteAction.Balance.Set -> { - if (service.balanceIsToppedUp()) { + if (action.balance.balanceIsToppedUp()) { store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) } } is OnboardingNoteAction.ShowAddressInfoDialog -> { - val addressData = noteState.onboardingService?.getAddressData() ?: return + val addressData = noteState.walletManager?.getAddressData() ?: return val addressWasCopied = noteState.resources.strings.addressWasCopied val appDialog = AppDialog.AddressInfoDialog( - addressData, - onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, - onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } ) store.dispatchDialogShow(appDialog) } is OnboardingNoteAction.TopUp -> { - val topUpUrl = noteState.onboardingService?.getToUpUrl() ?: return + val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return store.dispatchOpenUrl(topUpUrl) } OnboardingNoteAction.Done -> { - store.dispatch(GlobalAction.Onboarding.Deactivate) + store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + globalState.tapWalletManager.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 efe2540b66..03415eec84 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 @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.service.OnboardingNoteService import org.rekotlin.Action class OnboardingNoteReducer { @@ -15,34 +14,20 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt var state = appState.onboardingNoteState when (action) { - is GlobalAction.Onboarding.Activate -> { - if (action.onboardingService is OnboardingNoteService) { - val service = action.onboardingService - val balance = service.getBalance() - state = state.copy( - onboardingService = service, - artworkBitmap = service.getArtwork().value?.artwork, - balanceValue = balance.value, - balanceCurrency = balance.currency, - balanceState = balance.state, - amountToCreateAccount = balance.amountToCreateAccount, - showConfetti = false - ) - } - } - GlobalAction.Onboarding.Deactivate -> { + is GlobalAction.Onboarding.Start -> { state = OnboardingNoteState() } + is OnboardingNoteAction.SetArtworkUrl -> { + 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( - balanceValue = action.balance.value, - balanceCurrency = action.balance.currency, - balanceState = action.balance.state, - amountToCreateAccount = action.balance.amountToCreateAccount, - ) + state = state.copy(walletBalance = action.balance) } is OnboardingNoteAction.SetStepOfScreen -> { if (action.step != state.currentStep && state.steps.contains(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 b95c58510d..d4f1c2bcb7 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,28 +1,22 @@ package com.tangem.tap.features.onboarding.products.note.redux -import android.graphics.Bitmap -import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.features.onboarding.service.OnboardingNoteService -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType -import java.math.BigDecimal /** [REDACTED_AUTHOR] */ data class OnboardingNoteState( - val onboardingService: OnboardingNoteService? = null, + val walletManager: WalletManager? = null, val resources: AndroidResources = AndroidResources(), - // UI - val showConfetti: Boolean = false, - val artworkBitmap: Bitmap? = null, - val balanceValue: BigDecimal = BigDecimal.ZERO, - val balanceCurrency: Currency = Currency.Blockchain(Blockchain.Unknown), - val balanceState: ProgressState = ProgressState.Done, - val amountToCreateAccount: String? = null, + // UI + val cardArtwork: Artwork? = null, + val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(), val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), + val showConfetti: Boolean = false, ) : StateType { val progress: Int 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 99ce6f4280..3b2dcf7952 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 @@ -10,6 +10,7 @@ import androidx.transition.TransitionManager import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.transitions.InternalNoteLayoutTransition 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 import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep @@ -29,6 +30,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment = { dispatch, state -> { next -> { action -> - handleOtherCardsAction(action) + handleOtherCardsAction(action, dispatch) next(action) } } } -private fun handleOtherCardsAction(action: Action) { +private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingOtherCardsAction) return - val service = store.state.onboardingOtherCardsState.onboardingService ?: return - val noteState = store.state.onboardingOtherCardsState + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager ?: return + + val card = onboardingManager.scanResponse.card when (action) { + is OnboardingOtherCardsAction.LoadCardArtwork -> { + scope.launch { + val artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artwork)) } + } + } is OnboardingOtherCardsAction.DetermineStepOfScreen -> { val step = when { - service.walletManager == null -> OnboardingOtherCardsStep.CreateWallet + !card.hasWallets() -> OnboardingOtherCardsStep.CreateWallet else -> OnboardingOtherCardsStep.Done } store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) } is OnboardingOtherCardsAction.SetStepOfScreen -> { if (action.step == OnboardingOtherCardsStep.Done) { - service.activationFinished() + onboardingManager.activationFinished(card.cardId) postUi(500) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } } is OnboardingOtherCardsAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createProductWallet(service.scanResponse) + val result = tangemSdkManager.createProductWallet(onboardingManager.scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = service.scanResponse.copy(card = result.data) - service.scanResponse = updatedResponse - service.activationStarted() + val updatedResponse = onboardingManager.scanResponse.copy(card = result.data) + onboardingManager.scanResponse = updatedResponse + onboardingManager.activationStarted(updatedResponse.card.cardId) delay(DELAY_SDK_DIALOG_CLOSE) store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) @@ -71,9 +81,9 @@ private fun handleOtherCardsAction(action: Action) { } } OnboardingOtherCardsAction.Done -> { - store.dispatch(GlobalAction.Onboarding.Deactivate) + store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + globalState.tapWalletManager.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 316f97f636..a6313b9199 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 @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService import org.rekotlin.Action class OnboardingOtherCardsReducer { @@ -15,19 +14,12 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingOtherC var state = appState.onboardingOtherCardsState when (action) { - is GlobalAction.Onboarding.Activate -> { - if (action.onboardingService is OnboardingOtherCardsService) { - val service = action.onboardingService - state = state.copy( - onboardingService = service, - artworkBitmap = service.getArtwork().value?.artwork, - showConfetti = false - ) - } - } - GlobalAction.Onboarding.Deactivate -> { + is GlobalAction.Onboarding.Start -> { state = OnboardingOtherCardsState() } + is OnboardingOtherCardsAction.SetArtworkUrl -> { + state = state.copy(cardArtwork = action.artwork) + } is OnboardingOtherCardsAction.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/otherCards/redux/OnboardingOtherCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt index bcf232bd85..b306101506 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt @@ -1,17 +1,15 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux -import android.graphics.Bitmap -import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType /** [REDACTED_AUTHOR] */ data class OnboardingOtherCardsState( - val onboardingService: OnboardingOtherCardsService? = null, - // UI + // UI + val cardArtwork: Artwork? = null, val showConfetti: Boolean = false, - val artworkBitmap: Bitmap? = null, val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None, val steps: List = OnboardingOtherCardsStep.values().toList(), ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt deleted file mode 100644 index 8f7eb6f726..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.ProductType -import com.tangem.tap.domain.extensions.hasWallets -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.preferencesStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingHelper { - companion object { - - fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) - preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true - - fun createService( - fromScreen: AppScreen, - scanResponse: ScanResponse, - walletManagerFactory: WalletManagerFactory, - ): ProductOnboardingService? { - val cardInfoStorage = preferencesStorage.usedCardsPrefStorage - return when(scanResponse.productType) { - ProductType.Note -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) - ProductType.Wallet -> null - ProductType.Twin -> null - ProductType.Other -> OnboardingOtherCardsService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt deleted file mode 100644 index feaa76eb5c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.persistence.UsedCardsPrefStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingNoteService( - override val fromScreen: AppScreen, - override var scanResponse: ScanResponse, - cardInfoStorage: UsedCardsPrefStorage, - walletManagerFactory: WalletManagerFactory, -) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { - - override fun navigateToScreen(): AppScreen { - return AppScreen.OnboardingNote - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt deleted file mode 100644 index 5e81842312..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.tap.common.extensions.withMainContext -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.features.wallet.redux.AddressData -import com.tangem.tap.persistence.UsedCardsPrefStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingOtherCardsService( - override val fromScreen: AppScreen, - override var scanResponse: ScanResponse, - cardInfoStorage: UsedCardsPrefStorage, - walletManagerFactory: WalletManagerFactory, -) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { - - override fun navigateToScreen(): AppScreen { - return AppScreen.OnboardingOther - } - - // Return any value and any state. The balance of the card is not important in this service - override suspend fun updateBalance(): OnboardingWalletBalance { - val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") - withMainContext { loadedBalance = OnboardingWalletBalance.error(customError) } - return loadedBalance - } - - override fun getToUpUrl(): String? { - throw UnsupportedOperationException() - } - - override fun getAddressData(): AddressData? { - throw UnsupportedOperationException() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt deleted file mode 100644 index 6faff4073e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt +++ /dev/null @@ -1,291 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import android.util.Log -import com.tangem.blockchain.common.* -import com.tangem.common.card.Card -import com.tangem.common.extensions.guard -import com.tangem.common.extensions.isZero -import com.tangem.common.services.Result -import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.isPositive -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.withMainContext -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.UrlBitmapLoader -import com.tangem.tap.domain.extensions.getArtworkUrl -import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.domain.topup.TradeCryptoHelper -import com.tangem.tap.features.wallet.redux.AddressData -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.reducers.createAddressesData -import com.tangem.tap.persistence.UsedCardsPrefStorage -import com.tangem.tap.store -import kotlinx.coroutines.* -import java.io.PrintWriter -import java.io.StringWriter -import java.math.BigDecimal -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -/** -[REDACTED_AUTHOR] - */ -interface OnboardingService { - val fromScreen: AppScreen - val scanResponse: ScanResponse - - // val cardInfoStorage: UsedCardsPrefStorage - var walletManager: WalletManager? - - var onInitializationProgress: ((ProgressState) -> Unit)? - var onInitialized: ((AppScreen) -> Unit)? - var onError: ((TapError) -> Unit)? - - fun initializeOnboarding() - fun getArtwork(): OnboardingArtwork - fun getBalance(): OnboardingWalletBalance - suspend fun updateBalance(): OnboardingWalletBalance - fun getToUpUrl(): String? - fun getAddressData(): AddressData? - - fun activationStarted() - fun activationFinished() -} - -abstract class ProductOnboardingService( - private val cardInfoStorage: UsedCardsPrefStorage, - private val walletManagerFactory: WalletManagerFactory, -) : OnboardingService { - - override var walletManager: WalletManager? = null - - override var onInitializationProgress: ((ProgressState) -> Unit)? = null - override var onInitialized: ((AppScreen) -> Unit)? = null - override var onError: ((TapError) -> Unit)? = null - - protected var isProceeded = false - - protected var loadedCardArtwork: OnboardingArtwork = OnboardingArtwork.loading() - set(value) { - field = value - tryToProceed() - } - - protected var loadedBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading() - set(value) { - field = value - tryToProceed() - } - - protected val internalScope = CoroutineScope(Job() + Dispatchers.IO + coroutineExceptionHandler()) - - override fun initializeOnboarding() { - val card = scanResponse.card - onInitializationProgress?.invoke(ProgressState.Loading) - - internalScope.launch { - val artwork = loadCardArtwork(card) - withMainContext { - loadedCardArtwork = if (artwork == null) OnboardingArtwork.error() else OnboardingArtwork.done(artwork) - } - } - internalScope.launch { updateBalance() } - } - - override fun getArtwork(): OnboardingArtwork = loadedCardArtwork - - override fun getBalance(): OnboardingWalletBalance = loadedBalance - - override fun getToUpUrl(): String? { - val wallet = walletManager?.wallet ?: return null - val config = store.state.globalState.configManager?.config ?: return null - val defaultAddress = wallet.address - - return TradeCryptoHelper.getUrl( - TradeCryptoHelper.Action.Buy, - wallet.blockchain, - wallet.blockchain.currency, - defaultAddress, - config.moonPayApiKey, - config.moonPayApiSecretKey - ) - } - - override fun getAddressData(): AddressData? { - val wallet = walletManager?.wallet ?: return null - - val addressDataList = wallet.createAddressesData() - return if (addressDataList.isEmpty()) null - else addressDataList[0] - } - - protected suspend fun loadCardArtwork(card: Card): Artwork? { - return when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { - is Result.Success -> { - val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL - loadBitmap(artworkUrl) - } - is Result.Failure -> { - loadBitmap(Artwork.DEFAULT_IMG_URL) - } - } - } - - override suspend fun updateBalance(): OnboardingWalletBalance { - withMainContext { loadedBalance = OnboardingWalletBalance.loading(loadedBalance.value) } - - val walletManager = walletManager ?: walletManagerFactory.makePrimaryWalletManager(scanResponse).guard { - val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") - withMainContext { loadedBalance = OnboardingWalletBalance.error(customError) } - return loadedBalance - } - - this.walletManager = walletManager - val currency = Currency.Blockchain(walletManager.wallet.blockchain) - val updatedBalance = when (val result = walletManager.safeUpdate()) { - is Result.Success -> { - val wallet = walletManager.wallet - val valueOfAmount = wallet.amounts[AmountType.Coin]?.value - if (valueOfAmount == null) { - val customError = TapError.CustomError("Amount is NULL") - OnboardingWalletBalance.criticalError(customError) - } else { - val balance = if (valueOfAmount.isZero()) { - OnboardingWalletBalance.done(valueOfAmount, hasForIncomingTransactions(wallet), currency) - } else { - OnboardingWalletBalance.done(valueOfAmount, false, currency) - } - balance - } - } - is Result.Failure -> { - val error = (result.error as? TapError) ?: TapError.UnknownError - when (error) { - is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error) -// NoInternetConnection, WalletManagerUpdate.InternalError - else -> OnboardingWalletBalance.criticalError(error) - } - } - } - withMainContext { loadedBalance = updatedBalance } - return loadedBalance - } - - override fun activationStarted() { - cardInfoStorage.activationStarted(scanResponse.card.cardId) - } - - override fun activationFinished() { - cardInfoStorage.activationFinished(scanResponse.card.cardId) - } - - protected open suspend fun hasForIncomingTransactions(wallet: Wallet): Boolean { - return wallet.recentTransactions.isNotEmpty() - } - - protected open fun tryToProceed() { - if (isProceeded) return - - loadedBalance.criticalError?.let { - sendError(it) - return - } - if (!isReadyToProceed()) return - - isProceeded = true - onInitializationProgress?.invoke(ProgressState.Done) - onInitialized?.invoke(navigateToScreen()) - } - - protected open fun isReadyToProceed(): Boolean { - return loadedBalance.state != ProgressState.Loading && loadedCardArtwork.state != ProgressState.Loading - } - - protected fun sendError(error: TapError) { - onInitializationProgress?.invoke(ProgressState.Error) - onError?.invoke(error) - } - - protected abstract fun navigateToScreen(): AppScreen - - private suspend fun loadBitmap(url: String): Artwork? { - return withContext(Dispatchers.Main) { - suspendCoroutine { continuation -> - UrlBitmapLoader().loadBitmap(url) { - val result = when (it) { - is Result.Success -> Artwork(url, it.data) - is Result.Failure -> null - } - continuation.resume(result) - } - } - } - } - - private fun coroutineExceptionHandler(): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> - StringWriter().apply { - throwable.printStackTrace(PrintWriter(this)) - Log.e("Coroutine", this.toString()) - store.dispatchDebugErrorNotification(this.toString()) - } - } -} - -data class OnboardingArtwork( - val value: Artwork? = null, - val state: ProgressState -) { - companion object { - // balance was not loaded at all when any error occurred - fun error(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Error) - - // balance is loading - fun loading(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Loading) - - // balance was loaded - fun done(artwork: Artwork): OnboardingArtwork = OnboardingArtwork(artwork, ProgressState.Done) - } -} - -data class OnboardingWalletBalance( - val value: BigDecimal = BigDecimal.ZERO, - val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown), - val hasIncomingTransaction: Boolean = false, - val state: ProgressState, - val error: TapError? = null, - val criticalError: TapError? = null, -) { - - val amountToCreateAccount: String? - get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null - - companion object { - fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( - state = ProgressState.Error, - error = error - ) - - fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( - state = ProgressState.Error, - criticalError = error - ) - - fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance( - value, - state = ProgressState.Loading - ) - - fun done(value: BigDecimal, hasTransactions: Boolean, currency: Currency.Blockchain): OnboardingWalletBalance = - OnboardingWalletBalance(value, currency, hasTransactions, ProgressState.Done) - } -} - -fun ProductOnboardingService.balanceIsToppedUp(): Boolean { - val balance = getBalance() - return balance.value.isPositive() || balance.hasIncomingTransaction -} \ 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 51d6c3740d..55e7fb4f66 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 @@ -151,7 +151,7 @@ sealed class WalletAction : Action { object ShowOnboarding : TwinsAction() object SetOnboardingShown : TwinsAction() data class SetTwinCard( - val secondCardId: String, val number: TwinCardNumber, + val secondCardId: String?, val number: TwinCardNumber, val isCreatingTwinCardsAllowed: Boolean, ) : TwinsAction() } diff --git a/app/src/main/res/layout/layout_onboarding_note.xml b/app/src/main/res/layout/layout_onboarding_note.xml index e2cb262a46..90cfd95782 100644 --- a/app/src/main/res/layout/layout_onboarding_note.xml +++ b/app/src/main/res/layout/layout_onboarding_note.xml @@ -39,7 +39,7 @@ android:layout_marginStart="32dp" android:layout_marginTop="106dp" android:layout_marginEnd="32dp" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.495" 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 1a45daa73c..076cf8c8ec 100644 --- a/app/src/main/res/layout/lp_onboarding_create_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -39,7 +39,7 @@ android:layout_marginStart="32dp" android:layout_marginTop="106dp" android:layout_marginEnd="32dp" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.495" diff --git a/app/src/main/res/layout/lp_onboarding_done_activation.xml b/app/src/main/res/layout/lp_onboarding_done_activation.xml index a3e9722587..e476f28e3d 100644 --- a/app/src/main/res/layout/lp_onboarding_done_activation.xml +++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml @@ -38,7 +38,7 @@ android:layout_width="140dp" android:layout_height="88dp" android:scaleType="fitXY" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" android:translationY="-30dp" android:visibility="visible" 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 2783b41196..d0261db37a 100644 --- a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -38,7 +38,7 @@ android:layout_width="140dp" android:layout_height="88dp" android:scaleType="fitXY" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" android:translationY="-30dp" android:visibility="visible" From 2af5f84f123221c89f05cb64aa5db5972016e8d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 17 Oct 2021 23:07:28 +0300 Subject: [PATCH 48/55] Updated on 2026-08-14 --- .../onboarding/products/note/redux/OnboardingNoteMiddleware.kt | 3 +++ 1 file changed, 3 insertions(+) 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 e63b32b9b4..0a925dc7ee 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 @@ -16,6 +16,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -104,6 +105,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { walletManager } + val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading val balanceIsLoading = noteState.walletBalance.copy( state = ProgressState.Loading, error = null, @@ -114,6 +116,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { scope.launch { val loadedBalance = onboardingManager.updateBalance(walletManager) loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } + delay(if (isLoadedBefore) 0 else 300) withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } } From c6a201f5571ce81f21d45b91f7ef9ef4f0e124de Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Oct 2021 19:46:29 +0300 Subject: [PATCH 49/55] Updated on 2026-08-14 --- .../walletconnect/WalletConnectMiddleware.kt | 64 +++++++------------ .../tangem/tap/features/home/HomeFragment.kt | 2 +- .../tap/features/home/redux/HomeAction.kt | 2 + .../tap/features/home/redux/HomeMiddleware.kt | 7 ++ .../tap/features/home/redux/HomeReducer.kt | 4 +- .../tap/features/home/redux/HomeState.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 16 +---- 7 files changed, 39 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index d316ca59ec..dff6c35c72 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,11 +1,10 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.* -import com.tangem.common.CompletionResult import com.tangem.common.card.Card +import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString import com.tangem.tap.* -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.redux.AppState @@ -18,9 +17,6 @@ import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.wallet.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.Middleware class WalletConnectMiddleware { @@ -132,43 +128,29 @@ class WalletConnectMiddleware { } private fun handleScanCard(wcUri: String) { - scope.launch { - val result = tangemSdkManager.scanProduct( - FirebaseAnalyticsHandler, - R.string.wallet_connect_scan_card_message - ) - withContext(Dispatchers.Main) { - when (result) { - is CompletionResult.Success -> { - val card = result.data.card - - if (!card.isMultiwalletAllowed) { - store.dispatchOnMain(WalletConnectAction.UnsupportedCard) - return@withContext - } - - val walletManager = getWalletManager(card) - if (walletManager == null) { - store.dispatchOnMain(WalletConnectAction.UnsupportedCard) - return@withContext - } - - val key = walletManager.wallet.publicKey - store.dispatchOnMain(WalletConnectAction.OpenSession( - wcUri = wcUri, - wallet = WalletForSession( - card.cardId, key.toHexString(), - isTestNet = card.isTestCard - ), - )) - } - is CompletionResult.Failure -> - store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession( - null - )) - } + store.dispatch(GlobalAction.ScanCard({ scanResponse -> + val card = scanResponse.card + if (!card.isMultiwalletAllowed) { + store.dispatchOnMain(WalletConnectAction.UnsupportedCard) + return@ScanCard } - } + + val walletManager = getWalletManager(card).guard { + store.dispatchOnMain(WalletConnectAction.UnsupportedCard) + return@ScanCard + } + + val key = walletManager.wallet.publicKey + store.dispatchOnMain(WalletConnectAction.OpenSession( + wcUri = wcUri, + wallet = WalletForSession( + card.cardId, key.toHexString(), + isTestNet = card.isTestCard + ), + )) + }, { + store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null)) + }, R.string.wallet_connect_scan_card_message)) } private fun getWalletManager(card: Card): WalletManager? { 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 71e0b7a83c..314a302527 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 @@ -27,6 +27,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state -> store.dispatch(GlobalAction.GetMoonPayUserStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) } + is HomeAction.ShouldScanCardOnResume -> { + if (action.shouldScanCard) { + store.dispatch(HomeAction.ShouldScanCardOnResume(false)) + postUi(700) { store.dispatch(HomeAction.ReadCard) } + } + } is HomeAction.ReadCard -> handleReadCard() is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index 0b9b044622..d4f11f31f9 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -18,8 +18,8 @@ private fun internalReduce(action: Action, state: AppState): HomeState { is HomeAction.SetFragmentShareTransition -> { state = state.copy(shareTransition = action.shareTransition) } - is HomeAction.SetTermsOfUseState -> { - state = state.copy(isDisclaimerAccepted = action.isDisclaimerAccepted) + is HomeAction.ShouldScanCardOnResume -> { + state = state.copy(shouldScanCardOnResume = action.shouldScanCard) } is HomeAction.ChangeScanCardButtonState -> { state = state.copy(btnScanState = action.state) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 29a7825aae..8c9949cf4c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -6,7 +6,7 @@ import com.tangem.tap.features.send.redux.states.ButtonState import org.rekotlin.StateType data class HomeState( - val isDisclaimerAccepted: Boolean = false, + val shouldScanCardOnResume: Boolean = false, val shareTransition: FragmentShareTransition? = null, val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED), ) : StateType \ 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 ea654a6777..3b3a4f31c1 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 @@ -22,10 +22,10 @@ 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.Dispatchers import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -145,18 +145,8 @@ class WalletMiddleware { } is WalletAction.Scan -> { - scope.launch { - val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler) - scope.launch(Dispatchers.Main) { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - when (result) { - is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) - globalState?.tapWalletManager?.onCardScanned(result.data, true) - } - } - } - } + store.dispatch(HomeAction.ShouldScanCardOnResume(true)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) } is WalletAction.LoadCardInfo -> { scope.launch { From 5dcb39b560e0dcf0a68045d147fe47fb09608076 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Oct 2021 21:44:06 +0300 Subject: [PATCH 50/55] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/MainActivity.kt | 10 ++++++++-- app/src/main/res/values/colors.xml | 4 +++- app/src/main/res/values/styles.xml | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 41b89b88e3..6f99403572 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -59,8 +59,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + systemActions() store.state.globalState.feedbackManager?.updateAcivity(this) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) tangemSdk = TangemSdk.init(this, TangemSdkManager.config) @@ -69,6 +69,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { store.dispatch(WalletConnectAction.RestoreSessions) } + private fun systemActions() { + // makes the status bar text dark + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + override fun onResume() { super.onResume() notificationsHandler = NotificationsHandler(fragment_container) @@ -107,7 +113,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { if (snackbar != null) return snackbar = Snackbar.make( - fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE + fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE ) if (buttonTitle != null && action != null) { snackbar?.setAction(getString(buttonTitle), action) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b4334ef500..5d0fe290b1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -34,9 +34,11 @@ #F3F3F3 #F8F8FB + #DADADF + #D0D0D5 #C7C7CC #D1D1D6 - #F2F2F7 + #C9C9CD #1F000000 #14212121 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 703a685858..65e33f9906 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,10 +6,10 @@ @color/colorSecondary @color/darkGray6 @color/backgroundLightGray - - - - + false + true + true + @null - + + + + + + - \ No newline at end of file + + + + + + + + \ No newline at end of file From d087c388e253762d8df435a4c969fc1f4975250a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 22 Oct 2021 03:45:54 +0300 Subject: [PATCH 53/55] Updated on 2026-08-14 --- .../com/tangem/tap/features/onboarding/OnboardingManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 59788c84ab..2b4894d5b9 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 @@ -13,6 +13,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.UrlBitmapLoader import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.features.wallet.models.hasPendingTransactions import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.ProgressState @@ -64,7 +65,7 @@ class OnboardingManager( } else { val currency = Currency.Blockchain(walletManager.wallet.blockchain) val balance = if (valueOfAmount.isZero()) { - OnboardingWalletBalance.done(valueOfAmount, wallet.recentTransactions.isNotEmpty(), currency) + OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions(), currency) } else { OnboardingWalletBalance.done(valueOfAmount, false, currency) } From 167dae1f7598c53a962b80045814c74619a3e39f Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 24 Oct 2021 18:06:07 +0300 Subject: [PATCH 54/55] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/domain/tasks/ScanNoteTask.kt | 4 +++- .../com/tangem/tap/domain/twins/TwinCardsManager.kt | 12 +----------- .../tap/features/details/redux/DetailsReducer.kt | 3 +-- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index e80938f7d9..0cf3af21e6 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit e80938f7d9ba378a91ac544a9170597e64afa696 +Subproject commit 0cf3af21e6f441e6323a1ba127ee7518168797df 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 274f14bfa5..43425eab48 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 @@ -1,6 +1,8 @@ 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 @@ -18,6 +20,7 @@ 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 { @@ -118,7 +121,6 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { 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 - if (card.isMultiCurrencyWallet() || card.isNote()) return UpdateAppToUseThisCard() // Disable new multi-currency HD wallet cards on the old version of the app // if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard() return 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 b951b725bf..faa839cbe6 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 @@ -65,9 +65,7 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage ) - val response = tangemSdkManager.runTaskAsync( - task, null, initialMessage - ) + val response = tangemSdkManager.runTaskAsync(task, null, initialMessage) when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() @@ -116,14 +114,6 @@ class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) } - fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { - if (issuerData.size < 65) return false - val publicKey = issuerData.sliceArray(0 until 65) - val signedKey = issuerData.sliceArray(65 until issuerData.size) - return (cardWalletPublicKey != null && - CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey)) - } - private fun getIssuerKeys(context: Context, publicKey: String): KeyPair { val issuer = getIssuers(context).first { it.publicKey == publicKey } return KeyPair( 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 b22f25959e..d2bb330e40 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 @@ -191,10 +191,9 @@ private fun prepareAllowedSecurityOptions( ): EnumSet { val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true - val prohibitDefaultPin = card?.settings?.isRemovingAccessCodeAllowed != true val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java) - if (card?.isTwinCard() == true) { + if (card?.isTangemTwin() == true) { allowedSecurityOptions.add(SecurityOption.PassCode) } if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) { From 61cf84a62650ee7e308806b8309e59a0e241e7ea Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 25 Oct 2021 15:44:05 +0300 Subject: [PATCH 55/55] Updated on 2026-08-14 --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2d9e53e7e6..0b53a8df5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,16 +69,16 @@ dependencies { implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' - implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.1' implementation 'com.google.android.material:material:1.4.0' implementation "androidx.core:core-ktx:1.6.0" - implementation 'com.google.android.play:core:1.10.0' + implementation 'com.google.android.play:core:1.10.2' implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'com.tangem:blockchain:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-88' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-88' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-91' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-91' // WebView implementation "androidx.browser:browser:1.3.0"