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
implementation 'com.otaliastudios:cameraview:2.6.3'
// Image Loading - Picasso
// Image Loading
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
implementation platform('com.google.firebase:firebase-bom:26.0.0')
@ -172,9 +174,6 @@ dependencies {
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'
testImplementation 'junit:junit:4.13.2'

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

View file

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

View file

@ -1,6 +1,11 @@
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 androidx.constraintlayout.utils.widget.ImageFilterView
import com.squareup.picasso.Callback
@ -32,6 +37,28 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = 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 {
token?.symbol == QCX -> {
this.load(R.drawable.ic_qcx)?.into(imageView)
@ -59,7 +86,8 @@ fun Picasso.loadCurrenciesIcon(
}
if (blockchain.isTestnet()) imageView.saturation = 0f
}
})
}
)
}
else -> {
setOfflineCurrencyImage(imageView, textView, token, blockchain)
@ -70,36 +98,43 @@ fun Picasso.loadCurrenciesIcon(
private const val QCX = "QCX"
private const val VOYR = "VOYRME"
private fun setOfflineCurrencyImage(
private fun Picasso.setOfflineCurrencyImage(
imageView: ImageFilterView,
textView: TextView,
token: Token?,
blockchain: Blockchain,
) {
when (token) {
null -> setBlockchainImage(imageView, textView, blockchain)
else -> setTokenImage(imageView, textView, token, blockchain)
if (token == null) {
setBlockchainImage(imageView, textView, blockchain)
} else {
setTokenImage(imageView, textView, token, blockchain)
}
}
private fun setBlockchainImage(
private fun Picasso.setBlockchainImage(
imageView: ImageFilterView,
textView: TextView,
blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getRoundIconRes())
this
.load(blockchain.getRoundIconRes())
.into(imageView)
imageView.colorFilter = null
if (blockchain.isTestnet()) imageView.saturation = 0f
textView.text = null
}
private fun setTokenImage(
private fun Picasso.setTokenImage(
imageView: ImageFilterView,
textView: TextView,
token: Token,
tokenBlockchain: Blockchain
) {
imageView.setImageResource(R.drawable.shape_circle)
this
.load(R.drawable.shape_circle)
.into(imageView)
if (tokenBlockchain.isTestnet()) {
imageView.saturation = 0f
} else {

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.extensions
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.graphics.luminance
import androidx.core.graphics.toColorInt
import com.tangem.blockchain.common.Token
import com.tangem.wallet.R
@ -9,8 +11,13 @@ import com.tangem.wallet.R
fun Token.getColor(): Int {
return try {
("#" + this.contractAddress.subSequence(2..7).toString())
.toColorInt()
.toColorInt()
} catch (exception: Exception) {
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
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 androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorRes
@ -11,20 +15,32 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
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.redux.navigation.NavigationAction
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.getQRReceiveMessage
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.ui.adapters.PendingTransactionsAdapter
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.store
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) {
Picasso.get().loadCurrenciesIcon(
imageView = ivCurrency,
textView = tvTokenLetter,
loadCurrencyIcon(
currencyImageView = ivCurrency,
currencyTextView = tvTokenLetter,
blockchain = wallet.currency.blockchain,
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.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getString
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.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
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.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@ -98,9 +97,9 @@ class WalletAdapter
lContent.root.show()
}
Picasso.get().loadCurrenciesIcon(
imageView = ivCurrency,
textView = tvTokenLetter,
loadCurrencyIcon(
currencyImageView = ivCurrency,
currencyTextView = tvTokenLetter,
token = (wallet.currency as? Currency.Token)?.token,
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()
}