From ca51d6803aa9cf9dfa8d29f3a8c516fa1c943618 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:12:14 +0300 Subject: [PATCH] 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