Updated on 2026-08-14

This commit is contained in:
Tangem 2021-10-06 22:12:14 +03:00
parent baaa317381
commit ca51d6803a
13 changed files with 198 additions and 36 deletions

View file

@ -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"

View file

@ -16,9 +16,16 @@ fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
return toRemove.isNotEmpty()
}
fun <T> MutableList<T>.replaceBy(item: T, predicate: (T) -> Boolean) {
fun <T> MutableList<T>.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 <T> MutableList<T>.replaceByOrAdd(item: T, predicate: (T) -> Boolean) {
if (!replaceBy(item, predicate)) add(item)
}

View file

@ -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 <T> withMainContext(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.Main, block)
}
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.IO, block)
}

View file

@ -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

View file

@ -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))

View file

@ -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()) {

View file

@ -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()

View file

@ -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<View>(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()
}
}

View file

@ -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())
}
}

View file

@ -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)
}

View file

@ -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"
}
}

View file

@ -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() {

View file

@ -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<UsedCardInfo>? = 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<UsedCardInfo>) {
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<UsedCardInfo> {
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,
)