Updated on 2026-08-14

This commit is contained in:
Tangem 2022-06-21 16:24:26 +03:00
parent 18bd8ed03b
commit b5c1909961
10 changed files with 54 additions and 328 deletions

View file

@ -119,7 +119,6 @@ dependencies {
implementation 'com.otaliastudios:cameraview:2.6.3'
// 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'

View file

@ -1,170 +0,0 @@
package com.tangem.tap.common.extensions
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
import com.squareup.picasso.Picasso
import com.squareup.picasso.Transformation
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.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.tokens.getIconUrl
import com.tangem.wallet.R
fun Picasso.loadCurrenciesIcon(
imageView: ImageFilterView,
textView: TextView,
token: Token? = null,
blockchain: Blockchain,
) {
val url: String? = if (token != null) {
token.id?.let { getIconUrl(it) } ?: token.getCustomIconUrl()
?: IconsUtil.getTokenIconUri(blockchain, token)?.toString()
} else {
getIconUrl(blockchain.toNetworkId())
}
imageView.setImageDrawable(null)
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)
}
token?.symbol == VOYR -> {
this.load(R.drawable.ic_voyr)?.into(imageView)
}
url != 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
}
}
)
}
else -> {
setOfflineCurrencyImage(imageView, textView, token, blockchain)
}
}
}
private const val QCX = "QCX"
private const val VOYR = "VOYRME"
private fun Picasso.setOfflineCurrencyImage(
imageView: ImageFilterView,
textView: TextView,
token: Token?,
blockchain: Blockchain,
) {
if (token == null) {
setBlockchainImage(imageView, textView, blockchain)
} else {
setTokenImage(imageView, textView, token, blockchain)
}
}
private fun Picasso.setBlockchainImage(
imageView: ImageFilterView,
textView: TextView,
blockchain: Blockchain,
) {
this
.load(blockchain.getRoundIconRes())
.into(imageView)
imageView.colorFilter = null
if (blockchain.isTestnet()) imageView.saturation = 0f
textView.text = null
}
private fun Picasso.setTokenImage(
imageView: ImageFilterView,
textView: TextView,
token: Token,
tokenBlockchain: Blockchain
) {
this
.load(R.drawable.shape_circle)
.into(imageView)
if (tokenBlockchain.isTestnet()) {
imageView.saturation = 0f
} else {
imageView.setColorFilter(token.getColor())
}
textView.text = token.symbol.take(1)
}
private class RoundedCornersTransform : Transformation {
override fun transform(source: Bitmap): Bitmap {
val size = source.width.coerceAtMost(source.height)
val x = (source.width - size) / 2
val y = (source.height - size) / 2
val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
if (squaredBitmap != source) source.recycle()
val paint = Paint().apply {
shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
isAntiAlias = true
}
val rectF = RectF(0f, 0f, source.width.toFloat(), source.height.toFloat())
val radius = size / 8f
val bitmap = Bitmap.createBitmap(size, size, source.config)
Canvas(bitmap).drawRoundRect(rectF, radius, radius, paint)
squaredBitmap.recycle()
return bitmap
}
override fun key(): String = "rounded_corners"
}

View file

@ -1,68 +0,0 @@
package com.tangem.tap.common.images
import android.app.Application
import com.squareup.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
import com.tangem.tap.logConfig
import com.tangem.wallet.BuildConfig
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.io.File
import java.util.concurrent.TimeUnit
class PicassoHelper {
companion object {
private const val KEEP_CACHE_MAX_DAYS = 7
fun initPicassoWithCaching(application: Application) {
val picasso = Picasso.Builder(application)
.downloader(OkHttp3Downloader(getOkHttpForPicasso(application)))
.build()
picasso.isLoggingEnabled = logIsEnabled()
picasso.setIndicatorsEnabled(BuildConfig.DEBUG)
Picasso.setSingletonInstance(picasso)
}
private fun getOkHttpForPicasso(application: Application): OkHttpClient {
return OkHttpClient.Builder().apply {
cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE))
callTimeout(15000, TimeUnit.MILLISECONDS)
if (logIsEnabled()) {
addDebugInterceptors(this)
}
addInterceptor { chain ->
val cacheControl = CacheControl.Builder()
.maxStale(KEEP_CACHE_MAX_DAYS, TimeUnit.DAYS)
.build()
val origRequest = chain.request()
val neverExpireRequest = origRequest.newBuilder()
.cacheControl(cacheControl)
.build()
chain.proceed(neverExpireRequest)
}
}.build()
}
private fun addDebugInterceptors(okHttpBuilder: OkHttpClient.Builder) {
val picassoInterceptor = HttpLoggingInterceptor(PicassoOkHttpLogger()).apply {
level = HttpLoggingInterceptor.Level.BODY
}
okHttpBuilder.addInterceptor(picassoInterceptor)
}
private fun logIsEnabled(): Boolean = BuildConfig.DEBUG && logConfig.picasso
}
}
private class PicassoOkHttpLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Timber.d(message)
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.tap.domain
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
import com.tangem.common.services.Result
/**
[REDACTED_AUTHOR]
*/
class UrlBitmapLoader {
private val mainHandler = Handler(Looper.getMainLooper())
fun loadBitmap(url: String, callback: (Result<Bitmap>) -> Unit) {
val target = DownloadTarget(callback)
protectedFromGarbageCollectorTargets.add(target)
mainHandler.post { Picasso.get().load(url).into(target) }
}
fun loadBitmap(url: String, target: DownloadTarget) {
protectedFromGarbageCollectorTargets.add(target)
Picasso.get().load(url).into(target)
}
}
private val protectedFromGarbageCollectorTargets = mutableListOf<Target>()
// It adds the ability to trigger multiple downloads with a unique callback.
open class DownloadTarget(
val callback: (Result<Bitmap>) -> Unit,
) : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
callback(Result.Success(bitmap))
protectedFromGarbageCollectorTargets.remove(this)
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
callback.invoke(Result.Failure(e ?: Exception("Unknown exception")))
protectedFromGarbageCollectorTargets.remove(this)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
}
}

