From 18bd8ed03bff4ba3edae14d7e953cf9467096b2c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Jun 2022 16:58:02 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 7 +- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 18 +-- .../tangem/tap/common/extensions/Picasso.kt | 55 +++++++-- .../com/tangem/tap/common/extensions/Token.kt | 9 +- .../java/com/tangem/tap/common/images/Coil.kt | 50 ++++++++ .../wallet/ui/WalletDetailsFragment.kt | 30 +++-- .../wallet/ui/adapters/WalletAdapter.kt | 9 +- .../wallet/ui/images/CurrencyIconRequest.kt | 109 ++++++++++++++++++ 9 files changed, 252 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/images/Coil.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt diff --git a/app/build.gradle b/app/build.gradle index 51067545d6..c9ed7b48bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 985642c135..7f058ec409 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 985642c135606ba1f59fd739127aec03024da79b +Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564 diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b58485ab88..2e6c7b534b 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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, - ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt index dbeefadbe8..95344da7a6 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt index fdfb83ef77..10897bc069 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt new file mode 100644 index 0000000000..64b7b76734 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt @@ -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) } + } + } +} \ No newline at end of file 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 00b0fb8d78..997c042f15 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 @@ -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 ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index f786bbe4c3..26f5b8da86 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt new file mode 100644 index 0000000000..99051bc72d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -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() +} \ No newline at end of file