Updated on 2026-08-14
|
|
@ -68,10 +68,12 @@ dependencies {
|
|||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
|
||||
implementation 'com.tangem:blockchain:1.141.0'
|
||||
// implementation 'com.tangem:blockchain:1.141.0'
|
||||
implementation 'blockchain-sdk-kotlin:blockchain:tangem-20210302.084810-3'
|
||||
implementation 'com.tangem:core:1.101.0'
|
||||
implementation 'com.tangem:sdk:1.101.0'
|
||||
|
||||
|
|
@ -79,10 +81,10 @@ dependencies {
|
|||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
||||
//lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
|
||||
|
||||
implementation "org.rekotlin:rekotlin:1.0.4"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
|
|||
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
|
||||
import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
|
|
@ -26,6 +27,7 @@ val store = Store(
|
|||
state = AppState()
|
||||
)
|
||||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var currenciesRepository: CurrenciesRepository
|
||||
|
||||
class TapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
|
|
@ -45,6 +47,7 @@ class TapApplication : Application() {
|
|||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
PicassoHelper.initPicassoWithCaching(this)
|
||||
currenciesRepository = CurrenciesRepository(this)
|
||||
|
||||
loadConfigs()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.res.AssetManager
|
||||
|
||||
fun AssetManager.readJsonFileToString(fileName: String): String =
|
||||
this.open("$fileName.json").bufferedReader().readText()
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Unknown, Blockchain.Ducatus, Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet,
|
||||
Blockchain.BinanceTestnet -> 1
|
||||
Blockchain.Bitcoin -> R.drawable.ic_btc
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_btc_cash
|
||||
Blockchain.Litecoin -> R.drawable.ic_ltc
|
||||
Blockchain.Ethereum -> R.drawable.ic_eth
|
||||
Blockchain.RSK -> R.drawable.ic_rsk
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano
|
||||
Blockchain.Binance -> R.drawable.ic_binance
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos
|
||||
Blockchain.XRP -> R.drawable.ic_xrp
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment
|
|||
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
|
||||
import com.tangem.tap.features.home.HomeFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -46,5 +48,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
|
||||
AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
|
||||
AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
|
||||
AppScreen.AddTokens -> AddTokensFragment()
|
||||
AppScreen.WalletDetails -> WalletDetailsFragment()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.network.coinmarketcap.FiatCurrency
|
||||
|
|
@ -15,27 +9,6 @@ import java.text.DecimalFormat
|
|||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
|
||||
fun String.toQrCode(): Bitmap {
|
||||
val hintMap = Hashtable<EncodeHintType, Any>()
|
||||
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
|
||||
hintMap[EncodeHintType.MARGIN] = 2
|
||||
|
||||
val qrCodeWriter = QRCodeWriter()
|
||||
|
||||
val size = 256
|
||||
|
||||
val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
|
||||
val width = bitMatrix.width
|
||||
val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until width) {
|
||||
bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
|
||||
}
|
||||
}
|
||||
return bmp
|
||||
}
|
||||
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
|
||||
): String {
|
||||
|
|
|
|||
62
app/src/main/java/com/tangem/tap/common/extensions/String.kt
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import java.util.*
|
||||
|
||||
fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
|
||||
if (this.isNullOrBlank()) return ""
|
||||
val size = this.length
|
||||
val sizeDifference = size - allowedSize
|
||||
val endIndex = this.indexOf(" ")
|
||||
val startIndex = endIndex - sizeDifference
|
||||
val newString = this.removeRange(startIndex, endIndex)
|
||||
return newString.substring(0 until startIndex) + "..." +
|
||||
newString.substring(startIndex until newString.length)
|
||||
}
|
||||
|
||||
fun String.colorSegment(
|
||||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toQrCode(): Bitmap {
|
||||
val hintMap = Hashtable<EncodeHintType, Any>()
|
||||
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
|
||||
hintMap[EncodeHintType.MARGIN] = 2
|
||||
|
||||
val qrCodeWriter = QRCodeWriter()
|
||||
|
||||
val size = 256
|
||||
|
||||
val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
|
||||
val width = bitMatrix.width
|
||||
val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until width) {
|
||||
bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
|
||||
}
|
||||
}
|
||||
return bmp
|
||||
}
|
||||
|
|
@ -56,4 +56,13 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
|
|||
error = null
|
||||
isErrorEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.isEllipsized(): Boolean {
|
||||
val layout = this.layout
|
||||
if (layout != null) {
|
||||
val lines: Int = layout.lineCount
|
||||
return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
11
app/src/main/java/com/tangem/tap/common/extensions/Token.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
@ColorInt
|
||||
fun Token.getColor(): Int {
|
||||
return ("#" + this.contractAddress.subSequence(2..7).toString())
|
||||
.toColorInt()
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ import android.content.Intent
|
|||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -19,7 +17,6 @@ import androidx.annotation.ColorRes
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
|
|
@ -106,24 +103,6 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fun String.colorSegment(
|
||||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.tap.features.details.redux.DetailsReducer
|
|||
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
||||
import com.tangem.tap.features.tokens.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.WalletReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -20,7 +21,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
walletState = WalletReducer.reduce(action, state),
|
||||
sendState = SendScreenReducer.reduce(action, state.sendState),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
disclaimerState = DisclaimerReducer.reduce(action, state)
|
||||
disclaimerState = DisclaimerReducer.reduce(action, state),
|
||||
tokensState = TokensReducer.reduce(action, state),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ import com.tangem.tap.features.home.redux.HomeMiddleware
|
|||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.send.redux.middlewares.sendMiddleware
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.wallet.redux.WalletMiddleware
|
||||
import com.tangem.tap.features.tokens.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -24,7 +26,8 @@ data class AppState(
|
|||
val walletState: WalletState = WalletState(),
|
||||
val sendState: SendState = SendState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val disclaimerState: DisclaimerState = DisclaimerState()
|
||||
val disclaimerState: DisclaimerState = DisclaimerState(),
|
||||
val tokensState: TokensState = TokensState(),
|
||||
) : StateType {
|
||||
|
||||
companion object {
|
||||
|
|
@ -35,7 +38,8 @@ data class AppState(
|
|||
WalletMiddleware().walletMiddleware,
|
||||
sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
TokensMiddleware().tokensMiddleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,10 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
||||
data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
|
||||
data class SetFiatRate(
|
||||
val fiatRates: Pair<CryptoCurrencyName, BigDecimal>
|
||||
) : GlobalAction()
|
||||
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
object RestoreAppCurrency : GlobalAction() {
|
||||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
if (it.hideWarning(action.warning)) {
|
||||
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
|
||||
//TODO: No appropriate warningMessage identification. Make it better later
|
||||
store.dispatch(WalletAction.SaveCardId)
|
||||
store.dispatch(WalletAction.CheckSignedHashes.SaveCardId)
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.SetWarnings(it.getWarnings(WarningMessage.Location.MainScreen)))
|
||||
|
|
|
|||
|
|
@ -13,16 +13,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
return when (action) {
|
||||
is GlobalAction.SaveScanNoteResponse ->
|
||||
globalState.copy(scanNoteResponse = action.scanNoteResponse)
|
||||
is GlobalAction.SetFiatRate -> {
|
||||
val rates = globalState.conversionRates.rates.toMutableMap()
|
||||
rates[action.fiatRates.first] = action.fiatRates.second
|
||||
globalState.copy(conversionRates = ConversionRates(rates))
|
||||
}
|
||||
is GlobalAction.ChangeAppCurrency -> {
|
||||
globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
globalState.copy(appCurrency = action.appCurrency)
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency.Success -> {
|
||||
globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
globalState.copy(appCurrency = action.appCurrency)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanNoteResponse?.card?.copy(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class GlobalState(
|
||||
val scanNoteResponse: ScanNoteResponse? = null,
|
||||
|
|
@ -17,21 +16,11 @@ data class GlobalState(
|
|||
val payIdManager: PayIdManager = PayIdManager(),
|
||||
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
|
||||
val tangemService: TangemService = TangemService(),
|
||||
val conversionRates: ConversionRates = ConversionRates(emptyMap()),
|
||||
val configManager: ConfigManager? = null,
|
||||
val warningManager: WarningMessagesManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
|
||||
) : StateType
|
||||
|
||||
data class ConversionRates(
|
||||
val rates: Map<CryptoCurrencyName, BigDecimal>,
|
||||
) {
|
||||
|
||||
fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? {
|
||||
return rates[cryptoCurrency]
|
||||
}
|
||||
}
|
||||
|
||||
typealias CryptoCurrencyName = String
|
||||
typealias FiatCurrencyName = String
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ data class NavigationState(
|
|||
) : StateType
|
||||
|
||||
enum class AppScreen {
|
||||
Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
|
||||
CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding
|
||||
Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
|
||||
CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens
|
||||
}
|
||||
|
|
@ -2,46 +2,13 @@ package com.tangem.tap.domain
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.tap.network.payid.PayIdService
|
||||
import com.tangem.tap.network.payid.PayIdVerifyService
|
||||
import com.tangem.tap.network.payid.SetPayIdResponse
|
||||
import com.tangem.tap.network.payid.VerifyPayIdResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.HttpException
|
||||
import java.util.*
|
||||
|
||||
class PayIdManager {
|
||||
private val payIdService = PayIdService()
|
||||
|
||||
suspend fun getPayId(cardId: String, publicKey: String): Result<String?> = withContext(Dispatchers.IO) {
|
||||
val result = payIdService.getPayId(cardId, publicKey)
|
||||
when (result) {
|
||||
is Result.Success -> return@withContext Result.Success(result.data.payId)
|
||||
is Result.Failure -> {
|
||||
(result.error as? HttpException)?.let {
|
||||
if (it.code() == 404) return@withContext Result.Success(null)
|
||||
}
|
||||
return@withContext result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun setPayId(
|
||||
cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain
|
||||
): Result<SetPayIdResponse> = withContext(Dispatchers.IO) {
|
||||
val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork())
|
||||
when (result) {
|
||||
is Result.Success -> return@withContext result
|
||||
is Result.Failure -> {
|
||||
(result.error as? HttpException)?.let {
|
||||
if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated)
|
||||
}
|
||||
return@withContext result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result<VerifyPayIdResponse> = withContext(Dispatchers.IO) {
|
||||
val splitPayId = payId.split("\$")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
class TapBlockchainConfig {
|
||||
companion object {
|
||||
val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
|
||||
val blockcypherTokens: Set<String> = setOf(
|
||||
"aa8184b0e0894b88a5688e01b3dc1e82",
|
||||
"56c4ca23c6484c8f8864c32fde4def8d",
|
||||
"66a8a37c5e9d4d2c9bb191acfe7f93aa"
|
||||
)
|
||||
val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,22 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
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.currenciesRepository
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.domain.extensions.isNoAccountError
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
|
|
@ -28,29 +27,25 @@ import java.math.BigDecimal
|
|||
|
||||
|
||||
class TapWalletManager {
|
||||
private val payIdManager = PayIdManager()
|
||||
private val coinMarketCapService = CoinMarketCapService()
|
||||
|
||||
suspend fun loadWalletData() {
|
||||
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
|
||||
if (walletManager == null) {
|
||||
store.dispatch(WalletAction.LoadWallet.Failure())
|
||||
return
|
||||
private val blockchainSdkConfig by lazy {
|
||||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
}
|
||||
private val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun loadWalletData(walletManager: WalletManager) {
|
||||
val result = try {
|
||||
walletManager.update()
|
||||
Result.Success(walletManager.wallet)
|
||||
} catch (exception: Exception) {
|
||||
Result.Failure(exception)
|
||||
}
|
||||
loadWallet(walletManager)
|
||||
handleUpdateWalletResult(result, walletManager)
|
||||
}
|
||||
|
||||
suspend fun loadPayId() {
|
||||
val result = loadPayIdIfNeeded()
|
||||
result?.let { handlePayIdResult(it) }
|
||||
}
|
||||
|
||||
suspend fun updateWallet() {
|
||||
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
|
||||
if (walletManager == null) {
|
||||
withContext(Dispatchers.Main) { store.dispatch(WalletAction.UpdateWallet.Failure()) }
|
||||
return
|
||||
}
|
||||
suspend fun updateWallet(walletManager: WalletManager) {
|
||||
val result = try {
|
||||
walletManager.update()
|
||||
Result.Success(walletManager.wallet)
|
||||
|
|
@ -68,12 +63,18 @@ class TapWalletManager {
|
|||
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet ?: return
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
|
||||
val currencyList = wallet.getTokens().map { it.symbol }.toMutableList()
|
||||
currencyList.add(wallet.blockchain.currency)
|
||||
loadFiatRate(fiatCurrency, currencyList)
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, cryptoCurrencyName: CryptoCurrencyName) {
|
||||
val currencyList = listOf(cryptoCurrencyName)
|
||||
loadFiatRate(fiatCurrency, currencyList)
|
||||
}
|
||||
|
||||
private suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencyList: List<CryptoCurrencyName>) {
|
||||
val results = mutableListOf<Pair<CryptoCurrencyName, Result<BigDecimal>?>>()
|
||||
currencyList.forEach { results.add(it to coinMarketCapService.getRate(it, fiatCurrency)) }
|
||||
handleFiatRatesResult(results)
|
||||
|
|
@ -88,22 +89,20 @@ class TapWalletManager {
|
|||
store.state.globalState.warningManager?.setBlockchain(data.walletManager?.wallet?.blockchain)
|
||||
val configManager = store.state.globalState.configManager
|
||||
if (TapWorkarounds.isStart2Coin) {
|
||||
configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
|
||||
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){
|
||||
configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
|
||||
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
|
||||
if (data.card.isTwinCard()) {
|
||||
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)
|
||||
val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
|
||||
|
|
@ -113,13 +112,17 @@ class TapWalletManager {
|
|||
))
|
||||
}
|
||||
}
|
||||
if (data.walletManager?.wallet?.blockchain == Blockchain.Ethereum ||
|
||||
data.walletManager?.wallet?.blockchain == Blockchain.EthereumTestnet) {
|
||||
store.dispatch(TokensAction.LoadCardTokens)
|
||||
}
|
||||
loadData(data)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadData(data: ScanNoteResponse) {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.CheckIfWarningNeeded)
|
||||
store.dispatch(WalletAction.CheckSignedHashes.CheckIfWarningNeeded)
|
||||
val artworkId = data.verifyResponse?.artworkInfo?.id
|
||||
if (data.walletManager != null) {
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
|
|
@ -128,11 +131,40 @@ class TapWalletManager {
|
|||
}
|
||||
val config = store.state.globalState.configManager?.config ?: return@withContext
|
||||
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id, config.isTopUpEnabled))
|
||||
val primaryWalletManager = data.walletManager
|
||||
val primaryBlockchain = listOf(data.walletManager.wallet.blockchain)
|
||||
val primaryToken = primaryWalletManager.presetTokens.toList()
|
||||
|
||||
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain[0]))
|
||||
if (primaryWalletManager.presetTokens.isNotEmpty()) {
|
||||
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken.first()))
|
||||
}
|
||||
|
||||
if (data.card.isMultiwalletAllowed) {
|
||||
val savedCurrencies = currenciesRepository.loadCardCurrencies(data.card.cardId)
|
||||
val tokens = if (savedCurrencies.tokens.isNotEmpty()) {
|
||||
primaryToken + savedCurrencies.tokens
|
||||
} else {
|
||||
primaryToken
|
||||
}
|
||||
|
||||
val walletManagers = listOf(primaryWalletManager) +
|
||||
currenciesRepository.getBlockchains()
|
||||
.filterNot { it == primaryWalletManager.wallet.blockchain }
|
||||
.mapNotNull { walletManagerFactory.makeWalletManager(data.card, it) }
|
||||
val otherBlockhains = savedCurrencies.blockchains
|
||||
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain + otherBlockhains))
|
||||
store.dispatch(WalletAction.MultiWallet.AddTokens(tokens))
|
||||
} else {
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(listOf(primaryWalletManager)))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain))
|
||||
}
|
||||
store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
|
||||
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
store.dispatch(WalletAction.LoadPayId)
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
|
||||
store.dispatch(WalletAction.EmptyWallet)
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
|
|
@ -143,14 +175,22 @@ class TapWalletManager {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun loadWallet(walletManager: WalletManager) {
|
||||
val result = try {
|
||||
walletManager.update()
|
||||
Result.Success(walletManager.wallet)
|
||||
} catch (exception: Exception) {
|
||||
Result.Failure(exception)
|
||||
suspend fun reloadData(data: ScanNoteResponse) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (data.walletManager != null) {
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
|
||||
return@withContext
|
||||
}
|
||||
val config = store.state.globalState.configManager?.config ?: return@withContext
|
||||
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
|
||||
store.dispatch(WalletAction.EmptyWallet)
|
||||
} else {
|
||||
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
|
||||
}
|
||||
}
|
||||
handleUpdateWalletResult(result, walletManager)
|
||||
}
|
||||
|
||||
private suspend fun handleUpdateWalletResult(result: Result<Wallet>, walletManager: WalletManager) {
|
||||
|
|
@ -164,59 +204,24 @@ class TapWalletManager {
|
|||
val token = walletManager.wallet.getFirstToken()
|
||||
val amountToCreateAccount = blockchain.amountToCreateAccount(token)
|
||||
if (amountToCreateAccount != null) {
|
||||
store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString()))
|
||||
store.dispatch(WalletAction.LoadWallet.NoAccount(
|
||||
walletManager.wallet,
|
||||
amountToCreateAccount.toString()))
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.LoadWallet.Failure(result.error?.localizedMessage))
|
||||
store.dispatch(WalletAction.LoadWallet.Failure(walletManager.wallet, result.error?.localizedMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadPayIdIfNeeded(): Result<String?>? {
|
||||
val scanNoteResponse = store.state.globalState.scanNoteResponse
|
||||
if (store.state.globalState.configManager?.config?.isWalletPayIdEnabled == false
|
||||
|| scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
return null
|
||||
}
|
||||
val cardId = scanNoteResponse?.card?.cardId
|
||||
val publicKey = scanNoteResponse?.card?.cardPublicKey
|
||||
if (cardId == null || publicKey == null) {
|
||||
return null
|
||||
}
|
||||
return payIdManager.getPayId(cardId, publicKey.toHexString())
|
||||
}
|
||||
|
||||
private suspend fun handlePayIdResult(result: Result<String?>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val config = store.state.globalState.configManager?.config
|
||||
if (config?.isWalletPayIdEnabled == false) {
|
||||
store.dispatch(WalletAction.DisablePayId)
|
||||
return@withContext
|
||||
}
|
||||
val payId = result.data
|
||||
if (payId == null) {
|
||||
store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
} else {
|
||||
store.dispatch(WalletAction.LoadPayId.Success(payId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFiatRatesResult(results: List<Pair<CryptoCurrencyName, Result<BigDecimal>?>>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
results.map {
|
||||
when (it.second) {
|
||||
is Result.Success -> {
|
||||
val rate = it.first to (it.second as Result.Success<BigDecimal>).data
|
||||
store.dispatch(GlobalAction.SetFiatRate(rate))
|
||||
store.dispatch(WalletAction.LoadFiatRate.Success(rate))
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.configurable.config
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
|
||||
import com.tangem.tap.domain.configurable.Loader
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ data class Config(
|
|||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val isWalletPayIdEnabled: Boolean = true,
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
val isCreatingTwinCardsAllowed: Boolean = false
|
||||
|
|
@ -39,7 +40,6 @@ class ConfigManager(
|
|||
|
||||
fun turnOff(name: String) {
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false)
|
||||
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = false)
|
||||
isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false)
|
||||
|
|
@ -48,7 +48,6 @@ class ConfigManager(
|
|||
|
||||
fun resetToDefault(name: String) {
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled)
|
||||
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
isCreatingTwinCardsAllowed -> config =
|
||||
|
|
@ -60,13 +59,11 @@ class ConfigManager(
|
|||
val model = featureModel ?: return
|
||||
|
||||
config = config.copy(
|
||||
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||
|
|
@ -78,22 +75,28 @@ class ConfigManager(
|
|||
config = config.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
infuraProjectId = values.infuraProjectId
|
||||
)
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
infuraProjectId = values.infuraProjectId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val isWalletPayIdEnabled = "isWalletPayIdEnabled"
|
||||
const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled"
|
||||
const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed"
|
||||
const val isTopUpEnabled = "isTopUpEnabled"
|
||||
const val coinMarketCapKey = "coinMarketCapKey"
|
||||
const val moonPayApiKey = "moonPayApiKey"
|
||||
const val moonPayApiSecretKey = "moonPayApiSecretKey"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,9 @@ class ConfigValueModel(
|
|||
val coinMarketCapKey: String,
|
||||
val moonPayApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
)
|
||||
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
|
|
|
|||
|
|
@ -4,32 +4,41 @@ import com.tangem.CardSession
|
|||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.TangemError
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.ReadIssuerDataCommand
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.common.card.EllipticCurve
|
||||
import com.tangem.commands.common.card.masks.Product
|
||||
import com.tangem.commands.verifycard.VerifyCardCommand
|
||||
import com.tangem.commands.verifycard.VerifyCardResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tasks.ScanTask
|
||||
|
||||
data class ScanNoteResponse(
|
||||
val walletManager: WalletManager?,
|
||||
val card: Card,
|
||||
val verifyResponse: VerifyCardResponse? = null,
|
||||
val secondTwinPublicKey: String? = null
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val multiwalletCard: Boolean = false,
|
||||
) : CommandResponse
|
||||
|
||||
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
||||
private val blockchainSdkConfig = store.state.globalState.configManager?.config
|
||||
?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
private val walletManagerFactory = WalletManagerFactory(blockchainSdkConfig)
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
|
||||
ScanTask().run(session) { result ->
|
||||
when (result) {
|
||||
|
|
@ -53,7 +62,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
}
|
||||
|
||||
val walletManager = try {
|
||||
WalletManagerFactory.makeWalletManager(card)
|
||||
walletManagerFactory.makeWalletManager(card)
|
||||
} catch (exception: Exception) {
|
||||
return@run callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
}
|
||||
|
|
@ -72,7 +81,8 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
when (verifyResult) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(
|
||||
walletManager, card, verifyResult.data, publicKey
|
||||
walletManager, card, verifyResult.data, publicKey,
|
||||
batchesAllowingMultiwallet.contains(card.cardData?.batchId)
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -95,7 +105,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
val walletManager = try {
|
||||
WalletManagerFactory.makeMultisigWalletManager(card, twinPublicKey)
|
||||
walletManagerFactory.makeMultisigWalletManager(card, twinPublicKey)
|
||||
} catch (exception: Exception) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
return@run
|
||||
|
|
@ -130,4 +140,13 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
companion object {
|
||||
private val excludedBatches = listOf("0027", "0030", "0031")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
get() {
|
||||
return cardData?.productMask?.contains(Product.TwinCard) != true
|
||||
&& !TapWorkarounds.isStart2Coin
|
||||
&& this.curve == EllipticCurve.Secp256k1
|
||||
}
|
||||
|
||||
private val batchesAllowingMultiwallet = listOf("FFFF")
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.network.createMoshi
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
private val moshi = createMoshi()
|
||||
private val tokensAdapter: JsonAdapter<List<TokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, TokenDao::class.java)
|
||||
)
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Blockchain::class.java)
|
||||
)
|
||||
|
||||
fun loadCardCurrencies(cardId: String): CardCurrencies {
|
||||
return CardCurrencies(loadSavedTokens(cardId), loadSavedBlockchains(cardId))
|
||||
}
|
||||
|
||||
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
|
||||
saveTokens(cardId, currencies.tokens)
|
||||
saveBlockchains(cardId, currencies.blockchains)
|
||||
}
|
||||
|
||||
fun saveAddedToken(cardId: String, token: Token) {
|
||||
val tokens = loadSavedTokens(cardId) + token
|
||||
saveTokens(cardId, tokens)
|
||||
}
|
||||
|
||||
fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
|
||||
val blockchains = loadSavedBlockchains(cardId) + blockchain
|
||||
saveBlockchains(cardId, blockchains)
|
||||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token) {
|
||||
val tokens = loadSavedTokens(cardId).filterNot { it == token }
|
||||
saveTokens(cardId, tokens)
|
||||
}
|
||||
|
||||
fun removeBlockchain(cardId: String, blockchain: Blockchain) {
|
||||
val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
|
||||
saveBlockchains(cardId, blockchains)
|
||||
}
|
||||
|
||||
private fun loadSavedTokens(cardId: String): List<Token> {
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForTokens(cardId))
|
||||
tokensAdapter.fromJson(json)!!.map { it.toToken() }
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTokens(cardId: String, tokens: List<Token>) {
|
||||
val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) })
|
||||
context.rewriteFile(json, getFileNameForTokens(cardId))
|
||||
}
|
||||
|
||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainsAdapter.fromJson(json) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBlockchains(cardId: String, blockchains: List<Blockchain>) {
|
||||
val json = blockchainsAdapter.toJson(blockchains)
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
}
|
||||
|
||||
private fun Context.readFileText(fileName: String): String =
|
||||
this.openFileInput(fileName).bufferedReader().readText()
|
||||
|
||||
private fun Context.rewriteFile(content: String, fileName: String) {
|
||||
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
|
||||
it.write(content.toByteArray(), 0, content.length)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPopularTokens(): List<Token> {
|
||||
val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
|
||||
return tokensAdapter.fromJson(json)!!.map { it.toToken() }
|
||||
}
|
||||
|
||||
fun getBlockchains(): List<Blockchain> {
|
||||
return listOf(
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin,
|
||||
Blockchain.XRP, Blockchain.Tezos,
|
||||
Blockchain.Ethereum, Blockchain.RSK)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
return Token(name = name, symbol = symbol, contractAddress = contractAddress, decimals = decimalCount)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromToken(token: Token): TokenDao {
|
||||
return TokenDao(name = token.name, symbol = token.symbol, contractAddress = token.contractAddress, decimalCount = token.decimals)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardCurrenciesDao(
|
||||
val tokens: List<TokenDao>,
|
||||
val blockchains: List<Blockchain>
|
||||
) {
|
||||
fun toCardCurrencies(): CardCurrencies {
|
||||
return CardCurrencies(tokens = tokens.map { it.toToken() }, blockchains = blockchains)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
|
||||
return CardCurrenciesDao(tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }, blockchains = cardCurrencies.blockchains)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CardCurrencies(
|
||||
val tokens: List<Token>,
|
||||
val blockchains: List<Blockchain>
|
||||
)
|
||||
|
|
@ -17,7 +17,7 @@ sealed class DetailsAction : Action {
|
|||
data class PrepareScreen(
|
||||
val card: Card,
|
||||
val scanNoteResponse: ScanNoteResponse,
|
||||
val wallet: Wallet?,
|
||||
val wallets: List<Wallet>,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class DetailsMiddleware {
|
|||
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
|
||||
preferencesStorage.saveAppCurrency(action.fiatCurrencyName)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName))
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,9 +152,7 @@ class DetailsMiddleware {
|
|||
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,14 +66,15 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
|
|||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return DetailsState(
|
||||
card = action.card, wallet = action.wallet,
|
||||
card = action.card, wallets = action.wallets,
|
||||
cardInfo = action.card.toCardInfo(),
|
||||
appCurrencyState = AppCurrencyState(
|
||||
action.fiatCurrencyName
|
||||
),
|
||||
securityScreenState = SecurityScreenState(currentOption = securityOption),
|
||||
createTwinWalletState = twinsState
|
||||
createTwinWalletState = twinsState,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -81,8 +82,9 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
|
|||
return when (action) {
|
||||
DetailsAction.EraseWallet.Check -> {
|
||||
val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
|
||||
val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true ||
|
||||
state.wallet.amounts.toSendableAmounts().isNotEmpty()
|
||||
val notEmpty = state.wallets.any {
|
||||
!it.recentTransactions.isNullOrEmpty() || it.amounts.toSendableAmounts().isNotEmpty()
|
||||
}
|
||||
val eraseWalletState = when {
|
||||
notAllowedByCard -> EraseWalletState.NotAllowedByCard
|
||||
notEmpty -> EraseWalletState.NotEmpty
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import java.util.*
|
|||
|
||||
data class DetailsState(
|
||||
val card: Card? = null,
|
||||
val wallet: Wallet? = null,
|
||||
val wallets: List<Wallet> = emptyList(),
|
||||
val cardInfo: CardInfo? = null,
|
||||
val appCurrencyState: AppCurrencyState = AppCurrencyState(),
|
||||
val eraseWalletState: EraseWalletState? = null,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class CreateTwinWalletMiddleware {
|
|||
fun handle(action: DetailsAction.CreateTwinWalletAction) {
|
||||
when (action) {
|
||||
is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
|
||||
val wallet = store.state.detailsState.wallet
|
||||
val wallet = store.state.detailsState.wallets.getOrNull(0)
|
||||
if (wallet == null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
tv_signed_hashes.show(state.card?.isTwinCard() != true)
|
||||
tv_signed_hashes_title.show(state.card?.isTwinCard() != true)
|
||||
|
||||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
if (state.createTwinWalletState != null) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ object ReleaseSendState : Action
|
|||
|
||||
data class PrepareSendScreen(
|
||||
val coinAmount: Amount?,
|
||||
val coinRate: BigDecimal?,
|
||||
val tokenAmount: Amount? = null,
|
||||
val tokenRate: BigDecimal? = null
|
||||
) : SendScreenAction
|
||||
|
||||
// Address or PayId
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ import com.tangem.tap.features.send.redux.*
|
|||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
import com.tangem.tap.features.send.redux.states.SendButtonState
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.tangemSdk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -116,6 +118,12 @@ private fun sendTransaction(
|
|||
dispatch(SendAction.SendSuccess)
|
||||
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
scope.launch(Dispatchers.IO) {
|
||||
delay(10000)
|
||||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain.currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (result.error) {
|
||||
|
|
@ -142,7 +150,7 @@ private fun sendTransaction(
|
|||
}
|
||||
// make it easier latter by handling an appropriate enumError or, like on iOS,
|
||||
// accept a string identifier of the error message
|
||||
message.contains("Target account is not created. To create account send 1+ XLM.")-> {
|
||||
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
|
||||
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
|
||||
}
|
||||
else -> {
|
||||
|
|
|
|||
|
|
@ -63,12 +63,10 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
|
|||
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
|
||||
val decimals = amountToExtract.decimals
|
||||
|
||||
val coinConverter = createCurrencyConverter(walletManager.wallet.blockchain.currency, decimals)
|
||||
val tokenConverter = createCurrencyConverter(prepareAction.tokenAmount?.currencySymbol ?: "", decimals)
|
||||
return sendState.copy(
|
||||
walletManager = walletManager,
|
||||
coinConverter = coinConverter,
|
||||
tokenConverter = tokenConverter,
|
||||
coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) },
|
||||
tokenConverter = action.tokenRate?. let { CurrencyConverter(it, decimals) },
|
||||
amountState = sendState.amountState.copy(
|
||||
amountToExtract = amountToExtract,
|
||||
typeOfAmount = amountToExtract.type,
|
||||
|
|
@ -79,11 +77,6 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCurrencyConverter(currency: String, decimals: Int): CurrencyConverter? {
|
||||
val rate = store.state.globalState.conversionRates.getRate(currency)
|
||||
return if (rate == null) null else CurrencyConverter(rate, decimals)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState {
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
|
|||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.wallet.ui.SpacesItemDecoration
|
||||
import com.tangem.tap.features.wallet.ui.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.tap.features.send.redux.states.*
|
|||
import com.tangem.tap.features.send.ui.FeeUiHelper
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
|
||||
import com.tangem.tap.features.wallet.ui.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.btn_expand_collapse.*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class TokensAction : Action {
|
||||
|
||||
object LoadCurrencies : TokensAction() {
|
||||
data class Success(val currencies: List<CurrencyListItem>) : TokensAction()
|
||||
}
|
||||
|
||||
object LoadCardTokens : TokensAction() {
|
||||
data class Success(val tokens: List<Token>) : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class TokensMiddleware {
|
||||
|
||||
val tokensMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> {
|
||||
val tokens = currenciesRepository.getPopularTokens()
|
||||
val blockchains = currenciesRepository.getBlockchains()
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(
|
||||
blockchains, tokens
|
||||
)
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
class TokensReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): TokensState {
|
||||
if (action !is TokensAction) return state.tokensState
|
||||
|
||||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(addedCurrencies = action.wallets.mapNotNull { it.currencyData.currencySymbol })
|
||||
}
|
||||
is TokensAction.LoadCardTokens.Success -> {
|
||||
tokensState.copy(addedTokens = LinkedHashSet(
|
||||
action.tokens.map { TokenWithAmount(it, null) }
|
||||
))
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class TokensState(
|
||||
val addedTokens: LinkedHashSet<TokenWithAmount> = LinkedHashSet(),
|
||||
val addedCurrencies: List<CryptoCurrencyName> = emptyList(),
|
||||
val currencies: List<CurrencyListItem> = emptyList(),
|
||||
) : StateType
|
||||
|
||||
data class TokenWithAmount(
|
||||
val token: Token,
|
||||
val amount: Amount? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.tap.features.tokens.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_add_tokens.*
|
||||
import kotlinx.android.synthetic.main.fragment_details_twin_cards.toolbar
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
|
||||
class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
||||
StoreSubscriber<TokensState> {
|
||||
|
||||
private lateinit var viewAdapter: CurrenciesAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.tokensState == newState.tokensState
|
||||
}.select { it.tokensState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
setupPopularTokensRecyclerView()
|
||||
}
|
||||
|
||||
|
||||
private fun setupPopularTokensRecyclerView() {
|
||||
viewAdapter = CurrenciesAdapter()
|
||||
rv_popular_tokens.layoutManager = LinearLayoutManager(context)
|
||||
rv_popular_tokens.adapter = viewAdapter
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
if (activity == null) return
|
||||
viewAdapter.addedCurrencies = state.addedCurrencies
|
||||
viewAdapter.submitUnfilteredList(state.currencies)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_search -> {
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.popular_tokens, menu)
|
||||
|
||||
val menuItem = menu.findItem(R.id.menu_search)
|
||||
val searchView: SearchView = menuItem.actionView as SearchView
|
||||
searchView.queryHint = "Type here to search"
|
||||
searchView.maxWidth = android.R.attr.width
|
||||
searchView.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
viewAdapter.filter(searchView.query)
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
}
|
||||
|
||||
fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
||||
val watcher = setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
offer(newText ?: "")
|
||||
return false
|
||||
}
|
||||
})
|
||||
awaitClose { (watcher) }
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package com.tangem.tap.features.tokens.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
|
||||
import kotlinx.android.synthetic.main.item_popular_token.view.*
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>(DiffUtilCallback) {
|
||||
|
||||
var addedCurrencies: List<CryptoCurrencyName> = emptyList()
|
||||
|
||||
private var unfilteredList = listOf<CurrencyListItem>()
|
||||
|
||||
fun submitUnfilteredList(list: List<CurrencyListItem>) {
|
||||
unfilteredList = list
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (currentList[position]) {
|
||||
is CurrencyListItem.TitleListItem -> 0
|
||||
is CurrencyListItem.BlockchainListItem, is CurrencyListItem.TokenListItem -> 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
0 -> TitleViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_currency_subtitle, parent, false))
|
||||
1 -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_popular_token, parent, false))
|
||||
else -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_popular_token, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val listItem = currentList[position]
|
||||
if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) {
|
||||
holder.bind(listItem)
|
||||
} else if (holder is CurrenciesViewHolder) {
|
||||
holder.bind(listItem, addedCurrencies)
|
||||
}
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<CurrencyListItem>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem
|
||||
) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
fun filter(query: CharSequence?) {
|
||||
val list = mutableListOf<CurrencyListItem>()
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
val queryNormalized = query.toString().toLowerCase(Locale.US)
|
||||
list.addAll(
|
||||
unfilteredList.filter { element ->
|
||||
when (element) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
element.blockchain.currency.toLowerCase(Locale.US).contains(queryNormalized)
|
||||
|| element.blockchain.fullName.toLowerCase(Locale.US).contains(queryNormalized)
|
||||
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
element.token.name.toLowerCase(Locale.US).contains(queryNormalized)
|
||||
|| element.token.symbol.toLowerCase(Locale.US).contains(queryNormalized)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
list.addAll(unfilteredList)
|
||||
}
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
class CurrenciesViewHolder(val view: View) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
fun bind(currency: CurrencyListItem, addedCurrencies: List<CryptoCurrencyName>) {
|
||||
when (currency) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val blockchain = currency.blockchain
|
||||
view.tv_currency_name.text = blockchain.fullName
|
||||
view.tv_currency_symbol.text = blockchain.currency
|
||||
val isAdded = addedCurrencies.any { it == blockchain.currency }
|
||||
view.btn_add_token.show(!isAdded)
|
||||
view.btn_token_added.show(isAdded)
|
||||
|
||||
view.iv_currency.setImageResource(currency.blockchain.getIconRes())
|
||||
view.iv_currency.colorFilter = null
|
||||
view.tv_token_letter.text = null
|
||||
|
||||
view.btn_add_token.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
|
||||
view.btn_add_token.hide()
|
||||
view.btn_token_added.show()
|
||||
}
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val token = currency.token
|
||||
view.tv_currency_name.text = token.name
|
||||
view.tv_currency_symbol.text = token.symbol
|
||||
|
||||
val isAdded = addedCurrencies.any { it == token.symbol }
|
||||
view.btn_add_token.show(!isAdded)
|
||||
view.btn_token_added.show(isAdded)
|
||||
|
||||
view.iv_currency.setImageResource(R.drawable.shape_circle)
|
||||
view.iv_currency.setColorFilter(token.getColor())
|
||||
view.tv_token_letter.text = token.symbol.take(1)
|
||||
|
||||
view.btn_add_token.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.AddToken(token))
|
||||
view.btn_add_token.hide()
|
||||
view.btn_token_added.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TitleViewHolder(val view: View) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
fun bind(title: CurrencyListItem.TitleListItem) {
|
||||
view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem()
|
||||
|
||||
companion object {
|
||||
fun createListOfCurrencies(blockchains: List<Blockchain>, tokens: List<Token>): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
val tokensTitle = R.string.add_tokens_subtitle_tokens
|
||||
return listOf(TitleListItem(blockchainsTitle)) +
|
||||
blockchains.map { BlockchainListItem(it) } +
|
||||
listOf(TitleListItem(tokensTitle)) +
|
||||
tokens.map { TokenListItem(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
|
|
@ -47,4 +48,13 @@ fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<Pen
|
|||
|
||||
fun List<PendingTransaction>.removeUnknownTransactions(): List<PendingTransaction> {
|
||||
return this.filter { it.type != PendingTransactionType.Unknown }
|
||||
}
|
||||
|
||||
fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
|
||||
if (this.amount.currencySymbol != token.symbol) return null
|
||||
return this.toPendingTransaction(walletAddress)
|
||||
}
|
||||
|
||||
fun List<TransactionData>.toPendingTransactionsForToken(token: Token, walletAddress: String): List<PendingTransaction> {
|
||||
return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -23,73 +21,94 @@ sealed class WalletAction : Action {
|
|||
data class Failure(val error: TapError) : WalletAction()
|
||||
}
|
||||
|
||||
data class LoadWallet(
|
||||
val wallet: Wallet, val artworkId: String?, val allowTopUp: Boolean
|
||||
) : WalletAction() {
|
||||
|
||||
data class LoadWallet(val allowTopUp: Boolean? = null, val currency: CryptoCurrencyName? = null) : WalletAction() {
|
||||
data class Success(val wallet: Wallet) : WalletAction()
|
||||
data class NoAccount(val amountToCreateAccount: String) : WalletAction()
|
||||
data class Failure(val errorMessage: String? = null) : WalletAction()
|
||||
data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
|
||||
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
|
||||
}
|
||||
|
||||
object CheckIfWarningNeeded : WalletAction()
|
||||
object CheckHashesCountOnline : WalletAction()
|
||||
object NeedToCheckHashesCountOnline : WalletAction()
|
||||
object ConfirmHashesCount : WalletAction()
|
||||
data class SetWarnings(val warningList: List<WarningMessage>) : WalletAction()
|
||||
object SaveCardId : WalletAction()
|
||||
data class SetArtworkId(val artworkId: String?) : WalletAction()
|
||||
|
||||
object UpdateWallet : WalletAction() {
|
||||
// sealed class ProcessWallet : WalletAction() {
|
||||
// data class LoadWallet(val artworkId: String?, val allowTopUp: Boolean
|
||||
// ) : ProcessWallet() {
|
||||
// data class Success(val wallet: Wallet) : ProcessWallet()
|
||||
// data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : ProcessWallet()
|
||||
// data class Failure(val wallet: Wallet, val errorMessage: String? = null) : ProcessWallet()
|
||||
// }
|
||||
//
|
||||
// data class UpdateWallet(val currency: CryptoCurrencyName? = null) : ProcessWallet() {
|
||||
// object ScheduleUpdatingWallet : ProcessWallet()
|
||||
// data class Success(val wallet: Wallet) : ProcessWallet()
|
||||
// data class Failure(val errorMessage: String? = null) : ProcessWallet()
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
|
||||
data class AddWalletManagers(val walletManagers: List<WalletManager>) : MultiWallet()
|
||||
data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
data class AddBlockchains(val blockchains: List<Blockchain>) : MultiWallet()
|
||||
data class AddTokens(val tokens: List<Token>) : MultiWallet()
|
||||
data class AddToken(val token: Token) : MultiWallet()
|
||||
data class TokenLoaded(val amount: Amount) : MultiWallet()
|
||||
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
|
||||
data class RemoveWallet(val walletData: WalletData) : MultiWallet()
|
||||
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
data class SetPrimaryToken(val token: Token) : MultiWallet()
|
||||
}
|
||||
|
||||
sealed class CheckSignedHashes : WalletAction() {
|
||||
object CheckIfWarningNeeded : CheckSignedHashes()
|
||||
object CheckHashesCountOnline : CheckSignedHashes()
|
||||
object NeedToCheckHashesCountOnline : CheckSignedHashes()
|
||||
object ConfirmHashesCount : CheckSignedHashes()
|
||||
object SaveCardId : CheckSignedHashes()
|
||||
}
|
||||
|
||||
data class SetWarnings(val warningList: List<WarningMessage>) : WalletAction()
|
||||
|
||||
data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() {
|
||||
object ScheduleUpdatingWallet : WalletAction()
|
||||
data class Success(val wallet: Wallet) : WalletAction()
|
||||
data class Failure(val errorMessage: String? = null) : WalletAction()
|
||||
}
|
||||
|
||||
object LoadFiatRate : WalletAction() {
|
||||
data class Success(val fiatRates: Pair<CryptoCurrencyName, BigDecimal?>) : WalletAction()
|
||||
data class LoadFiatRate(
|
||||
val wallet: Wallet? = null, val currency: CryptoCurrencyName? = null
|
||||
) : WalletAction() {
|
||||
data class Success(val fiatRate: Pair<CryptoCurrencyName, BigDecimal?>) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
object LoadPayId : WalletAction() {
|
||||
data class Success(val payId: String) : WalletAction()
|
||||
object NotCreated : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
object DisablePayId : WalletAction()
|
||||
|
||||
data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() {
|
||||
data class Success(val artwork: Artwork) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
object Scan : WalletAction()
|
||||
data class Send(val amount: Amount? = null) : WalletAction() {
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
object CreatePayId : WalletAction() {
|
||||
data class CompleteCreatingPayId(val payId: String) : WalletAction()
|
||||
data class Success(val payId: String) : WalletAction()
|
||||
object EmptyField : WalletAction(), ErrorAction {
|
||||
override val error = TapError.PayIdEmptyField
|
||||
}
|
||||
|
||||
class Failure(override val error: TapError) : WalletAction(), ErrorAction
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
data class CopyAddress(val context: Context) : WalletAction() {
|
||||
data class CopyAddress(val address: String, val context: Context) : WalletAction() {
|
||||
object Success : WalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_notification_address_copied
|
||||
}
|
||||
}
|
||||
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
object ShowQrCode : WalletAction()
|
||||
object HideDialog : WalletAction()
|
||||
data class ExploreAddress(val context: Context) : WalletAction()
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
object CreateWallet : WalletAction()
|
||||
object EmptyWallet : WalletAction()
|
||||
object Scan : WalletAction()
|
||||
|
||||
data class Send(val amount: Amount? = null) : WalletAction() {
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
sealed class TopUpAction : WalletAction() {
|
||||
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
|
||||
|
|
|
|||
|
|
@ -1,316 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardType
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.getType
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.NetworkStateChanged
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class WalletMiddleware {
|
||||
private val topUpMiddleware = TopUpMiddleware()
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadWalletData()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadPayId -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadPayId()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
is WalletAction.CreateWallet -> {
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId?.let {
|
||||
TwinsHelper.getTwinCardNumber(it)
|
||||
},
|
||||
CreateTwinWallet.CreateWallet
|
||||
))
|
||||
} else {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet -> {
|
||||
if (store.state.walletState.state == ProgressState.Done) {
|
||||
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
|
||||
is WalletAction.LoadWallet.Success -> {
|
||||
store.dispatch(WalletAction.CheckHashesCountOnline)
|
||||
if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet)
|
||||
}
|
||||
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
|
||||
scope.launch {
|
||||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey
|
||||
if (cardId != null && wallet != null && publicKey != null) {
|
||||
val result = PayIdManager().setPayId(
|
||||
cardId, publicKey.toHexString(),
|
||||
action.payId, wallet.address, wallet.blockchain
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success ->
|
||||
store.dispatch(WalletAction.CreatePayId.Success(action.payId))
|
||||
is Result.Failure -> {
|
||||
val error = result.error as? TapError
|
||||
?: TapError.PayIdCreatingError
|
||||
store.dispatch(WalletAction.CreatePayId.Failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.Scan -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data, true)
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData -> {
|
||||
scope.launch {
|
||||
store.state.globalState.scanNoteResponse?.let {
|
||||
store.state.globalState.tapWalletManager.loadData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
is NetworkStateChanged -> {
|
||||
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
|
||||
store.dispatch(WalletAction.CheckHashesCountOnline)
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
store.state.walletState.walletAddresses?.selectedAddress?.address?.let {
|
||||
action.context.copyToClipboard(it)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
val uri = Uri.parse(store.state.walletState.walletAddresses?.selectedAddress?.exploreUrl)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
ContextCompat.startActivity(action.context, intent, null)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
val newAction = prepareSendAction(action.amount)
|
||||
store.dispatch(newAction)
|
||||
if (newAction is PrepareSendScreen) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
}
|
||||
is WalletAction.CheckIfWarningNeeded -> {
|
||||
val globalState = store.state.globalState
|
||||
val validator = globalState.scanNoteResponse?.walletManager as? SignatureCountValidator
|
||||
globalState.scanNoteResponse?.card?.let { card ->
|
||||
store.state.globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.getType() != CardType.Release) addWarningMessage(WarningMessagesManager.devCardWarning())
|
||||
if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
|
||||
checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
|
||||
}
|
||||
updateWarningMessages()
|
||||
}
|
||||
|
||||
}
|
||||
is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
|
||||
is WalletAction.SaveCardId -> {
|
||||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.saveScannedCardId(it) }
|
||||
}
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
|
||||
}
|
||||
is WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
preferencesStorage.saveTwinsOnboardingShown()
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWalletUpdate(wallet: Wallet) {
|
||||
if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) {
|
||||
store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
delay(10000)
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.UpdateWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun prepareSendAction(amount: Amount?): Action {
|
||||
return if (amount != null) {
|
||||
if (amount.type is AmountType.Token) {
|
||||
PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount)
|
||||
} else {
|
||||
PrepareSendScreen(amount)
|
||||
}
|
||||
} else {
|
||||
val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts()
|
||||
if (amounts?.size ?: 0 > 1) {
|
||||
WalletAction.Send.ChooseCurrency(amounts)
|
||||
} else {
|
||||
val amountToSend = amounts?.first()
|
||||
PrepareSendScreen(amountToSend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfWarningNeeded(
|
||||
card: Card, signatureCountValidator: SignatureCountValidator? = null,
|
||||
): WarningMessage? {
|
||||
if (card.isTwinCard()) return null
|
||||
|
||||
return if (signatureCountValidator == null) {
|
||||
if (card.walletSignedHashes ?: 0 > 0) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning()
|
||||
} else {
|
||||
store.dispatch(WalletAction.SaveCardId)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
store.dispatch(WalletAction.NeedToCheckHashesCountOnline)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHashesCountOnline() {
|
||||
if (store.state.walletState.hashesCountVerified != false) return
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
|
||||
|
||||
val card = store.state.globalState.scanNoteResponse?.card
|
||||
if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
|
||||
|
||||
if (card.isTwinCard()) return
|
||||
|
||||
val validator = store.state.globalState.scanNoteResponse?.walletManager
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success -> {
|
||||
store.dispatch(WalletAction.ConfirmHashesCount)
|
||||
store.dispatch(WalletAction.SaveCardId)
|
||||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
} else if (card.walletSignedHashes ?: 0 > 0) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
|
||||
store.state.globalState.warningManager?.addWarning(warning)
|
||||
if (autoUpdate) updateWarningMessages()
|
||||
}
|
||||
|
||||
private fun updateWarningMessages() {
|
||||
val warningManager = store.state.globalState.warningManager ?: return
|
||||
store.dispatch(WalletAction.SetWarnings(warningManager.getWarnings(WarningMessage.Location.MainScreen)))
|
||||
}
|
||||
}
|
||||
|
||||
private class TopUpMiddleware {
|
||||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val defaultAddress = store.state.walletState.walletAddresses!!.list[0].address
|
||||
val url = TopUpHelper.getUrl(
|
||||
store.state.walletState.currencyData.currencySymbol!!,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(action.toolbarColor)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(action.context, Uri.parse(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,26 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.common.network.TangemService
|
||||
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
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.reducers.MultiWalletReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.OnWalletLoadedReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.TwinsReducer
|
||||
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 org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletReducer {
|
||||
companion object {
|
||||
|
|
@ -29,11 +30,19 @@ class WalletReducer {
|
|||
|
||||
private fun internalReduce(action: Action, state: AppState): WalletState {
|
||||
|
||||
val multiWalletReducer = MultiWalletReducer()
|
||||
val twinsReducer = TwinsReducer()
|
||||
val onWalletLoadedReducer = OnWalletLoadedReducer()
|
||||
|
||||
if (action !is WalletAction) return state.walletState
|
||||
|
||||
var newState = state.walletState
|
||||
|
||||
when (action) {
|
||||
is WalletAction.CheckSignedHashes -> newState = handleCheckSignedHashesActions(action, newState)
|
||||
is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState)
|
||||
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
|
||||
|
||||
is WalletAction.ResetState -> newState = WalletState()
|
||||
is WalletAction.EmptyWallet -> {
|
||||
val creatingWalletAllowed = !(newState.twinCardsState != null &&
|
||||
|
|
@ -41,121 +50,149 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
wallets = listOf(WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
|
||||
topUpState = TopUpState(false)
|
||||
)))
|
||||
}
|
||||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
walletAddresses = createAddressList(wallet, newState.walletAddresses),
|
||||
currencyData = BalanceWidgetData(
|
||||
status = BalanceStatus.Unreachable,
|
||||
currency = wallet?.blockchain?.fullName,
|
||||
token = wallet?.getFirstToken()?.symbol?.let {
|
||||
TokenData("", tokenSymbol = it)
|
||||
}),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
}
|
||||
is TapError.UnknownBlockchain -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
|
||||
topUpState = TopUpState(false)
|
||||
wallets = listOf(WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
|
||||
topUpState = TopUpState(false)
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is WalletAction.LoadData -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadWallet -> {
|
||||
val wallet = action.wallet
|
||||
if (action.currency == null) {
|
||||
val wallets = newState.wallets.map { wallet ->
|
||||
wallet.copy(
|
||||
currencyData = wallet.currencyData.copy(
|
||||
status = BalanceStatus.Loading,
|
||||
currency = wallet.currencyData.currency,
|
||||
currencySymbol = wallet.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = action.allowTopUp
|
||||
?: wallet.topUpState.allowed)
|
||||
)
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
wallets = wallets
|
||||
)
|
||||
} else {
|
||||
val walletManager = newState.getWalletManager(action.currency) ?: return newState
|
||||
val currencies = listOf(walletManager.wallet.blockchain.currency + walletManager.presetTokens.map { it.symbol })
|
||||
val newWallets = newState.wallets.filter { currencies.contains(it.currencyData.currencySymbol) }
|
||||
.map { wallet ->
|
||||
wallet.copy(
|
||||
currencyData = wallet.currencyData.copy(
|
||||
status = BalanceStatus.Loading,
|
||||
currency = wallet.currencyData.currency,
|
||||
currencySymbol = wallet.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(
|
||||
allowed = action.allowTopUp ?: wallet.topUpState.allowed
|
||||
)
|
||||
)
|
||||
}
|
||||
val wallets = newState.replaceSomeWallets(newWallets)
|
||||
newState = newState.copy(wallets = wallets)
|
||||
}
|
||||
|
||||
}
|
||||
is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState)
|
||||
is WalletAction.UpdateWallet.Success -> {
|
||||
newState = onWalletLoadedReducer.reduce(action.wallet, newState)
|
||||
// newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
is WalletAction.LoadWallet.NoAccount -> {
|
||||
val walletData = newState.getWalletData(action.wallet.blockchain.currency)?.copy(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.NoAccount, action.wallet.blockchain.fullName,
|
||||
currencySymbol = action.wallet.blockchain.currency,
|
||||
amountToCreateAccount = action.amountToCreateAccount
|
||||
)
|
||||
)
|
||||
val wallets = newState.replaceWalletInWallets(walletData)
|
||||
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = state,
|
||||
wallets = wallets
|
||||
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadWallet.Failure -> {
|
||||
val walletData = newState.getWalletData(action.wallet.blockchain.currency)
|
||||
val newWalletData = walletData?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable,
|
||||
errorMessage = action.errorMessage
|
||||
),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
val wallets = newState.replaceWalletInWallets(newWalletData)
|
||||
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = state, wallets = wallets
|
||||
)
|
||||
}
|
||||
is WalletAction.SetArtworkId -> {
|
||||
val cardImage = if (newState.cardImage?.artworkId == action.artworkId) {
|
||||
newState.cardImage
|
||||
} else {
|
||||
null
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
cardImage = cardImage,
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
token = wallet.getFirstToken()?.symbol?.let {
|
||||
TokenData("", tokenSymbol = it)
|
||||
}
|
||||
),
|
||||
walletAddresses = createAddressList(wallet, newState.walletAddresses),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = action.allowTopUp)
|
||||
)
|
||||
newState = newState.copy(cardImage = cardImage)
|
||||
}
|
||||
is WalletAction.LoadWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
|
||||
|
||||
is WalletAction.LoadWallet.NoAccount -> {
|
||||
val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done, wallet = wallet,
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.NoAccount, wallet?.blockchain?.fullName,
|
||||
currencySymbol = wallet?.blockchain?.currency,
|
||||
amountToCreateAccount = action.amountToCreateAccount
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadWallet.Failure -> newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = newState.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable,
|
||||
errorMessage = action.errorMessage
|
||||
),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
is WalletAction.UpdateWallet -> {
|
||||
if (store.state.walletState.state == ProgressState.Done) {
|
||||
newState = newState.copy(updatingWallet = true)
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
|
||||
newState = newState.copy(updatingWallet = true)
|
||||
is WalletAction.UpdateWallet.Success -> {
|
||||
newState = onWalletLoaded(action.wallet, newState)
|
||||
newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty())
|
||||
}
|
||||
is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
newState.copy(currencyData = newState.currencyData.copy(
|
||||
fiatAmount = null,
|
||||
token = newState.currencyData.token?.copy(fiatAmount = null))
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadFiatRate.Success -> {
|
||||
val rate = action.fiatRates.second ?: return newState
|
||||
val currency = action.fiatRates.first
|
||||
val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) {
|
||||
newState.wallet?.amounts?.get(AmountType.Coin)?.value
|
||||
?.toFiatString(rate, state.globalState.appCurrency)
|
||||
} else {
|
||||
newState.currencyData.fiatAmount
|
||||
}
|
||||
val token = newState.wallet?.getFirstToken()
|
||||
val tokenFiatAmount = if (currency == token?.symbol) {
|
||||
newState.wallet?.getTokenAmount(token)?.value?.toFiatString(rate, state.globalState.appCurrency)
|
||||
} else {
|
||||
newState.currencyData.token?.fiatAmount
|
||||
}
|
||||
newState = newState.copy(currencyData = newState.currencyData.copy(
|
||||
fiatAmount = fiatAmount,
|
||||
token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount)
|
||||
))
|
||||
}
|
||||
// is WalletAction.UpdateWallet -> {
|
||||
// if (store.state.walletState.state == ProgressState.Done) {
|
||||
// newState = newState.copy(updatingWallet = true)
|
||||
// }
|
||||
// }
|
||||
// is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
|
||||
// newState = newState.copy(updatingWallet = true)
|
||||
|
||||
// is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
|
||||
// is WalletAction.LoadFiatRate -> {
|
||||
// newState.copy(currencyData = newState.currencyData.copy(
|
||||
// fiatAmount = null,
|
||||
// token = newState.currencyData.token?.copy(fiatAmount = null))
|
||||
// )
|
||||
// }
|
||||
is WalletAction.LoadFiatRate.Success ->
|
||||
newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
|
||||
is WalletAction.LoadArtwork -> {
|
||||
val cardId = action.card.cardId
|
||||
val cardPublicKey = action.card.cardPublicKey?.toHexString()
|
||||
|
|
@ -177,38 +214,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
|
||||
}
|
||||
is WalletAction.ShowQrCode -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedWallet)
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.QrDialog(
|
||||
newState.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
|
||||
newState.walletAddresses?.selectedAddress?.shareUrl,
|
||||
newState.currencyData.currency
|
||||
selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
|
||||
selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl,
|
||||
selectedWalletData?.currencyData?.currency
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.HideDialog -> {
|
||||
newState = newState.copy(walletDialog = null)
|
||||
}
|
||||
is WalletAction.LoadPayId.Success -> newState = newState.copy(
|
||||
payIdData = PayIdData(PayIdState.Created, action.payId)
|
||||
)
|
||||
is WalletAction.LoadPayId.NotCreated -> newState = newState.copy(
|
||||
payIdData = PayIdData(PayIdState.NotCreated, null)
|
||||
)
|
||||
is WalletAction.DisablePayId -> newState = newState.copy(
|
||||
payIdData = PayIdData(PayIdState.Disabled, null)
|
||||
)
|
||||
is WalletAction.CreatePayId, is WalletAction.CreatePayId.Failure ->
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.EnterPayId)
|
||||
)
|
||||
is WalletAction.CreatePayId.CompleteCreatingPayId -> newState = newState.copy(
|
||||
walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.Waiting)
|
||||
)
|
||||
is WalletAction.CreatePayId.Success -> newState = newState.copy(
|
||||
payIdData = PayIdData(PayIdState.Created, action.payId),
|
||||
walletDialog = null
|
||||
)
|
||||
is WalletAction.CreatePayId.Cancel -> newState = newState.copy(walletDialog = null)
|
||||
is WalletAction.Send.ChooseCurrency -> {
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts)
|
||||
|
|
@ -216,43 +233,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
}
|
||||
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
|
||||
is WalletAction.SetWarnings -> newState = newState.copy(mainWarningsList = action.warningList)
|
||||
is WalletAction.NeedToCheckHashesCountOnline ->
|
||||
newState = newState.copy(hashesCountVerified = false)
|
||||
is WalletAction.ConfirmHashesCount ->
|
||||
newState = newState.copy(hashesCountVerified = true)
|
||||
is WalletAction.TopUpAction -> {
|
||||
newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState))
|
||||
}
|
||||
is WalletAction.TopUpAction -> return newState
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val walletAddresses = newState.walletAddresses ?: return newState
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedWallet)
|
||||
|
||||
val walletAddresses = newState.getWalletData(selectedWalletData?.currencyData?.currencySymbol)?.walletAddresses
|
||||
?: return newState
|
||||
val address = walletAddresses.list.firstOrNull { it.type == action.type }
|
||||
?: return newState
|
||||
|
||||
newState = newState.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
|
||||
}
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = TwinCardsState(
|
||||
secondCardId = action.secondCardId,
|
||||
cardNumber = action.number,
|
||||
showTwinOnboarding = newState.twinCardsState?.showTwinOnboarding
|
||||
?: false,
|
||||
isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.ShowOnboarding -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true)
|
||||
?: TwinCardsState(null, null,
|
||||
showTwinOnboarding = true,
|
||||
isCreatingTwinCardsAllowed = false)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false)
|
||||
val wallets = newState.replaceWalletInWallets(
|
||||
selectedWalletData?.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
|
||||
)
|
||||
newState = newState.copy(wallets = wallets)
|
||||
}
|
||||
}
|
||||
return newState
|
||||
|
|
@ -281,56 +273,89 @@ fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null)
|
|||
return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
|
||||
}
|
||||
|
||||
private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState {
|
||||
private fun handleCheckSignedHashesActions(action: WalletAction.CheckSignedHashes, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> state
|
||||
WalletAction.CheckSignedHashes.CheckHashesCountOnline -> state
|
||||
WalletAction.CheckSignedHashes.CheckIfWarningNeeded -> state
|
||||
WalletAction.CheckSignedHashes.ConfirmHashesCount -> state.copy(hashesCountVerified = true)
|
||||
WalletAction.CheckSignedHashes.NeedToCheckHashesCountOnline -> state.copy(hashesCountVerified = false)
|
||||
WalletAction.CheckSignedHashes.SaveCardId -> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWalletLoaded(
|
||||
wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
|
||||
|
||||
private fun setNewFiatRate(
|
||||
fiatRate: Pair<CryptoCurrencyName, BigDecimal?>,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
): WalletState {
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val rate = fiatRate.second ?: return state
|
||||
val rateFormatted = rate.toFormattedCurrencyString(2, appCurrency)
|
||||
val currency = fiatRate.first
|
||||
|
||||
if (!state.isMultiwalletAllowed) {
|
||||
return setSingeWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
|
||||
} else {
|
||||
return setMultiWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMultiWalletFiatRate(
|
||||
rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
): WalletState {
|
||||
val walletData = state.getWalletData(currency) ?: return state
|
||||
val wallet = state.walletManagers.find { it.wallet.blockchain.currency == currency }?.wallet
|
||||
if (wallet != null) {
|
||||
val newWalletData = state.getWalletData(currency)?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
fiatAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
?.toFiatString(rate, appCurrency)),
|
||||
fiatRate = rate, fiatRateString = rateFormatted
|
||||
)
|
||||
return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
|
||||
} else {
|
||||
val ethereumWallet = state.walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }?.wallet
|
||||
val token = ethereumWallet?.getTokens()?.find { it.symbol == currency } ?: return state
|
||||
val tokenFiatAmount = ethereumWallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
|
||||
val newWalletData = state.getWalletData(currency)?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
fiatAmount = tokenFiatAmount),
|
||||
fiatRate = rate, fiatRateString = rateFormatted
|
||||
)
|
||||
return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSingeWalletFiatRate(
|
||||
rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
): WalletState {
|
||||
val wallet = state.walletManagers[0].wallet
|
||||
val token = wallet.getFirstToken()
|
||||
val tokenData = if (token != null) {
|
||||
val tokenAmount = wallet.getTokenAmount(token)
|
||||
if (tokenAmount != null) {
|
||||
val tokenFiatRate = store.state.globalState.conversionRates.getRate(tokenAmount.currencySymbol)
|
||||
val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
|
||||
tokenAmount.currencySymbol, tokenFiatAmount)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val amount = wallet.amounts[AmountType.Coin]?.value
|
||||
val formattedAmount = amount?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
val fiatRate = store.state.globalState.conversionRates.getRate(wallet.blockchain.currency)
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
if (currency == state.primaryWallet?.currencyData?.currencySymbol) {
|
||||
val fiatAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
?.toFiatString(rate, appCurrency)
|
||||
val walletData = state.primaryWallet.copy(
|
||||
currencyData = state.primaryWallet.currencyData.copy(fiatAmount = fiatAmount),
|
||||
fiatRate = rate,
|
||||
fiatRateString = rateFormatted
|
||||
)
|
||||
return state.copy(wallets = listOf(walletData))
|
||||
} else if (currency == token?.symbol) {
|
||||
val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
|
||||
val tokenData = state.primaryWallet?.currencyData?.token?.copy(
|
||||
fiatAmount = tokenFiatAmount,
|
||||
fiatRate = rate,
|
||||
fiatRateString = rateFormatted
|
||||
)
|
||||
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
val walletData = state.primaryWallet?.copy(
|
||||
currencyData = state.primaryWallet.currencyData.copy(
|
||||
token = tokenData
|
||||
))
|
||||
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
|
||||
return state.copy(wallets = wallets)
|
||||
}
|
||||
return walletState.copy(
|
||||
state = ProgressState.Done, wallet = wallet,
|
||||
currencyData = BalanceWidgetData(
|
||||
balanceStatus, wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
formattedAmount,
|
||||
token = tokenData,
|
||||
fiatAmount = fiatAmount
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,47 +1,98 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class WalletState(
|
||||
val state: ProgressState = ProgressState.Done,
|
||||
val error: ErrorType? = null,
|
||||
val cardImage: Artwork? = null,
|
||||
val wallet: Wallet? = null,
|
||||
val pendingTransactions: List<PendingTransaction> = emptyList(),
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val walletAddresses: WalletAddresses? = null,
|
||||
val currencyData: BalanceWidgetData = BalanceWidgetData(),
|
||||
val payIdData: PayIdData = PayIdData(),
|
||||
val walletDialog: WalletDialog? = null,
|
||||
val updatingWallet: Boolean = false,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val topUpState: TopUpState = TopUpState(),
|
||||
val twinCardsState: TwinCardsState? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf()
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val wallets: List<WalletData> = emptyList(),
|
||||
val walletManagers: List<WalletManager> = emptyList(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
val cardCurrency: CryptoCurrencyName? = null,
|
||||
val selectedWallet: CryptoCurrencyName? = null,
|
||||
val primaryBlockchain: Blockchain? = null,
|
||||
val primaryToken: Token? = null
|
||||
) : StateType {
|
||||
val showDetails: Boolean =
|
||||
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
|
||||
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
|
||||
|
||||
val showMultipleAddress: Boolean
|
||||
get() {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
return (wallet?.blockchain == Blockchain.Bitcoin ||
|
||||
wallet?.blockchain == Blockchain.BitcoinTestnet ||
|
||||
wallet?.blockchain == Blockchain.CardanoShelley) &&
|
||||
listOfAddresses.size > 1
|
||||
val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
|
||||
// val primaryWalletManager = if (walletManagers.isNotEmpty()) walletManagers[0] else null
|
||||
|
||||
val shouldShowDetails: Boolean =
|
||||
primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
|
||||
primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
|
||||
|
||||
fun getWalletManager(currencyName: CryptoCurrencyName?): WalletManager? {
|
||||
if (currencyName == null) return null
|
||||
val walletManager = walletManagers.find { it.wallet.blockchain.currency == currencyName }
|
||||
if (walletManager != null) return walletManager
|
||||
|
||||
val ethereumWalletManager = walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }
|
||||
return if (ethereumWalletManager?.presetTokens?.find { it.symbol == currencyName } != null) {
|
||||
ethereumWalletManager
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletData(currencyName: CryptoCurrencyName?): WalletData? {
|
||||
if (currencyName == null) return null
|
||||
return wallets.find { it.currencyData.currencySymbol == currencyName }
|
||||
}
|
||||
|
||||
fun getSelectedWalletData(): WalletData? {
|
||||
return wallets.find { it.currencyData.currencySymbol == selectedWallet }
|
||||
}
|
||||
|
||||
fun canBeRemoved(walletData: WalletData?): Boolean {
|
||||
if (walletData == null) return false
|
||||
|
||||
if (walletData.blockchain != store.state.walletState.primaryBlockchain
|
||||
&& (walletData.token == null || walletData.token != store.state.walletState.primaryToken)) {
|
||||
val walletManager = getWalletManager(walletData.currencyData.currencySymbol)
|
||||
?: return true
|
||||
val wallet = walletManager.wallet
|
||||
if (walletData.blockchain != null) {
|
||||
return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
|
||||
wallet.amounts.toSendableAmounts().isEmpty()
|
||||
} else if (walletData.token != null) (
|
||||
return wallet.recentTransactions.toPendingTransactionsForToken(
|
||||
walletData.token, wallet.address).isEmpty()
|
||||
&& wallet.amounts[AmountType.Token(token = walletData.token)]?.isAboveZero() != true
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun replaceWalletInWallets(walletData: WalletData?): List<WalletData> {
|
||||
if (walletData == null) return wallets
|
||||
return wallets.filter { it.currencyData.currency != walletData.currencyData.currency } + walletData
|
||||
}
|
||||
|
||||
fun replaceSomeWallets(newWallets: List<WalletData>): List<WalletData> {
|
||||
val currencies = newWallets.map { it.currencyData.currency }
|
||||
return wallets.filter { !currencies.contains(it.currencyData.currency) } + newWallets
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed class WalletDialog {
|
||||
|
|
@ -49,24 +100,13 @@ sealed class WalletDialog {
|
|||
val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName?
|
||||
) : WalletDialog()
|
||||
|
||||
data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
|
||||
data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog()
|
||||
}
|
||||
|
||||
enum class ProgressState { Loading, Done, Error }
|
||||
|
||||
enum class ErrorType { NoInternetConnection }
|
||||
|
||||
enum class PayIdState { Disabled, Loading, NotCreated, Created, ErrorLoading }
|
||||
|
||||
data class PayIdData(
|
||||
val payIdState: PayIdState = PayIdState.Loading,
|
||||
val payId: String? = null
|
||||
)
|
||||
|
||||
enum class CreatingPayIdState { EnterPayId, Waiting }
|
||||
|
||||
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
|
||||
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
|
||||
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
|
||||
|
|
@ -81,7 +121,8 @@ data class AddressData(
|
|||
val address: String,
|
||||
val type: AddressType,
|
||||
val shareUrl: String,
|
||||
val exploreUrl: String
|
||||
val exploreUrl: String,
|
||||
val qrCode: Bitmap? = null
|
||||
)
|
||||
|
||||
data class Artwork(
|
||||
|
|
@ -110,4 +151,27 @@ data class TwinCardsState(
|
|||
val cardNumber: TwinCardNumber?,
|
||||
val showTwinOnboarding: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean
|
||||
)
|
||||
)
|
||||
|
||||
data class WalletData(
|
||||
val pendingTransactions: List<PendingTransaction> = emptyList(),
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val walletAddresses: WalletAddresses? = null,
|
||||
val currencyData: BalanceWidgetData = BalanceWidgetData(),
|
||||
val updatingWallet: Boolean = false,
|
||||
val topUpState: TopUpState = TopUpState(),
|
||||
val allowToSend: Boolean = true,
|
||||
val fiatRateString: String? = null,
|
||||
val fiatRate: BigDecimal? = null,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val token: Token? = null,
|
||||
val blockchain: Blockchain? = null,
|
||||
) {
|
||||
fun shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
return (currencyData.currencySymbol == Blockchain.Bitcoin.currency ||
|
||||
currencyData.currencySymbol == Blockchain.BitcoinTestnet.currency ||
|
||||
currencyData.currencySymbol == Blockchain.CardanoShelley.currency) &&
|
||||
listOfAddresses.size > 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.SignatureCountValidator
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardType
|
||||
import com.tangem.common.extensions.getType
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tasks.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class CheckSignedHashesMiddleware {
|
||||
fun handle(action: WalletAction.CheckSignedHashes, globalState: GlobalState?) {
|
||||
when (action) {
|
||||
is WalletAction.CheckSignedHashes.CheckIfWarningNeeded -> {
|
||||
val validator = globalState?.scanNoteResponse?.walletManager as? SignatureCountValidator
|
||||
globalState?.scanNoteResponse?.card?.let { card ->
|
||||
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.getType() != CardType.Release) addWarningMessage(WarningMessagesManager.devCardWarning())
|
||||
if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
|
||||
checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
|
||||
}
|
||||
updateWarningMessages()
|
||||
}
|
||||
|
||||
}
|
||||
is WalletAction.CheckSignedHashes.CheckHashesCountOnline -> checkHashesCountOnline()
|
||||
is WalletAction.CheckSignedHashes.SaveCardId -> {
|
||||
val cardId = globalState?.scanNoteResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.saveScannedCardId(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfWarningNeeded(
|
||||
card: Card, signatureCountValidator: SignatureCountValidator? = null,
|
||||
): WarningMessage? {
|
||||
if (card.isTwinCard() || card.isMultiwalletAllowed) return null
|
||||
|
||||
return if (signatureCountValidator == null) {
|
||||
if (card.walletSignedHashes ?: 0 > 0) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning()
|
||||
} else {
|
||||
store.dispatch(WalletAction.CheckSignedHashes.SaveCardId)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
store.dispatch(WalletAction.CheckSignedHashes.NeedToCheckHashesCountOnline)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHashesCountOnline() {
|
||||
if (store.state.walletState.hashesCountVerified != false) return
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
|
||||
|
||||
val card = store.state.globalState.scanNoteResponse?.card
|
||||
if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
|
||||
|
||||
if (card.isTwinCard() || card.isMultiwalletAllowed) return
|
||||
|
||||
val validator = store.state.globalState.scanNoteResponse?.walletManager
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success -> {
|
||||
store.dispatch(WalletAction.CheckSignedHashes.ConfirmHashesCount)
|
||||
store.dispatch(WalletAction.CheckSignedHashes.SaveCardId)
|
||||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
} else if (card.walletSignedHashes ?: 0 > 0) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
|
||||
store.state.globalState.warningManager?.addWarning(warning)
|
||||
if (autoUpdate) updateWarningMessages()
|
||||
}
|
||||
|
||||
private fun updateWarningMessages() {
|
||||
val warningManager = store.state.globalState.warningManager ?: return
|
||||
store.dispatch(WalletAction.SetWarnings(warningManager.getWarnings(WarningMessage.Location.MainScreen)))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TokenManager
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class MultiWalletMiddleware {
|
||||
fun handle(
|
||||
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?
|
||||
) {
|
||||
when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.walletData != null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
globalState?.scanNoteResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveAddedToken(it, action.token)
|
||||
}
|
||||
addToken(action.token, walletState)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
action.tokens.map { addToken(it, walletState) }
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
globalState?.scanNoteResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveAddedBlockchain(it, action.blockchain)
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = action.blockchain.currency))
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val cardId = globalState?.scanNoteResponse?.card?.cardId
|
||||
if (action.walletData.token != null) {
|
||||
cardId?.let { currenciesRepository.removeToken(it, action.walletData.token) }
|
||||
} else if (action.walletData.blockchain != null) {
|
||||
cardId?.let { currenciesRepository.removeBlockchain(it, action.walletData.blockchain) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToken(token: Token, walletState: WalletState?) {
|
||||
val tokenManager =
|
||||
(walletState?.getWalletManager(Blockchain.Ethereum.currency) as? TokenManager)
|
||||
scope.launch {
|
||||
val result = tokenManager?.addToken(token)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = token.symbol))
|
||||
store.dispatch(WalletAction.MultiWallet.TokenLoaded(result.data))
|
||||
}
|
||||
is Result.Failure -> null
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
|
||||
class TopUpMiddleware {
|
||||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData()
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val defaultAddress = selectedWalletData?.walletAddresses!!.list[0].address
|
||||
val url = TopUpHelper.getUrl(
|
||||
selectedWalletData.currencyData.currencySymbol!!,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(action.toolbarColor)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(action.context, Uri.parse(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.store
|
||||
|
||||
class TwinsMiddleware {
|
||||
fun handle(action: WalletAction.TwinsAction) {
|
||||
when (action) {
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
|
||||
}
|
||||
WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
preferencesStorage.saveTwinsOnboardingShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.shareText
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
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.network.NetworkStateChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class WalletMiddleware {
|
||||
private val topUpMiddleware = TopUpMiddleware()
|
||||
private val twinsMiddleware = TwinsMiddleware()
|
||||
private val checkSignedHashesMiddleware = CheckSignedHashesMiddleware()
|
||||
private val multiWalletMiddleware = MultiWalletMiddleware()
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
val globalState = state()?.globalState
|
||||
val walletState = state()?.walletState
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
|
||||
is WalletAction.TwinsAction -> twinsMiddleware.handle(action)
|
||||
is WalletAction.CheckSignedHashes ->
|
||||
checkSignedHashesMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet ->
|
||||
multiWalletMiddleware.handle(action, walletState, globalState)
|
||||
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
if (action.currency == null) {
|
||||
walletState?.walletManagers?.map { walletManager ->
|
||||
globalState?.tapWalletManager?.loadWalletData(walletManager)
|
||||
}
|
||||
} else {
|
||||
val walletManager = walletState?.getWalletManager(action.currency)
|
||||
walletManager?.let { globalState?.tapWalletManager?.loadWalletData(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
scope.launch {
|
||||
if (action.wallet != null) {
|
||||
globalState?.tapWalletManager?.loadFiatRate(
|
||||
globalState.appCurrency, action.wallet
|
||||
)
|
||||
} else if (action.currency != null) {
|
||||
globalState?.tapWalletManager?.loadFiatRate(
|
||||
globalState.appCurrency, action.currency
|
||||
)
|
||||
} else {
|
||||
walletState?.wallets?.filter { it.blockchain != null }
|
||||
?.map { walletState.getWalletManager(it.blockchain?.currency) }
|
||||
?.mapNotNull { walletManager ->
|
||||
walletManager?.let {
|
||||
globalState?.tapWalletManager?.loadFiatRate(
|
||||
globalState.appCurrency, walletManager.wallet
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.CreateWallet -> {
|
||||
if (walletState?.twinCardsState != null) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
globalState?.scanNoteResponse?.card?.cardId?.let {
|
||||
TwinsHelper.getTwinCardNumber(it)
|
||||
},
|
||||
CreateTwinWallet.CreateWallet
|
||||
))
|
||||
} else {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
globalState?.scanNoteResponse?.card?.cardId
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
globalState?.tapWalletManager?.onCardScanned(result.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet -> {
|
||||
if (action.currency != null) {
|
||||
scope.launch {
|
||||
val walletManager = walletState?.getWalletManager(action.currency)
|
||||
walletManager?.let { globalState?.tapWalletManager?.updateWallet(it) }
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
if (walletState?.state == ProgressState.Done) {
|
||||
walletState.walletManagers.map { walletManager ->
|
||||
globalState?.tapWalletManager?.updateWallet(walletManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
is WalletAction.LoadWallet.Success -> {
|
||||
store.dispatch(WalletAction.CheckSignedHashes.CheckHashesCountOnline)
|
||||
}
|
||||
is WalletAction.Scan -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
globalState?.tapWalletManager
|
||||
?.onCardScanned(result.data, true)
|
||||
if (walletState?.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData -> {
|
||||
scope.launch {
|
||||
val scanNoteResponse = globalState?.scanNoteResponse ?: return@launch
|
||||
if (!walletState?.wallets.isNullOrEmpty()) {
|
||||
globalState.tapWalletManager.reloadData(scanNoteResponse)
|
||||
} else {
|
||||
globalState.tapWalletManager.loadData(scanNoteResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
is NetworkStateChanged -> {
|
||||
globalState?.scanNoteResponse?.let { scanNoteResponse ->
|
||||
store.dispatch(WalletAction.CheckSignedHashes.CheckHashesCountOnline)
|
||||
scope.launch {
|
||||
globalState.tapWalletManager.loadData(scanNoteResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
action.context.copyToClipboard(action.address)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
is WalletAction.ShareAddress -> {
|
||||
action.context.shareText(action.address)
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
val uri = Uri.parse(action.exploreUrl)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
ContextCompat.startActivity(action.context, intent, null)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
val newAction = prepareSendAction(action.amount, state()?.walletState)
|
||||
store.dispatch(newAction)
|
||||
if (newAction is PrepareSendScreen) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
|
||||
val selectedWalletData = state?.getSelectedWalletData()
|
||||
val currency = selectedWalletData?.currencyData?.currencySymbol
|
||||
val wallet = currency?.let { state.getWalletManager(currency)?.wallet }
|
||||
|
||||
return if (amount != null) {
|
||||
if (amount.type is AmountType.Token) {
|
||||
prepareSendActionForToken(amount, state, selectedWalletData, wallet)
|
||||
} else {
|
||||
PrepareSendScreen(amount, selectedWalletData?.fiatRate)
|
||||
}
|
||||
} else {
|
||||
val amounts = wallet?.amounts?.toSendableAmounts()
|
||||
if (currency != null && state.isMultiwalletAllowed) {
|
||||
val amountToSend = amounts?.find { it.currencySymbol == currency }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
if (amountToSend.type is AmountType.Token) {
|
||||
prepareSendActionForToken(amount, state, selectedWalletData, wallet)
|
||||
} else {
|
||||
PrepareSendScreen(amountToSend, selectedWalletData.fiatRate)
|
||||
}
|
||||
} else {
|
||||
if (amounts?.size ?: 0 > 1) {
|
||||
WalletAction.Send.ChooseCurrency(amounts)
|
||||
} else {
|
||||
val amountToSend = amounts?.first()
|
||||
PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareSendActionForToken(
|
||||
amount: Amount?, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?
|
||||
): PrepareSendScreen {
|
||||
val coinRate = state?.getWalletData(wallet?.blockchain?.currency)?.fiatRate
|
||||
val tokenRate = if (state?.isMultiwalletAllowed == true) {
|
||||
selectedWalletData?.fiatRate
|
||||
} else {
|
||||
selectedWalletData?.currencyData?.token?.fiatRate
|
||||
}
|
||||
return PrepareSendScreen(
|
||||
wallet?.amounts?.get(AmountType.Coin), coinRate,
|
||||
amount, tokenRate)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
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
|
||||
|
||||
class MultiWalletReducer {
|
||||
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.MultiWallet.AddWalletManagers -> {
|
||||
state.copy(
|
||||
walletManagers = action.walletManagers
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchains -> {
|
||||
val wallets = action.blockchains.map { blockchain ->
|
||||
val wallet = state.getWalletManager(blockchain.currency)?.wallet
|
||||
val cardToken = if (!state.isMultiwalletAllowed) {
|
||||
wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
blockchain.fullName,
|
||||
currencySymbol = blockchain.currency,
|
||||
token = cardToken
|
||||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
blockchain = blockchain
|
||||
)
|
||||
}
|
||||
|
||||
val selectedWallet = if (!state.isMultiwalletAllowed) {
|
||||
wallets[0].currencyData.currencySymbol
|
||||
} else {
|
||||
state.selectedWallet
|
||||
}
|
||||
state.copy(
|
||||
wallets = wallets,
|
||||
selectedWallet = selectedWallet
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
val wallet = state.getWalletManager(action.blockchain.currency)?.wallet
|
||||
val walletData = WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
action.blockchain.fullName,
|
||||
currencySymbol = action.blockchain.currency,
|
||||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
blockchain = action.blockchain
|
||||
)
|
||||
val newState = state.copy(wallets = state.replaceWalletInWallets(walletData))
|
||||
if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
|
||||
OnWalletLoadedReducer().reduce(wallet, newState)
|
||||
} else {
|
||||
newState
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
if (!state.isMultiwalletAllowed) return state
|
||||
val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet)
|
||||
val wallets = action.tokens.map { token ->
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
currency = token.name,
|
||||
currencySymbol = token.symbol
|
||||
),
|
||||
walletAddresses = walletAddresses,
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
token = token
|
||||
)
|
||||
}
|
||||
state.copy(wallets = state.wallets + wallets)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
if (!state.isMultiwalletAllowed) return state
|
||||
val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet)
|
||||
val wallet = WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
currency = action.token.name,
|
||||
currencySymbol = action.token.symbol
|
||||
),
|
||||
walletAddresses = walletAddresses,
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
token = action.token
|
||||
)
|
||||
val wallets = state.replaceWalletInWallets(wallet)
|
||||
state.copy(wallets = wallets)
|
||||
}
|
||||
is WalletAction.MultiWallet.TokenLoaded -> {
|
||||
val pendingTransactions = state.getWalletManager(Blockchain.Ethereum.currency)
|
||||
?.wallet?.let { wallet ->
|
||||
wallet.recentTransactions.toPendingTransactions(wallet.address)
|
||||
} ?: emptyList()
|
||||
|
||||
val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val tokenWalletData = state.getWalletData(action.amount.currencySymbol)
|
||||
val newTokenWalletData = tokenWalletData?.copy(
|
||||
currencyData = tokenWalletData.currencyData.copy(
|
||||
status = balanceStatus,
|
||||
amount = action.amount.value?.toFormattedCurrencyString(
|
||||
action.amount.decimals, action.amount.currencySymbol
|
||||
),
|
||||
fiatAmount = tokenWalletData.fiatRate?.let {
|
||||
action.amount.value
|
||||
?.toFiatString(it, store.state.globalState.appCurrency)
|
||||
}
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
val wallets = state.replaceWalletInWallets(newTokenWalletData)
|
||||
state.copy(wallets = wallets)
|
||||
}
|
||||
is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
|
||||
state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
|
||||
|
||||
is WalletAction.MultiWallet.SelectWallet ->
|
||||
state.copy(selectedWallet = action.walletData?.currencyData?.currencySymbol)
|
||||
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
state.copy(wallets = state.wallets.filterNot {
|
||||
it.currencyData.currencySymbol == action.walletData.currencyData.currencySymbol
|
||||
})
|
||||
}
|
||||
is WalletAction.MultiWallet.SetPrimaryBlockchain ->
|
||||
state.copy(primaryBlockchain = action.blockchain)
|
||||
|
||||
is WalletAction.MultiWallet.SetPrimaryToken ->
|
||||
state.copy(primaryToken = action.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
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 java.math.RoundingMode
|
||||
|
||||
class OnWalletLoadedReducer {
|
||||
fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState {
|
||||
if (!walletState.isMultiwalletAllowed) {
|
||||
return onSingleWalletLoaded(wallet, walletState, topUpAllowed)
|
||||
} else {
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val amount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (walletState.getWalletData(wallet.blockchain.currency) == null && amount?.isZero() != false) {
|
||||
return walletState
|
||||
}
|
||||
if (wallet.blockchain != Blockchain.Ethereum) {
|
||||
val formattedAmount = amount?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val walletData = walletState.getWalletData(wallet.blockchain.currency)
|
||||
?: WalletData()
|
||||
val newWalletData = walletData.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = balanceStatus, currency = wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
amount = formattedAmount,
|
||||
fiatAmount = walletData.fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
val wallets = walletState.replaceWalletInWallets(newWalletData)
|
||||
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
return walletState.copy(
|
||||
state = state, wallets = wallets
|
||||
)
|
||||
} else {
|
||||
val formattedAmount = amount?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
|
||||
val ethereumWalletData = walletState.getWalletData(wallet.blockchain.currency)
|
||||
?: WalletData()
|
||||
val newEthereumWalletData = ethereumWalletData.copy(
|
||||
currencyData = ethereumWalletData.currencyData.copy(
|
||||
status = balanceStatus,
|
||||
amount = formattedAmount,
|
||||
currency = wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
fiatAmount = ethereumWalletData.fiatRate?.let {
|
||||
amount?.toFiatString(it, fiatCurrencySymbol)
|
||||
}
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
|
||||
val tokens = wallet.getTokens().mapNotNull { token ->
|
||||
val tokenWalletData = walletState.getWalletData(token.symbol)
|
||||
tokenWalletData?.copy(
|
||||
currencyData = tokenWalletData.currencyData.copy(
|
||||
status = balanceStatus,
|
||||
amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString(
|
||||
token.decimals, token.symbol
|
||||
),
|
||||
fiatAmount = tokenWalletData.fiatRate?.let {
|
||||
wallet.getTokenAmount(token)?.value
|
||||
?.toFiatString(it, fiatCurrencySymbol)
|
||||
}
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
}
|
||||
val wallets = walletState.replaceSomeWallets((tokens + newEthereumWalletData).filterNotNull())
|
||||
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
return walletState.copy(
|
||||
state = state, wallets = wallets
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
|
||||
): WalletState {
|
||||
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
|
||||
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val token = wallet.getFirstToken()
|
||||
val tokenData = if (token != null) {
|
||||
val tokenAmount = wallet.getTokenAmount(token)
|
||||
if (tokenAmount != null) {
|
||||
val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
|
||||
val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
|
||||
tokenAmount.currencySymbol, tokenFiatAmount)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val amount = wallet.amounts[AmountType.Coin]?.value
|
||||
val formattedAmount = amount?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
val fiatRate = walletState.primaryWallet?.fiatRate
|
||||
val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val walletData = walletState.primaryWallet?.copy(
|
||||
currencyData = BalanceWidgetData(
|
||||
balanceStatus, wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
formattedAmount,
|
||||
token = tokenData,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountRaw = fiatAmountRaw
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
|
||||
)
|
||||
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
|
||||
return walletState.copy(
|
||||
state = ProgressState.Done, wallets = wallets
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class TwinsReducer {
|
||||
fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
state.copy(
|
||||
twinCardsState = TwinCardsState(
|
||||
secondCardId = action.secondCardId,
|
||||
cardNumber = action.number,
|
||||
showTwinOnboarding = state.twinCardsState?.showTwinOnboarding
|
||||
?: false,
|
||||
isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.ShowOnboarding -> {
|
||||
state.copy(
|
||||
twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true)
|
||||
?: TwinCardsState(null, null,
|
||||
showTwinOnboarding = true,
|
||||
isCreatingTwinCardsAllowed = false)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
state.copy(
|
||||
twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.wallet.R
|
|||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
import kotlinx.android.synthetic.main.layout_balance.*
|
||||
import kotlinx.android.synthetic.main.layout_balance_error.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
enum class BalanceStatus {
|
||||
VerifiedOnline,
|
||||
|
|
@ -25,6 +26,7 @@ data class BalanceWidgetData(
|
|||
val currencySymbol: String? = null,
|
||||
val amount: String? = null,
|
||||
val fiatAmount: String? = null,
|
||||
val fiatAmountRaw: BigDecimal? = null,
|
||||
val token: TokenData? = null,
|
||||
val amountToCreateAccount: String? = null,
|
||||
val errorMessage: String? = null
|
||||
|
|
@ -33,14 +35,16 @@ data class BalanceWidgetData(
|
|||
data class TokenData(
|
||||
val amount: String,
|
||||
val tokenSymbol: String,
|
||||
val fiatAmount: String? = null
|
||||
val fiatAmount: String? = null,
|
||||
val fiatRateString: String? = null,
|
||||
val fiatRate: BigDecimal? = null,
|
||||
)
|
||||
|
||||
|
||||
class BalanceWidget(
|
||||
val fragment: Fragment,
|
||||
val data: BalanceWidgetData,
|
||||
val isTwinCard: Boolean,
|
||||
private val isTwinCard: Boolean,
|
||||
) {
|
||||
|
||||
fun setup() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class MultipleAddressUiHelper {
|
||||
companion object {
|
||||
fun typeToId(type: AddressType): Int {
|
||||
return when (type) {
|
||||
is BitcoinAddressType.Legacy -> R.id.chip_legacy
|
||||
is BitcoinAddressType.Segwit -> R.id.chip_default
|
||||
is CardanoAddressType.Byron -> R.id.chip_legacy
|
||||
is CardanoAddressType.Shelley -> R.id.chip_default
|
||||
else -> View.NO_ID
|
||||
}
|
||||
}
|
||||
|
||||
fun idToType(id: Int, currencyName: CryptoCurrencyName?): AddressType? {
|
||||
val blockchain = currencyName?.let { currency ->
|
||||
Blockchain.fromCurrency(currency)
|
||||
}
|
||||
return idToType(id, blockchain)
|
||||
}
|
||||
|
||||
fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
|
||||
return when (id) {
|
||||
R.id.chip_default -> {
|
||||
when (blockchain) {
|
||||
Blockchain.Bitcoin -> BitcoinAddressType.Segwit
|
||||
Blockchain.CardanoShelley -> CardanoAddressType.Shelley
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
R.id.chip_legacy -> {
|
||||
when (blockchain) {
|
||||
Blockchain.Bitcoin -> BitcoinAddressType.Legacy
|
||||
Blockchain.CardanoShelley -> CardanoAddressType.Byron
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_details_twin_cards.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet_details.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet_details.rv_pending_transaction
|
||||
import kotlinx.android.synthetic.main.fragment_wallet_details.toolbar
|
||||
import kotlinx.android.synthetic.main.item_currency_wallet.view.*
|
||||
import kotlinx.android.synthetic.main.layout_balance_error.*
|
||||
import kotlinx.android.synthetic.main.layout_balance_wallet_details.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_details.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber<WalletState> {
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.walletState == newState.walletState
|
||||
}.select { it.walletState }
|
||||
}
|
||||
store.dispatch(WalletAction.UpdateWallet())
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
|
||||
setupTransactionsRecyclerView()
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
|
||||
private fun setupTransactionsRecyclerView() {
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
rv_pending_transaction.layoutManager = LinearLayoutManager(requireContext())
|
||||
rv_pending_transaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
btn_confirm.text = getString(R.string.wallet_button_send)
|
||||
btn_confirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
|
||||
|
||||
btn_share.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) }
|
||||
|
||||
btn_top_up.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: WalletState) {
|
||||
if (activity == null) return
|
||||
if (state.selectedWallet == null) return
|
||||
val selectedWallet = state.getSelectedWalletData() ?: return
|
||||
|
||||
tv_currency_title.text = selectedWallet.currencyData.currency
|
||||
|
||||
showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
|
||||
setupAddressCard(selectedWallet)
|
||||
setupBalanceData(selectedWallet.currencyData)
|
||||
|
||||
btn_confirm.isEnabled = selectedWallet.mainButton.enabled
|
||||
btn_copy.setOnClickListener {
|
||||
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
|
||||
}
|
||||
}
|
||||
btn_share.setOnClickListener {
|
||||
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
|
||||
}
|
||||
}
|
||||
|
||||
handleDialogs(state.walletDialog)
|
||||
handleCurrencyIcon(selectedWallet)
|
||||
|
||||
srl_wallet_details.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
selectedWallet.topUpState.allowed,
|
||||
selectedWallet.currencyData.currencySymbol
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
srl_wallet_details.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(wallet: WalletData) {
|
||||
val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
|
||||
if (blockchain != null && blockchain != Blockchain.Unknown) {
|
||||
tv_token_letter.text = null
|
||||
iv_currency.colorFilter = null
|
||||
iv_currency.setImageResource(blockchain.getIconRes())
|
||||
} else {
|
||||
tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
|
||||
wallet.token?.getColor()?.let { iv_currency.setColorFilter(it) }
|
||||
iv_currency.setImageResource(R.drawable.shape_circle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
pendingTransactionAdapter.submitList(pendingTransactions)
|
||||
rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupAddressCard(state: WalletData) {
|
||||
if (state.walletAddresses != null) {
|
||||
if (state.shouldShowMultipleAddress()) {
|
||||
(card_balance as? ViewGroup)?.beginDelayedTransition()
|
||||
chip_group_address_type.show()
|
||||
chip_group_address_type.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
|
||||
|
||||
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
chip_group_address_type.hide()
|
||||
}
|
||||
tv_address.text = state.walletAddresses.selectedAddress.address
|
||||
tv_explore?.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(
|
||||
state.walletAddresses.selectedAddress.exploreUrl,
|
||||
requireContext()))
|
||||
}
|
||||
iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.address.toQrCode())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBalanceData(data: BalanceWidgetData) {
|
||||
when (data.status) {
|
||||
BalanceStatus.Loading -> {
|
||||
l_balance.show()
|
||||
l_balance_error.hide()
|
||||
tv_fiat_amount.hide()
|
||||
tv_amount.text = ""
|
||||
|
||||
showStatus(R.id.tv_status_loading)
|
||||
showBalanceWithoutToken(data, false)
|
||||
}
|
||||
BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> {
|
||||
l_balance.show()
|
||||
l_balance_error.hide()
|
||||
val statusView = if (data.status == BalanceStatus.VerifiedOnline) {
|
||||
R.id.tv_status_verified
|
||||
} else {
|
||||
tv_status_error.text =
|
||||
getText(R.string.wallet_balance_tx_in_progress)
|
||||
R.id.group_error
|
||||
}
|
||||
showStatus(statusView)
|
||||
tv_status_error_message.hide()
|
||||
showBalanceWithoutToken(data, true)
|
||||
}
|
||||
BalanceStatus.Unreachable -> {
|
||||
l_balance.show()
|
||||
l_balance_error.hide()
|
||||
tv_fiat_amount.hide()
|
||||
|
||||
tv_amount.text = ""
|
||||
|
||||
tv_status_error_message.text = data.errorMessage
|
||||
tv_status_error.text =
|
||||
getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
|
||||
showStatus(R.id.group_error)
|
||||
tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
|
||||
}
|
||||
|
||||
BalanceStatus.NoAccount -> {
|
||||
l_balance.hide()
|
||||
l_balance_error.show()
|
||||
tv_error_title.text = getText(R.string.wallet_error_no_account)
|
||||
tv_error_descriptions.text =
|
||||
getString(
|
||||
R.string.wallet_error_no_account_subtitle_format,
|
||||
data.amountToCreateAccount, data.currencySymbol
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStatus(@IdRes viewRes: Int) {
|
||||
group_error.show(viewRes == R.id.group_error)
|
||||
tv_status_loading.show(viewRes == R.id.tv_status_loading)
|
||||
tv_status_verified.show(viewRes == R.id.tv_status_verified)
|
||||
}
|
||||
|
||||
private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
|
||||
tv_amount.text = if (showAmount) data.amount else ""
|
||||
if (showAmount) {
|
||||
tv_fiat_amount.show()
|
||||
tv_fiat_amount.text = data.fiatAmount
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: WalletDialog?) {
|
||||
when (walletDialog) {
|
||||
is WalletDialog.SelectAmountToSendDialog -> {
|
||||
if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
|
||||
this.show(walletDialog.amounts)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.meny_remove -> {
|
||||
store.state.walletState.getSelectedWalletData()?.let { walletData ->
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData))
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
true
|
||||
}
|
||||
false
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
if (store.state.walletState.canBeRemoved(store.state.walletState.getSelectedWalletData())) {
|
||||
inflater.inflate(R.menu.wallet_details, menu)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.Menu
|
||||
import android.view.Menu.NONE
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -12,39 +14,32 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
|
||||
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.WalletView
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_address.*
|
||||
import kotlinx.android.synthetic.main.layout_send_fee.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
|
||||
class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<WalletState> {
|
||||
|
||||
private var dialog: Dialog? = null
|
||||
private var snackbar: Snackbar? = null
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||
|
||||
private var walletView: WalletView = SingleWalletView()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
|
@ -65,12 +60,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
oldState.walletState == newState.walletState
|
||||
}.select { it.walletState }
|
||||
}
|
||||
store.dispatch(WalletAction.UpdateWallet)
|
||||
walletView.setFragment(this)
|
||||
store.dispatch(WalletAction.UpdateWallet())
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
walletView.removeFragment()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
@ -81,7 +78,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
setupWarningsRecyclerView()
|
||||
setupTransactionsRecyclerView()
|
||||
walletView.changeWalletView(this)
|
||||
}
|
||||
|
||||
private fun setupWarningsRecyclerView() {
|
||||
|
|
@ -92,79 +89,44 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
rv_warning_messages.adapter = warningsAdapter
|
||||
}
|
||||
|
||||
private fun setupTransactionsRecyclerView() {
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
rv_pending_transaction.layoutManager = LinearLayoutManager(context)
|
||||
rv_pending_transaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: WalletState) {
|
||||
if (activity == null) return
|
||||
|
||||
state.twinCardsState?.cardNumber?.let { cardNumber ->
|
||||
tv_twin_card_number.show()
|
||||
iv_twin_card.show()
|
||||
val number = when (cardNumber) {
|
||||
TwinCardNumber.First -> "1"
|
||||
TwinCardNumber.Second -> "2"
|
||||
}
|
||||
tv_twin_card_number.text = getString(R.string.wallet_twins_chip_format, number)
|
||||
}
|
||||
if (state.twinCardsState?.cardNumber == null) {
|
||||
tv_twin_card_number.hide()
|
||||
iv_twin_card.hide()
|
||||
if (state.isMultiwalletAllowed && walletView is SingleWalletView) {
|
||||
walletView = MultiWalletView()
|
||||
walletView.changeWalletView(this)
|
||||
} else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
|
||||
walletView = SingleWalletView()
|
||||
walletView.changeWalletView(this)
|
||||
}
|
||||
walletView.onNewState(state)
|
||||
|
||||
if (!state.showDetails) {
|
||||
if (!state.shouldShowDetails) {
|
||||
toolbar.menu.removeItem(R.id.details_menu)
|
||||
} else if (toolbar.menu.findItem(R.id.details_menu) == null) {
|
||||
toolbar.menu.add(R.menu.wallet, R.id.details_menu, NONE, R.string.details_title)
|
||||
}
|
||||
|
||||
srl_wallet.setOnRefreshListener {
|
||||
store.dispatch(WalletAction.LoadData)
|
||||
}
|
||||
|
||||
setupNoInternetHandling(state)
|
||||
|
||||
setupButtons(state)
|
||||
setupAddressCard(state)
|
||||
setupCardImage(state.cardImage)
|
||||
|
||||
warningsAdapter.submitList(state.mainWarningsList)
|
||||
rv_warning_messages.show(state.mainWarningsList.isNotEmpty())
|
||||
showWarningsIfPresent(state.mainWarningsList)
|
||||
|
||||
pendingTransactionAdapter.submitList(state.pendingTransactions)
|
||||
rv_pending_transaction.show(state.pendingTransactions.isNotEmpty())
|
||||
srl_wallet.setOnRefreshListener {
|
||||
if (state.state != ProgressState.Loading) {
|
||||
store.dispatch(WalletAction.LoadData)
|
||||
}
|
||||
}
|
||||
|
||||
handleDialogs(state.walletDialog)
|
||||
|
||||
l_balance.show()
|
||||
BalanceWidget(this, state.currencyData, state.twinCardsState != null).setup()
|
||||
|
||||
if (state.state == ProgressState.Done) {
|
||||
if (state.state != ProgressState.Loading) {
|
||||
srl_wallet.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
when (state.payIdData.payIdState) {
|
||||
PayIdState.Disabled, PayIdState.Loading -> group_payid.hide()
|
||||
PayIdState.NotCreated -> {
|
||||
group_payid.show()
|
||||
tv_create_payid.show()
|
||||
tv_create_payid.setOnClickListener { store.dispatch(WalletAction.CreatePayId) }
|
||||
tv_payid_address.hide()
|
||||
}
|
||||
PayIdState.Created -> {
|
||||
group_payid.show()
|
||||
tv_create_payid.hide()
|
||||
tv_payid_address.show()
|
||||
tv_payid_address.text = state.payIdData.payId
|
||||
|
||||
}
|
||||
PayIdState.ErrorLoading -> {
|
||||
}
|
||||
}
|
||||
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {
|
||||
warningsAdapter.submitList(warnings)
|
||||
rv_warning_messages.show(warnings.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupNoInternetHandling(state: WalletState) {
|
||||
|
|
@ -174,98 +136,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
snackbar = Snackbar.make(
|
||||
coordinator_wallet, getString(R.string.wallet_notification_no_internet),
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
).setAction(getString(R.string.common_retry))
|
||||
{ store.dispatch(WalletAction.LoadData) }
|
||||
.also { it.show() }
|
||||
).setAction(getString(R.string.common_retry)) { store.dispatch(WalletAction.LoadData) }
|
||||
snackbar?.show()
|
||||
}
|
||||
} else {
|
||||
snackbar?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons(state: WalletState) {
|
||||
if (state.topUpState.allowed) {
|
||||
l_buttons_long.hide()
|
||||
l_buttons_short.show()
|
||||
} else {
|
||||
l_buttons_long.show()
|
||||
l_buttons_short.hide()
|
||||
}
|
||||
|
||||
val btnConfirm = if (state.topUpState.allowed) {
|
||||
btn_confirm_short
|
||||
} else {
|
||||
btn_confirm_long
|
||||
}
|
||||
val btnScan = if (state.topUpState.allowed) {
|
||||
btn_scan_short
|
||||
} else {
|
||||
btn_scan_long
|
||||
}
|
||||
|
||||
btnScan.setOnClickListener {
|
||||
store.dispatch(WalletAction.Scan)
|
||||
}
|
||||
|
||||
btn_copy.setOnClickListener { store.dispatch(WalletAction.CopyAddress(requireContext())) }
|
||||
btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) }
|
||||
|
||||
val buttonTitle = when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> R.string.wallet_button_send
|
||||
is WalletMainButton.CreateWalletButton -> {
|
||||
if (state.twinCardsState == null) {
|
||||
R.string.wallet_button_create_wallet
|
||||
} else {
|
||||
R.string.wallet_button_create_twin_wallet
|
||||
}
|
||||
}
|
||||
}
|
||||
btnConfirm.text = getString(buttonTitle)
|
||||
btnConfirm.isEnabled = state.mainButton.enabled
|
||||
|
||||
btnConfirm.setOnClickListener {
|
||||
when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
|
||||
is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
|
||||
}
|
||||
}
|
||||
btn_top_up.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAddressCard(state: WalletState) {
|
||||
if (state.walletAddresses != null) {
|
||||
l_address?.show()
|
||||
if (state.showMultipleAddress) {
|
||||
(l_address as? ViewGroup)?.beginDelayedTransition()
|
||||
chip_group_address_type.show()
|
||||
chip_group_address_type.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
|
||||
|
||||
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
|
||||
MultipleAddressUiHelper.idToType(checkedId, state.wallet?.blockchain)?.let {
|
||||
store.dispatch(WalletAction.ChangeSelectedAddress(it))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chip_group_address_type.hide()
|
||||
}
|
||||
tv_address.text = state.walletAddresses.selectedAddress.address
|
||||
tv_explore?.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(requireContext()))
|
||||
}
|
||||
} else {
|
||||
l_address?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCardImage(cardImage: Artwork?) {
|
||||
Picasso.get()
|
||||
.load(cardImage?.artworkId)
|
||||
|
|
@ -274,47 +152,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
?.into(iv_card)
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: WalletDialog?) {
|
||||
when (walletDialog) {
|
||||
is WalletDialog.QrDialog -> {
|
||||
if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
|
||||
if (dialog == null) dialog = QrDialog(requireContext()).apply {
|
||||
this.showQr(
|
||||
walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletDialog.CreatePayIdDialog -> {
|
||||
when (walletDialog.creatingPayIdState) {
|
||||
CreatingPayIdState.EnterPayId -> {
|
||||
if (dialog == null) dialog = PayIdDialog(requireContext()).apply {
|
||||
this.show()
|
||||
}
|
||||
(dialog as? PayIdDialog)?.stopProgress()
|
||||
}
|
||||
CreatingPayIdState.Waiting -> (dialog as? PayIdDialog)?.showProgress()
|
||||
}
|
||||
}
|
||||
is WalletDialog.SelectAmountToSendDialog -> {
|
||||
if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
|
||||
this.show(walletDialog.amounts)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
|
||||
store.dispatch(DetailsAction.PrepareScreen(
|
||||
scanNoteResponse.card, scanNoteResponse,
|
||||
store.state.walletState.wallet,
|
||||
store.state.walletState.walletManagers.map { it.wallet },
|
||||
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
|
||||
store.state.globalState.appCurrency
|
||||
))
|
||||
|
|
@ -328,41 +172,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
if (store.state.walletState.showDetails) inflater.inflate(R.menu.wallet, menu)
|
||||
if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.wallet, menu)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MultipleAddressUiHelper {
|
||||
companion object {
|
||||
fun typeToId(type: AddressType): Int {
|
||||
return when (type) {
|
||||
is BitcoinAddressType.Legacy -> R.id.chip_legacy
|
||||
is BitcoinAddressType.Segwit -> R.id.chip_default
|
||||
is CardanoAddressType.Byron -> R.id.chip_legacy
|
||||
is CardanoAddressType.Shelley -> R.id.chip_default
|
||||
else -> View.NO_ID
|
||||
}
|
||||
}
|
||||
|
||||
fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
|
||||
return when (id) {
|
||||
R.id.chip_default -> {
|
||||
when (blockchain) {
|
||||
Blockchain.Bitcoin -> BitcoinAddressType.Segwit
|
||||
Blockchain.CardanoShelley -> CardanoAddressType.Shelley
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
R.id.chip_legacy -> {
|
||||
when (blockchain) {
|
||||
Blockchain.Bitcoin -> BitcoinAddressType.Legacy
|
||||
Blockchain.CardanoShelley -> CardanoAddressType.Byron
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
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.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.item_currency_wallet.view.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletAdapter
|
||||
: ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
|
||||
}
|
||||
|
||||
fun submitList(list: List<WalletData>, primaryBlockchain: Blockchain?, primaryToken: Token? = null) {
|
||||
val listModified = list.toMutableList()
|
||||
val primaryBlockchainWallet = when (
|
||||
val index = listModified.indexOfFirst { it.blockchain == primaryBlockchain }
|
||||
) {
|
||||
-1 -> null
|
||||
else -> listModified.removeAt(index)
|
||||
}
|
||||
|
||||
|
||||
val primaryTokenWallet = if (primaryToken == null) null else when (
|
||||
val index = listModified.indexOfFirst { it.token == primaryToken }
|
||||
) {
|
||||
-1 -> null
|
||||
else -> listModified.removeAt(index)
|
||||
}
|
||||
|
||||
if (list.all { it.currencyData.fiatAmount == null }) {
|
||||
val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + listModified
|
||||
super.submitList(sortedList)
|
||||
return
|
||||
}
|
||||
|
||||
val sorted = listModified.sortedWith(
|
||||
compareByDescending<WalletData> { it.currencyData.fiatAmountRaw ?: BigDecimal.ZERO }
|
||||
.thenBy { it.currencyData.currencySymbol }
|
||||
)
|
||||
val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + sorted
|
||||
super.submitList(sortedList)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
|
||||
val layout = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_currency_wallet, parent, false)
|
||||
return WalletsViewHolder(layout)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletsViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<WalletData>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
class WalletsViewHolder(val view: View) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun bind(wallet: WalletData) {
|
||||
view.tv_currency.text = wallet.currencyData.currency
|
||||
view.tv_amount.text = wallet.currencyData.amount
|
||||
if (view.tv_amount.isEllipsized()) {
|
||||
val allowedSize = view.tv_amount.text.length - 4
|
||||
view.tv_amount.text = wallet.currencyData.amount?.ellipsizeBeforeSpace(allowedSize)
|
||||
}
|
||||
view.tv_amount_fiat.text = wallet.currencyData.fiatAmount
|
||||
view.tv_exchange_rate.text = wallet.fiatRateString
|
||||
view.card_wallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
}
|
||||
val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
|
||||
if (blockchain != null && blockchain != Blockchain.Unknown) {
|
||||
view.tv_token_letter.text = null
|
||||
view.iv_currency.colorFilter = null
|
||||
view.iv_currency.setImageResource(blockchain.getIconRes())
|
||||
} else {
|
||||
view.tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
|
||||
wallet.token?.getColor()?.let { view.iv_currency.setColorFilter(it) }
|
||||
view.iv_currency.setImageResource(R.drawable.shape_circle)
|
||||
}
|
||||
when (wallet.currencyData.status) {
|
||||
BalanceStatus.VerifiedOnline -> hideWarning()
|
||||
BalanceStatus.Loading -> {
|
||||
hideWarning()
|
||||
if (wallet.currencyData.amount == null) {
|
||||
view.tv_exchange_rate.text = view.context.getText(R.string.wallet_balance_loading)
|
||||
}
|
||||
}
|
||||
BalanceStatus.TransactionInProgress ->
|
||||
showWarning(view.context.getString(R.string.wallet_balance_tx_in_progress))
|
||||
BalanceStatus.Unreachable ->
|
||||
showWarning(view.context.getString(R.string.wallet_balance_blockchain_unreachable))
|
||||
|
||||
BalanceStatus.NoAccount ->
|
||||
showWarning(view.context.getString(R.string.wallet_error_no_account))
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWarning(message: String) {
|
||||
toggleWarning(true)
|
||||
view.tv_status_error_message.text = message
|
||||
}
|
||||
|
||||
private fun hideWarning() {
|
||||
toggleWarning(false)
|
||||
}
|
||||
|
||||
private fun toggleWarning(show: Boolean) {
|
||||
view.tv_exchange_rate.show(!show)
|
||||
view.tv_status_error_message.show(show)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.notificationsHandler
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.dialog_create_payid.*
|
||||
|
||||
class PayIdDialog(context: Context) : Dialog(context) {
|
||||
|
||||
init {
|
||||
this.setContentView(R.layout.dialog_create_payid)
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
notificationsHandler?.replaceBaseLayout(this.cl_payid_dialog)
|
||||
this.setOnDismissListener {
|
||||
notificationsHandler?.returnBaseLayout()
|
||||
store.dispatch(WalletAction.CreatePayId.Cancel)
|
||||
}
|
||||
this.btn_create_payid?.setOnClickListener {
|
||||
if (this.et_payid.text.isNullOrBlank()) {
|
||||
store.dispatch(WalletAction.CreatePayId.EmptyField)
|
||||
} else {
|
||||
val payid = this.et_payid.text!!.toString() +
|
||||
this.context.getString(R.string.wallet_create_payid_domain)
|
||||
store.dispatch(WalletAction.CreatePayId.CompleteCreatingPayId(payid))
|
||||
}
|
||||
}
|
||||
super.show()
|
||||
}
|
||||
|
||||
fun showProgress() {
|
||||
this.btn_create_payid?.visibility = View.INVISIBLE
|
||||
this.pb_create_payid?.show()
|
||||
}
|
||||
|
||||
fun stopProgress() {
|
||||
this.btn_create_payid?.show()
|
||||
this.pb_create_payid?.hide()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_balance_error.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
|
||||
|
||||
|
||||
class MultiWalletView : WalletView {
|
||||
|
||||
private var fragment: WalletFragment? = null
|
||||
private lateinit var walletsAdapter: WalletAdapter
|
||||
|
||||
override fun changeWalletView(fragment: WalletFragment) {
|
||||
setFragment(fragment)
|
||||
onViewCreated()
|
||||
showMultiWalletView()
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
|
||||
private fun showMultiWalletView() {
|
||||
val fragment = fragment ?: return
|
||||
fragment.tv_twin_card_number.hide()
|
||||
fragment.iv_twin_card.hide()
|
||||
fragment.rv_pending_transaction.hide()
|
||||
fragment.l_card_balance.hide()
|
||||
fragment.l_address.hide()
|
||||
fragment.l_buttons_short.hide()
|
||||
fragment.l_buttons_long.hide()
|
||||
fragment.btn_scan_multiwallet?.show()
|
||||
fragment.rv_multiwallet.show()
|
||||
fragment.btn_add_token.show()
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
val fragment = fragment ?: return
|
||||
fragment.btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
|
||||
}
|
||||
|
||||
override fun setFragment(fragment: WalletFragment) {
|
||||
this.fragment = fragment
|
||||
}
|
||||
|
||||
override fun removeFragment() {
|
||||
this.fragment = null
|
||||
}
|
||||
|
||||
override fun onViewCreated() {
|
||||
setupWalletsRecyclerView()
|
||||
}
|
||||
|
||||
private fun setupWalletsRecyclerView() {
|
||||
val fragment = fragment ?: return
|
||||
walletsAdapter = WalletAdapter()
|
||||
walletsAdapter.setHasStableIds(true)
|
||||
fragment.rv_multiwallet.layoutManager = LinearLayoutManager(fragment.requireContext())
|
||||
fragment.rv_multiwallet.adapter = walletsAdapter
|
||||
}
|
||||
|
||||
override fun onNewState(state: WalletState) {
|
||||
val fragment = fragment ?: return
|
||||
|
||||
walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken)
|
||||
|
||||
fragment.btn_add_token?.setOnClickListener {
|
||||
store.dispatch(TokensAction.LoadCurrencies)
|
||||
store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
handleErrorStates(state, fragment)
|
||||
}
|
||||
|
||||
private fun handleErrorStates(state: WalletState, fragment: WalletFragment) {
|
||||
when (state.primaryWallet?.currencyData?.status) {
|
||||
BalanceStatus.EmptyCard -> {
|
||||
showErrorState(
|
||||
fragment,
|
||||
fragment.getText(R.string.wallet_error_empty_card),
|
||||
fragment.getString(R.string.wallet_error_empty_card_subtitle)
|
||||
)
|
||||
configureButtonsForEmptyWalletState(fragment)
|
||||
}
|
||||
BalanceStatus.UnknownBlockchain -> {
|
||||
showErrorState(
|
||||
fragment,
|
||||
fragment.getText(R.string.wallet_error_unsupported_blockchain),
|
||||
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorState(
|
||||
fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence
|
||||
) {
|
||||
fragment.l_card_balance.show()
|
||||
fragment.l_balance.hide()
|
||||
fragment.l_balance_error.show()
|
||||
fragment.rv_multiwallet.hide()
|
||||
fragment.btn_add_token.hide()
|
||||
fragment.tv_error_title.text = errorTitle
|
||||
fragment.tv_error_descriptions.text = errorDescription
|
||||
}
|
||||
|
||||
private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) {
|
||||
fragment.btn_scan_multiwallet.hide()
|
||||
fragment.l_buttons_long.show()
|
||||
fragment.btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
|
||||
fragment.btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
|
||||
fragment.btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.app.Dialog
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidget
|
||||
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_address.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
|
||||
|
||||
class SingleWalletView : WalletView {
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private var fragment: WalletFragment? = null
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
override fun setFragment(fragment: WalletFragment) {
|
||||
this.fragment = fragment
|
||||
}
|
||||
|
||||
override fun removeFragment() {
|
||||
fragment = null
|
||||
}
|
||||
|
||||
override fun changeWalletView(fragment: WalletFragment) {
|
||||
setFragment(fragment)
|
||||
onViewCreated()
|
||||
showSingleWalletView()
|
||||
}
|
||||
|
||||
private fun showSingleWalletView() {
|
||||
val fragment = fragment ?: return
|
||||
fragment.rv_multiwallet.hide()
|
||||
fragment.btn_add_token.hide()
|
||||
fragment.btn_scan_multiwallet?.hide()
|
||||
fragment.rv_pending_transaction.hide()
|
||||
fragment.l_card_balance.show()
|
||||
fragment.l_address.show()
|
||||
}
|
||||
|
||||
override fun onViewCreated() {
|
||||
setupTransactionsRecyclerView()
|
||||
}
|
||||
|
||||
|
||||
private fun setupTransactionsRecyclerView() {
|
||||
val fragment = fragment ?: return
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
fragment.rv_pending_transaction.layoutManager = LinearLayoutManager(fragment.requireContext())
|
||||
fragment.rv_pending_transaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
override fun onNewState(state: WalletState) {
|
||||
setupTwinCards(state.twinCardsState)
|
||||
state.primaryWallet ?: return
|
||||
setupButtons(state.primaryWallet, state.twinCardsState != null)
|
||||
setupAddressCard(state.primaryWallet)
|
||||
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
|
||||
setupBalance(state, state.primaryWallet)
|
||||
handleDialogs(state.walletDialog)
|
||||
}
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
pendingTransactionAdapter.submitList(pendingTransactions)
|
||||
fragment?.rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
|
||||
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
|
||||
fragment?.apply {
|
||||
this.l_balance.show()
|
||||
BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTwinCards(twinCardsState: TwinCardsState?) {
|
||||
fragment?.apply {
|
||||
twinCardsState?.cardNumber?.let { cardNumber ->
|
||||
this.tv_twin_card_number.show()
|
||||
this.iv_twin_card.show()
|
||||
val number = when (cardNumber) {
|
||||
TwinCardNumber.First -> "1"
|
||||
TwinCardNumber.Second -> "2"
|
||||
}
|
||||
this.tv_twin_card_number.text =
|
||||
this.getString(R.string.wallet_twins_chip_format, number)
|
||||
}
|
||||
if (twinCardsState?.cardNumber == null) {
|
||||
this.tv_twin_card_number.hide()
|
||||
this.iv_twin_card.hide()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setupButtons(state: WalletData, isTwinsWallet: Boolean) {
|
||||
val fragment = fragment ?: return
|
||||
|
||||
setupButtonsType(state, fragment)
|
||||
|
||||
val btnConfirm = if (state.topUpState.allowed) {
|
||||
fragment.btn_confirm_short
|
||||
} else {
|
||||
fragment.btn_confirm_long
|
||||
}
|
||||
val btnScan = if (state.topUpState.allowed) {
|
||||
fragment.btn_scan_short
|
||||
} else {
|
||||
fragment.btn_scan_long
|
||||
}
|
||||
|
||||
setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
|
||||
|
||||
btnScan.setOnClickListener {
|
||||
store.dispatch(WalletAction.Scan)
|
||||
}
|
||||
|
||||
fragment.btn_copy.setOnClickListener {
|
||||
state.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
|
||||
}
|
||||
}
|
||||
fragment.btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) }
|
||||
|
||||
fragment.btn_top_up.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonsType(state: WalletData, fragment: WalletFragment) {
|
||||
if (state.topUpState.allowed) {
|
||||
fragment.l_buttons_long.hide()
|
||||
fragment.l_buttons_short.show()
|
||||
} else {
|
||||
fragment.l_buttons_long.show()
|
||||
fragment.l_buttons_short.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupConfirmButton(
|
||||
state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean
|
||||
) {
|
||||
val buttonTitle = when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> R.string.wallet_button_send
|
||||
is WalletMainButton.CreateWalletButton -> {
|
||||
if (!isTwinsWallet) {
|
||||
R.string.wallet_button_create_wallet
|
||||
} else {
|
||||
R.string.wallet_button_create_twin_wallet
|
||||
}
|
||||
}
|
||||
}
|
||||
btnConfirm.text = fragment.getString(buttonTitle)
|
||||
btnConfirm.isEnabled = state.mainButton.enabled
|
||||
btnConfirm.setOnClickListener {
|
||||
when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
|
||||
is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupAddressCard(state: WalletData) {
|
||||
val fragment = fragment ?: return
|
||||
if (state.walletAddresses != null) {
|
||||
fragment.l_address?.show()
|
||||
if (state.shouldShowMultipleAddress()) {
|
||||
(fragment.l_address as? ViewGroup)?.beginDelayedTransition()
|
||||
fragment.chip_group_address_type.show()
|
||||
fragment.chip_group_address_type.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) fragment.chip_group_address_type.check(checkedId)
|
||||
|
||||
fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
fragment.chip_group_address_type.hide()
|
||||
}
|
||||
fragment.tv_address.text = state.walletAddresses.selectedAddress.address
|
||||
fragment.tv_explore?.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(
|
||||
state.walletAddresses.selectedAddress.exploreUrl,
|
||||
fragment.requireContext()))
|
||||
}
|
||||
} else {
|
||||
fragment.l_address?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: WalletDialog?) {
|
||||
val fragment = fragment ?: return
|
||||
when (walletDialog) {
|
||||
is WalletDialog.QrDialog -> {
|
||||
if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
|
||||
if (dialog == null) dialog = QrDialog(fragment.requireContext()).apply {
|
||||
this.showQr(
|
||||
walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletDialog.SelectAmountToSendDialog -> {
|
||||
if (dialog == null) dialog = AmountToSendDialog(fragment.requireContext()).apply {
|
||||
this.show(walletDialog.amounts)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
||||
interface WalletView {
|
||||
|
||||
fun setFragment(fragment: WalletFragment)
|
||||
|
||||
fun removeFragment()
|
||||
|
||||
fun changeWalletView(fragment: WalletFragment)
|
||||
|
||||
fun onViewCreated()
|
||||
|
||||
fun onNewState(state: WalletState)
|
||||
|
||||
}
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_binance.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_btc.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_btc_cash.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_cardano.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_eth.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_ltc.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_rsk.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_stellar.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_tezos.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_xlm.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_xrp.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_binance.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_btc.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_btc_cash.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_cardano.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_eth.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_ltc.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_rsk.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_stellar.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_tezos.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_xlm.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-ldpi/ic_xrp.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
app/src/main/res/drawable-mdpi/ic_binance.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_btc.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_btc_cash.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_cardano.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_eth.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_ltc.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_rsk.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_stellar.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_tezos.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_xlm.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_xrp.png
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
app/src/main/res/drawable-xhdpi/ic_binance.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_btc.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |