Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-22 12:29:03 +03:00
parent 9a14c11e33
commit c55e5745e9
11 changed files with 90 additions and 86 deletions

View file

@ -91,9 +91,12 @@ dependencies {
// Camera
implementation 'com.otaliastudios:cameraview:2.6.3'
// Image Loading - Picasso
implementation 'com.squareup.picasso:picasso:2.71828'
testImplementation 'junit:junit:4.13'
testImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap
import android.app.Application
import com.tangem.tap.common.images.PicassoHelper
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.network.NetworkConnectivity
@ -24,5 +25,6 @@ class TapApplication : Application() {
}
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
PicassoHelper.initPicassoWithCaching(this)
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.tap.common.images
import android.app.Application
import com.squareup.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient
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.setSingletonInstance(picasso)
}
private fun getOkHttpForPicasso(application: Application): OkHttpClient {
val okHttpBuilder = OkHttpClient.Builder()
okHttpBuilder.cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE))
okHttpBuilder.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)
}
return okHttpBuilder.build()
}
}
}

View file

@ -2,17 +2,14 @@ package com.tangem.tap.domain
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.TangemService
import com.tangem.common.extensions.toHexString
import com.tangem.tap.TapConfig
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.redux.PayIdState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
@ -26,7 +23,6 @@ import java.math.BigDecimal
class TapWalletManager {
private val payIdManager = PayIdManager()
private val coinMarketCapService = CoinMarketCapService()
private val tangemService = TangemService()
suspend fun loadWalletData() {
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
@ -37,21 +33,6 @@ class TapWalletManager {
updateWallet(walletManager)
}
suspend fun loadArtwork(card: Card, artworkId: String) {
val result =
tangemService.getArtwork(card.cardId, card.cardPublicKey!!.toHexString(), artworkId)
withContext(Dispatchers.Main) {
when (result) {
is Result.Success -> {
store.dispatch(WalletAction.LoadArtwork.Success(
Artwork(artworkId, result.data.byteStream().readBytes())
))
}
is Result.Failure -> store.dispatch(WalletAction.LoadArtwork.Failure)
}
}
}
suspend fun loadPayId() {
val result = loadPayIdIfNeeded()
result?.let { handlePayIdResult(it) }

View file

@ -19,7 +19,6 @@ import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -46,21 +45,6 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
}
}
is WalletAction.LoadArtwork -> {
if (action.artworkId != null) {
scope.launch {
store.state.globalState.tapWalletManager.loadArtwork(
action.card, action.artworkId
)
}
} else {
val artworkRes = R.drawable.card_default
store.dispatch(WalletAction.LoadArtwork.Success(Artwork(
artworkId = artworkRes.toString(), artworkResId = artworkRes
)))
}
}
is WalletAction.CreateWallet -> {
scope.launch {
val result = tangemSdkManager.createWallet(

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux
import com.tangem.blockchain.common.AmountType
import com.tangem.common.extensions.isZero
import com.tangem.common.extensions.toHexString
import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedString
@ -12,6 +13,8 @@ import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.features.wallet.ui.TokenData
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.Action
class WalletReducer {
@ -165,8 +168,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount)
))
}
is WalletAction.LoadArtwork.Success -> {
newState = newState.copy(cardImage = action.artwork)
is WalletAction.LoadArtwork -> {
// TODO: form URL in Card SDK
var artworkAddress = "https://verify.tangem.com/card/artwork"
artworkAddress += "?artworkId=${action.artworkId}"
artworkAddress += "&CID=${store.state.globalState.scanNoteResponse?.card?.cardId}"
artworkAddress += "&publicKey=${store.state.globalState.scanNoteResponse?.card?.cardPublicKey?.toHexString()}"
val artwork = if (action.artworkId != null) {
Artwork(artworkId = artworkAddress)
} else {
Artwork(artworkResId = R.drawable.card_default)
}
newState = newState.copy(cardImage = artwork)
}
is WalletAction.ShowQrCode -> {
newState = newState.copy(

View file

@ -62,7 +62,7 @@ data class AddressData(
)
data class Artwork(
val artworkId: String,
val artworkId: String? = null,
val artwork: Bitmap? = null,
val artworkResId: Int? = null
) {

View file

@ -85,6 +85,7 @@ class BalanceWidget(
fragment.tv_status_error_message.text = data.errorMessage
showStatus(R.id.group_error)
fragment.tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
}
BalanceStatus.EmptyCard -> {
fragment.l_balance.hide()

View file

@ -35,7 +35,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
private var dialog: Dialog? = null
private var snackbar: Snackbar? = null
private var artworkId: String? = null
private lateinit var viewAdapter: PendingTransactionsAdapter
@ -187,25 +186,15 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
private fun setupCardImage(cardImage: Artwork?) {
if (cardImage?.artwork != null) {
if (cardImage.artworkId != this.artworkId) {
prepareShowingCardImage(cardImage.artworkId)
iv_card.setImageBitmap(cardImage.artwork)
}
} else if (cardImage?.artworkResId != null) {
if (cardImage.artworkId != this.artworkId) {
prepareShowingCardImage(cardImage.artworkId)
iv_card.setImageDrawable(getDrawable(cardImage.artworkResId))
}
val address = cardImage?.artworkId ?: cardImage?.artworkResId
val picassoRequest = when (address) {
is String -> Picasso.get().load(address)
is Int -> Picasso.get().load(address)
else -> null
}
}
private fun prepareShowingCardImage(artworkId: String) {
this.artworkId = artworkId
val fadeInAnimation: Animation = AlphaAnimation(0.0f, 1.0f)
fadeInAnimation.duration = 400
iv_card.startAnimation(fadeInAnimation)
iv_card_tangem_logo.hide()
picassoRequest?.placeholder(R.drawable.card_placeholder)
?.error(R.drawable.card_placeholder)
?.into(iv_card)
}
private fun handleDialogs(walletDialog: WalletDialog?) {

View file

@ -1,19 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners
android:radius="12dp"
/>
<size
android:height="174dp"
android:width="328dp"/>
<gradient
android:startColor="#FFFFFF"
android:endColor="#FFFFFF"
/>
</shape>
<corners android:radius="12dp" />
<size
android:width="328dp"
android:height="174dp" />
<gradient
android:endColor="#FFFFFF"
android:startColor="#FFFFFF" />
</shape>
</item>
<item
android:drawable="@drawable/ic_tangem_pale"
android:gravity="center">
<size
android:width="85dp"
android:height="23dp" />
</item>
</layer-list>

View file

@ -60,17 +60,6 @@
android:scaleType="fitCenter"
android:src="@drawable/card_placeholder"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_card_tangem_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_tangem_pale"
app:layout_constraintStart_toStartOf="@id/iv_card"
app:layout_constraintEnd_toEndOf="@id/iv_card"
app:layout_constraintTop_toTopOf="@id/iv_card"
app:layout_constraintBottom_toBottomOf="@id/iv_card"
android:elevation="6dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pending_transaction"