View file

@ -10,7 +10,7 @@ import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.TransitionManager
import com.squareup.picasso.Picasso
import coil.load
import com.tangem.blockchain.common.Blockchain
import com.tangem.tangem_sdk_new.extensions.fadeIn
import com.tangem.tangem_sdk_new.extensions.fadeOut
@ -67,11 +67,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
override fun newState(state: OnboardingNoteState) {
if (activity == null || view == null) return
Picasso.get()
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvFrontCard)
binding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
placeholder(R.drawable.card_placeholder_black)
error(R.drawable.card_placeholder_black)
fallback(R.drawable.card_placeholder_black)
}
pbBinding.pbState.max = state.steps.size - 1
pbBinding.pbState.progress = state.progress

View file

@ -7,7 +7,7 @@ import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.TransitionManager
import com.squareup.picasso.Picasso
import coil.load
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
@ -54,11 +54,11 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
if (activity == null || view == null) return
if (state.currentStep == OnboardingOtherCardsStep.None) return
Picasso.get()
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvFrontCard)
binding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
placeholder(R.drawable.card_placeholder_black)
error(R.drawable.card_placeholder_black)
fallback(R.drawable.card_placeholder_black)
}
pbBinding.pbState.max = state.steps.size - 1
pbBinding.pbState.progress = state.progress

View file

@ -9,13 +9,18 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.TransitionInflater
import androidx.transition.TransitionManager
import com.squareup.picasso.Picasso
import coil.load
import com.tangem.Message
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.redux.navigation.ShareElement
@ -75,24 +80,28 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
val deviceScaleFactorForWelcomeState = typedValue.float
twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) {
twinsWidget = TwinsCardWidget(
LeapfrogWidget(binding.onboardingTopContainer.cardsContainer),
deviceScaleFactorForWelcomeState
) {
285f * deviceScaleFactorForWelcomeState
}
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
btnRefreshBalanceWidget =
RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
Picasso.get()
.load(Artwork.TWIN_CARD_1)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvTwinFrontCard)
binding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) {
placeholder(R.drawable.card_placeholder_black)
error(R.drawable.card_placeholder_black)
fallback(R.drawable.card_placeholder_black)
}
Picasso.get()
.load(Artwork.TWIN_CARD_2)
.error(R.drawable.card_placeholder_white)
.placeholder(R.drawable.card_placeholder_white)
?.into(binding.onboardingTopContainer.imvTwinBackCard)
binding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) {
placeholder(R.drawable.card_placeholder_white)
error(R.drawable.card_placeholder_white)
fallback(R.drawable.card_placeholder_white)
}
}
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =

View file

@ -11,9 +11,9 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import androidx.transition.TransitionManager
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayoutMediator
import com.squareup.picasso.Picasso
import com.tangem.common.CardIdFormatter
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
@ -22,7 +22,12 @@ import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.FragmentOnBackPressedHandler
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.products.wallet.redux.*
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.store
import com.tangem.wallet.R
@ -124,11 +129,11 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
private fun loadImageIntoImageView(url: String?, view: ImageView) {
Picasso.get()
.load(url)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(view)
view.load(url) {
placeholder(R.drawable.card_placeholder_black)
error(R.drawable.card_placeholder_black)
fallback(R.drawable.card_placeholder_black)
}
}
private fun setupCreateWalletState() = with(binding) {

View file

@ -12,7 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import coil.load
import com.tangem.tap.MainActivity
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@ -165,11 +165,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
private fun setupCardImage(cardImage: Artwork?) {
Picasso.get()
.load(cardImage?.artworkId)
.placeholder(R.drawable.card_placeholder_black)
?.error(R.drawable.card_placeholder_black)
?.into(binding.ivCard)
binding.ivCard.load(cardImage?.artworkId) {
placeholder(R.drawable.card_placeholder_black)
error(R.drawable.card_placeholder_black)
fallback(R.drawable.card_placeholder_black)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View file

@ -13,4 +13,4 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
org.gradle.parallel=true
org.gradle.daemon=true
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true