Updated on 2026-08-14

This commit is contained in:
Tangem 2022-06-17 16:58:02 +03:00
parent 0c31e7fa6a
commit 18bd8ed03b
9 changed files with 252 additions and 37 deletions

View file

@ -118,8 +118,10 @@ dependencies {
// Camera // Camera
implementation 'com.otaliastudios:cameraview:2.6.3' implementation 'com.otaliastudios:cameraview:2.6.3'
// Image Loading - Picasso // Image Loading
implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'io.coil-kt:coil:2.1.0'
implementation 'io.coil-kt:coil-compose:2.1.0'
// Firebase // Firebase
implementation platform('com.google.firebase:firebase-bom:26.0.0') implementation platform('com.google.firebase:firebase-bom:26.0.0')
@ -172,9 +174,6 @@ dependencies {
implementation "com.github.skydoves:androidveil:1.1.2" implementation "com.github.skydoves:androidveil:1.1.2"
implementation("io.coil-kt:coil:2.0.0-rc01")
implementation("io.coil-kt:coil-compose:2.0.0-rc01")
implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6' implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

@ -1 +1 @@
Subproject commit 985642c135606ba1f59fd739127aec03024da79b Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564

View file

@ -1,6 +1,8 @@
package com.tangem.tap package com.tangem.tap
import android.app.Application import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.appsflyer.AppsFlyerLib import com.appsflyer.AppsFlyerLib
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig import com.google.firebase.remoteconfig.ktx.remoteConfig
@ -10,7 +12,7 @@ import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer import com.tangem.domain.DomainLayer
import com.tangem.network.common.MoshiConverter import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.images.PicassoHelper import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
@ -43,7 +45,7 @@ lateinit var currenciesRepository: CurrenciesRepository
lateinit var walletConnectRepository: WalletConnectRepository lateinit var walletConnectRepository: WalletConnectRepository
lateinit var shopService: TangemShopService lateinit var shopService: TangemShopService
class TapApplication : Application() { class TapApplication : Application(), ImageLoaderFactory {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -61,7 +63,6 @@ class TapApplication : Application() {
NetworkConnectivity.createInstance(store, this) NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this) preferencesStorage = PreferencesStorage(this)
PicassoHelper.initPicassoWithCaching(this)
currenciesRepository = CurrenciesRepository( currenciesRepository = CurrenciesRepository(
this, store.state.domainNetworks.tangemTechService this, store.state.domainNetworks.tangemTechService
) )
@ -75,6 +76,10 @@ class TapApplication : Application() {
initAppsFlyer() initAppsFlyer()
} }
override fun newImageLoader(): ImageLoader {
return createCoilImageLoader(context = this)
}
private fun loadConfigs() { private fun loadConfigs() {
val moshi = MoshiConverter.defaultMoshi() val moshi = MoshiConverter.defaultMoshi()
val localLoader = FeaturesLocalLoader(this, moshi) val localLoader = FeaturesLocalLoader(this, moshi)
@ -108,11 +113,6 @@ class TapApplication : Application() {
} }
data class LogConfig( data class LogConfig(
// disables both [internal, http] val coil: Boolean = BuildConfig.DEBUG,
val picasso: Boolean = BuildConfig.DEBUG,
val picassoInternal: Boolean = true,
val picassoHttp: Boolean = true,
val storeAction: Boolean = BuildConfig.DEBUG, val storeAction: Boolean = BuildConfig.DEBUG,
) )

View file

@ -1,6 +1,11 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.graphics.* import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.constraintlayout.utils.widget.ImageFilterView
import com.squareup.picasso.Callback import com.squareup.picasso.Callback
@ -32,6 +37,28 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = null imageView.colorFilter = null
textView.text = null textView.text = null
if (token != null) {
setTokenImage(imageView, textView, token, blockchain)
}
this.load(url)
.transform(RoundedCornersTransform())
.noPlaceholder()
?.into(imageView,
object : Callback {
override fun onError(e: Exception?) {
setOfflineCurrencyImage(imageView, textView, token, blockchain)
}
override fun onSuccess() {
if (token != null) {
imageView.colorFilter = null
textView.text = null
}
if (blockchain.isTestnet()) imageView.saturation = 0f
}
}
)
when { when {
token?.symbol == QCX -> { token?.symbol == QCX -> {
this.load(R.drawable.ic_qcx)?.into(imageView) this.load(R.drawable.ic_qcx)?.into(imageView)
@ -59,7 +86,8 @@ fun Picasso.loadCurrenciesIcon(
} }
if (blockchain.isTestnet()) imageView.saturation = 0f if (blockchain.isTestnet()) imageView.saturation = 0f
} }
}) }
)
} }
else -> { else -> {
setOfflineCurrencyImage(imageView, textView, token, blockchain) setOfflineCurrencyImage(imageView, textView, token, blockchain)
@ -70,36 +98,43 @@ fun Picasso.loadCurrenciesIcon(
private const val QCX = "QCX" private const val QCX = "QCX"
private const val VOYR = "VOYRME" private const val VOYR = "VOYRME"
private fun setOfflineCurrencyImage( private fun Picasso.setOfflineCurrencyImage(
imageView: ImageFilterView, imageView: ImageFilterView,
textView: TextView, textView: TextView,
token: Token?, token: Token?,
blockchain: Blockchain, blockchain: Blockchain,
) { ) {
when (token) { if (token == null) {
null -> setBlockchainImage(imageView, textView, blockchain) setBlockchainImage(imageView, textView, blockchain)
else -> setTokenImage(imageView, textView, token, blockchain) } else {
setTokenImage(imageView, textView, token, blockchain)
} }
} }
private fun setBlockchainImage( private fun Picasso.setBlockchainImage(
imageView: ImageFilterView, imageView: ImageFilterView,
textView: TextView, textView: TextView,
blockchain: Blockchain, blockchain: Blockchain,
) { ) {
imageView.setImageResource(blockchain.getRoundIconRes()) this
.load(blockchain.getRoundIconRes())
.into(imageView)
imageView.colorFilter = null imageView.colorFilter = null
if (blockchain.isTestnet()) imageView.saturation = 0f if (blockchain.isTestnet()) imageView.saturation = 0f
textView.text = null textView.text = null
} }
private fun setTokenImage( private fun Picasso.setTokenImage(
imageView: ImageFilterView, imageView: ImageFilterView,
textView: TextView, textView: TextView,
token: Token, token: Token,
tokenBlockchain: Blockchain tokenBlockchain: Blockchain
) { ) {
imageView.setImageResource(R.drawable.shape_circle) this
.load(R.drawable.shape_circle)
.into(imageView)
if (tokenBlockchain.isTestnet()) { if (tokenBlockchain.isTestnet()) {
imageView.saturation = 0f imageView.saturation = 0f
} else { } else {

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.graphics.Color
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.graphics.luminance
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Token
import com.tangem.wallet.R import com.tangem.wallet.R
@ -9,8 +11,13 @@ import com.tangem.wallet.R
fun Token.getColor(): Int { fun Token.getColor(): Int {
return try { return try {
("#" + this.contractAddress.subSequence(2..7).toString()) ("#" + this.contractAddress.subSequence(2..7).toString())
.toColorInt() .toColorInt()
} catch (exception: Exception) { } catch (exception: Exception) {
R.color.lightGray4 R.color.lightGray4
} }
}
@ColorInt
fun Token.getTextColor(): Int {
return if (this.getColor().luminance > 0.5) Color.BLACK else Color.WHITE
} }

View file

@ -0,0 +1,50 @@
package com.tangem.tap.common.images
import android.content.Context
import android.util.Log
import coil.ImageLoader
import coil.util.Logger
import com.tangem.tap.logConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
private const val COIL_LOG_TAG = "COIL"
fun createCoilImageLoader(
context: Context
): ImageLoader {
return ImageLoader.Builder(context)
.apply {
if (!logConfig.coil) return@apply
logger(CoilTimberLogger())
okHttpClient {
OkHttpClient.Builder()
.addNetworkInterceptor(
HttpLoggingInterceptor { message ->
Timber.tag(COIL_LOG_TAG).d(message)
}
.apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}
}
.build()
}
private class CoilTimberLogger : Logger {
override var level: Int = Log.DEBUG
override fun log(tag: String, priority: Int, message: String?, throwable: Throwable?) {
with(Timber.tag(COIL_LOG_TAG)) {
throwable?.let { e -> e(e, message) }
message?.let { msg -> d(msg) }
}
}
}

View file

@ -1,7 +1,11 @@
package com.tangem.tap.features.wallet.ui package com.tangem.tap.features.wallet.ui
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
@ -11,20 +15,32 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions import com.tangem.tap.common.TestActions
import com.tangem.tap.common.extensions.* import com.tangem.tap.common.extensions.appendIfNotNull
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.onboarding.getQRReceiveMessage
import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ErrorType
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.images.loadCurrencyIcon
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
@ -199,9 +215,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
} }
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) { private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
Picasso.get().loadCurrenciesIcon( loadCurrencyIcon(
imageView = ivCurrency, currencyImageView = ivCurrency,
textView = tvTokenLetter, currencyTextView = tvTokenLetter,
blockchain = wallet.currency.blockchain, blockchain = wallet.currency.blockchain,
token = (wallet.currency as? Currency.Token)?.token token = (wallet.currency as? Currency.Token)?.token
) )

View file

@ -6,18 +6,17 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.images.loadCurrencyIcon
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@ -98,9 +97,9 @@ class WalletAdapter
lContent.root.show() lContent.root.show()
} }
Picasso.get().loadCurrenciesIcon( loadCurrencyIcon(
imageView = ivCurrency, currencyImageView = ivCurrency,
textView = tvTokenLetter, currencyTextView = tvTokenLetter,
token = (wallet.currency as? Currency.Token)?.token, token = (wallet.currency as? Currency.Token)?.token,
blockchain = wallet.currency.blockchain, blockchain = wallet.currency.blockchain,
) )

View file

@ -0,0 +1,109 @@
package com.tangem.tap.features.wallet.ui.images
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import coil.imageLoader
import coil.load
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.common.extensions.getTextColor
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.tokens.getIconUrl
import com.tangem.wallet.R
private const val QCX = "QCX"
private const val VOYR = "VOYRME"
fun loadCurrencyIcon(
currencyImageView: ImageFilterView,
currencyTextView: TextView,
token: Token?,
blockchain: Blockchain,
) {
when {
token == null -> currencyImageView.loadIcon(
iconUrl = getIconUrl(blockchain.toNetworkId()),
placeholderRes = blockchain.getRoundIconRes(),
onStart = {
if (blockchain.isTestnet()) {
currencyImageView.saturation = 0f
} else {
currencyImageView.colorFilter = null
}
}
)
token.symbol == QCX -> currencyImageView.load(R.drawable.ic_qcx)
token.symbol == VOYR -> currencyImageView.load(R.drawable.ic_voyr)
else -> currencyImageView.loadIcon(
iconUrl = getTokenIconUrl(token, blockchain),
placeholderRes = R.drawable.shape_circle,
onStart = {
currencyTextView.text = token.symbol.take(1)
currencyTextView.setTextColor(token.getTextColor())
if (blockchain.isTestnet()) {
currencyImageView.saturation = 0f
}
},
onError = {
currencyImageView.colorFilter = PorterDuffColorFilter(
/* color = */
token.getColor(),
/* mode = */
PorterDuff.Mode.SRC_ATOP,
)
},
onSuccess = {
if (!blockchain.isTestnet()) {
currencyImageView.colorFilter = null
}
}
)
}
}
private inline fun ImageView.loadIcon(
iconUrl: String?,
placeholderRes: Int,
crossinline onStart: () -> Unit = {},
crossinline onSuccess: () -> Unit = {},
crossinline onError: () -> Unit = {},
) {
ImageRequest.Builder(context)
.data(iconUrl)
.placeholder(placeholderRes)
.error(placeholderRes)
.fallback(placeholderRes)
.transformations(
RoundedCornersTransformation(
topLeft = 32f,
topRight = 32f,
bottomLeft = 32f,
bottomRight = 32f
)
)
.listener(
onStart = { onStart() },
onSuccess = { _, _ -> onSuccess() },
onError = { _, _ -> onError() }
)
.target(imageView = this)
.build()
.also(context.imageLoader::enqueue)
}
private fun getTokenIconUrl(token: Token, blockchain: Blockchain): String? {
return token.id?.let(::getIconUrl)
?: token.getCustomIconUrl()
?: IconsUtil.getTokenIconUri(blockchain, token)
?.toString()
}