Updated on 2026-08-14
This commit is contained in:
parent
040f53e52e
commit
2f1e813058
74 changed files with 1731 additions and 22861 deletions
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
sealed class Keyboard {
|
||||
data class Opened(val height: Int): Keyboard()
|
||||
object Closed : Keyboard()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun keyboardAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
val view = LocalView.current
|
||||
DisposableEffect(view) {
|
||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
||||
Keyboard.Opened(keypadHeight)
|
||||
} else {
|
||||
Keyboard.Closed
|
||||
}
|
||||
}
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
|
||||
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
|
||||
}
|
||||
}
|
||||
|
||||
return keyboardState
|
||||
}
|
||||
|
|
@ -21,4 +21,59 @@ fun Blockchain.getIconRes(): Int {
|
|||
Blockchain.Stellar -> R.drawable.ic_stellar
|
||||
else -> R.drawable.shape_circle
|
||||
}
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getRoundIconRes(): Int {
|
||||
return when (this) {
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_round
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_round
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance_round
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_round
|
||||
Blockchain.XRP -> R.drawable.ic_xrp_round
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar_round
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> R.drawable.ic_bsc_round
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getGreyedOutIconRes(): Int {
|
||||
return when (this) {
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_no_color
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance_no_color
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
|
||||
Blockchain.XRP -> R.drawable.ic_xrp_no_color
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar_no_color
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_no_color
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> R.drawable.ic_bsc_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.getNetworkName(): String {
|
||||
return when (this) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> "BEP20"
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> "BEP2"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import android.content.res.Resources
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -19,6 +20,7 @@ import androidx.fragment.app.Fragment
|
|||
import com.google.android.material.card.MaterialCardView
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
||||
|
||||
fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(requireContext(), drawableResId)
|
||||
}
|
||||
|
|
@ -94,6 +96,13 @@ fun Context.dpToPixels(dp: Int): Int =
|
|||
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
|
||||
fun Context.pixelsToDp(pixels: Int): Int {
|
||||
return (pixels.toFloat() /
|
||||
(resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
|
||||
.toInt()
|
||||
}
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.tap.domain.extensions.setCustomIconUrl
|
|||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
|
||||
|
|
@ -29,6 +30,9 @@ class CurrenciesRepository(val context: Application) {
|
|||
private val obsoleteTokensAdapter: JsonAdapter<List<ObsoleteTokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
|
||||
)
|
||||
private val currenciesAdapter : JsonAdapter<CurrenciesFromJson> =
|
||||
moshi.adapter(CurrenciesFromJson::class.java)
|
||||
|
||||
|
||||
fun loadCardCurrencies(cardId: String): CardCurrencies? {
|
||||
val blockchains = loadSavedBlockchains(cardId).toMutableSet()
|
||||
|
|
@ -116,18 +120,10 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Token> {
|
||||
val supportedTokens = if (isTestNet) {
|
||||
getSupportedTokens().mapNotNull { it.getTestnetVersion() }
|
||||
} else {
|
||||
getSupportedTokens()
|
||||
}
|
||||
|
||||
val blockchainTokens = supportedTokens.mapNotNull { blockchain ->
|
||||
loadTokensJson(blockchain)?.let { fromJsonToTokensDao(it, blockchain) }
|
||||
}
|
||||
val tokens = blockchainTokens.flatten().map { it.toToken() }
|
||||
return tokens
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Currency> {
|
||||
val fileName = if (isTestNet) "testnet_tokens" else "tokens"
|
||||
val json = context.assets.readJsonFileToString(fileName)
|
||||
return currenciesAdapter.fromJson(json)!!.tokens.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun loadTokensJson(blockchain: Blockchain): String? {
|
||||
|
|
@ -142,7 +138,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
|
||||
private fun getFileName(blockchain: Blockchain): String {
|
||||
return StringBuilder().apply {
|
||||
append(blockchain.id.toLowerCase().replace("/test", ""))
|
||||
append(blockchain.id.lowercase(Locale.getDefault()).replace("/test", ""))
|
||||
append("_tokens")
|
||||
appendIf("_testnet") { blockchain.isTestnet() }
|
||||
}.toString()
|
||||
|
|
|
|||
134
app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt
Normal file
134
app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contracts: List<ContractFromJson>? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ContractFromJson(
|
||||
val networkId: String,
|
||||
val address: String,
|
||||
val decimalCount: Int
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrenciesFromJson(
|
||||
val imageHost: String,
|
||||
val tokens: List<CurrencyFromJson>
|
||||
)
|
||||
|
||||
|
||||
fun List<ContractFromJson>.toContracts(): List<Contract> {
|
||||
return map { Contract.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
data class Currency(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val iconUrl: String,
|
||||
val contracts: List<Contract>?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(currency: CurrencyFromJson): Currency {
|
||||
return Currency(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id),
|
||||
contracts = currency.contracts?.toContracts()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Contract(
|
||||
val networkId: String,
|
||||
val blockchain: Blockchain,
|
||||
val address: String,
|
||||
val decimalCount: Int,
|
||||
val iconUrl: String,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(contract: ContractFromJson): Contract {
|
||||
return Contract(
|
||||
networkId = contract.networkId,
|
||||
blockchain = Blockchain.fromNetworkId(contract.networkId),
|
||||
address = contract.address,
|
||||
decimalCount = contract.decimalCount,
|
||||
iconUrl = getIconUrl(contract.networkId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconUrl(id: String): String {
|
||||
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
|
||||
}
|
||||
|
||||
|
||||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain {
|
||||
return when (networkId) {
|
||||
"avalanche" -> Blockchain.Avalanche
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"polygon-pos" -> Blockchain.Polygon
|
||||
"solana" -> Blockchain.Solana
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"cardano" -> Blockchain.CardanoShelley
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rsk" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"ripple" -> Blockchain.XRP
|
||||
else -> Blockchain.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalaunch"
|
||||
Blockchain.AvalancheTestnet -> "avalaunch"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.CardanoShelley -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "matic-network"
|
||||
Blockchain.PolygonTestnet -> "matic-networks"
|
||||
Blockchain.RSK -> "rsk"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,13 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -35,7 +38,12 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
|
|||
StoriesScreen(
|
||||
homeState,
|
||||
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) }
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) },
|
||||
onSearchTokensClick = {
|
||||
store.dispatch(TokensAction.AllowToAddTokens(false))
|
||||
store.dispatch(TokensAction.LoadCurrencies)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import androidx.compose.foundation.Image
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -16,8 +19,12 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -27,7 +34,8 @@ import kotlin.math.max
|
|||
fun StoriesScreen(
|
||||
homeState: MutableState<HomeState> = mutableStateOf(HomeState()),
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
) {
|
||||
val steps = 6
|
||||
val stepDuration = 8_000
|
||||
|
|
@ -77,7 +85,8 @@ fun StoriesScreen(
|
|||
}
|
||||
)
|
||||
Box(
|
||||
Modifier.weight(1f)
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(isPressed) {
|
||||
detectTapGestures(
|
||||
|
|
@ -106,7 +115,7 @@ fun StoriesScreen(
|
|||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
StoriesProgressBar(
|
||||
|
|
@ -137,15 +146,44 @@ fun StoriesScreen(
|
|||
6 -> StoriesWalletForEveryone()
|
||||
}
|
||||
}
|
||||
HomeButtons(
|
||||
isDarkBackground = isDarkBackground,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(16.dp, 0.dp, 16.dp, 37.dp)
|
||||
.fillMaxWidth(),
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick
|
||||
)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
if (currentStep.value == 4) Button(
|
||||
onClick = onSearchTokensClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.height(48.dp)
|
||||
,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
backgroundColor = Color.White,
|
||||
contentColor = Color(0xFF080C10)
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_search),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.search_tokens_title),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
HomeButtons(
|
||||
isDarkBackground = isDarkBackground,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 37.dp)
|
||||
.fillMaxWidth(),
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,5 +191,5 @@ fun StoriesScreen(
|
|||
@Preview
|
||||
@Composable
|
||||
fun InstagramScreenPreview() {
|
||||
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {})
|
||||
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {})
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
var isAdded: Boolean = false
|
||||
var isLocked: Boolean = false
|
||||
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
|
||||
data class TitleListItem(
|
||||
@StringRes val titleResId: Int,
|
||||
var isContentShown: Boolean = true,
|
||||
val blockchain: Blockchain? = null,
|
||||
) : CurrencyListItem()
|
||||
|
||||
companion object {
|
||||
fun createListOfCurrencies(
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>,
|
||||
): List<CurrencyListItem> {
|
||||
val blockchainsItemList = createBlockchainList(blockchains)
|
||||
val tokenItemsLists = tokens.groupBy { it.blockchain }.map {
|
||||
createTokensList(R.string.add_tokens_subtitle_format, it.key, it.value)
|
||||
}
|
||||
return blockchainsItemList + tokenItemsLists.flatten()
|
||||
}
|
||||
|
||||
private fun createBlockchainList(blockchains: List<Blockchain>): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
return listOf(TitleListItem(blockchainsTitle)) + blockchains.map { BlockchainListItem(it) }
|
||||
}
|
||||
|
||||
private fun createTokensList(
|
||||
@StringRes titleResId: Int,
|
||||
blockchain: Blockchain,
|
||||
tokens: List<Token>
|
||||
): List<CurrencyListItem> {
|
||||
val filteredTokens = tokens.filter {
|
||||
it.blockchain == blockchain || it.blockchain == blockchain.getTestnetVersion()
|
||||
}
|
||||
val tokensListItem = filteredTokens.map { TokenListItem(it) }
|
||||
return listOf(TitleListItem(titleResId, blockchain = blockchain)) + tokensListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.removeTokensForBlockchain(blockchain: Blockchain): List<CurrencyListItem> {
|
||||
return filterNot {
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.addTokensForBlockchain(
|
||||
blockchain: Blockchain, fullCurrenciesList: List<CurrencyListItem>,
|
||||
): List<CurrencyListItem> {
|
||||
val tokensToAdd = fullCurrenciesList.filter {
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
val indexToInsert = indexOfFirst {
|
||||
it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
} + 1
|
||||
return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) }
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.toggleHeaderContentShownValue(blockchain: Blockchain) {
|
||||
map {
|
||||
if (it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)) {
|
||||
it.isContentShown = !it.isContentShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if you do not check the testNet blockchains, it will not collapse
|
||||
private fun CurrencyListItem.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return when (this) {
|
||||
is CurrencyListItem.TitleListItem -> this.blockchain?.isTheSameOrTestnetBlockchain(blockchain) ?: false
|
||||
is CurrencyListItem.BlockchainListItem -> this.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
is CurrencyListItem.TokenListItem -> this.token.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return this == blockchain || this == blockchain.getTestnetVersion()
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -8,14 +10,17 @@ sealed class TokensAction : Action {
|
|||
|
||||
object ResetState : TokensAction()
|
||||
|
||||
data class AllowToAddTokens(val allow: Boolean) : TokensAction()
|
||||
|
||||
object LoadCurrencies : TokensAction() {
|
||||
data class Success(val currencies: List<CurrencyListItem>) : TokensAction()
|
||||
data class Success(val currencies: List<Currency>) : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
data class SetNonRemovableCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
|
||||
object OpenAllTokens : TokensAction()
|
||||
|
||||
data class SaveChanges(val addedItems: List<CurrencyListItem>) : TokensAction()
|
||||
data class SaveChanges(
|
||||
val addedTokens: List<Token>,
|
||||
val addedBlockchains: List<Blockchain>
|
||||
) : TokensAction()
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
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.domain.DELAY_SDK_DIALOG_CLOSE
|
||||
|
|
@ -33,6 +34,11 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is NavigationAction.NavigateTo -> {
|
||||
if (action.screen == AppScreen.AddTokens) {
|
||||
store.dispatch(TokensAction.LoadCurrencies)
|
||||
}
|
||||
}
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies()
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
|
|
@ -42,34 +48,37 @@ class TokensMiddleware {
|
|||
}
|
||||
|
||||
private fun handleLoadCurrencies() {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
val isTestcard = scanResponse.card.isTestCard
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
val isTestcard = scanResponse?.card?.isTestCard ?: false
|
||||
|
||||
val currencies = currenciesRepository.getSupportedTokens(isTestcard)
|
||||
|
||||
val tokens = currenciesRepository.getSupportedTokens(isTestcard)
|
||||
val blockchains = currenciesRepository.getBlockchains(
|
||||
cardFirmware = scanResponse.card.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
).sortedBy { it.fullName }
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens)
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
||||
val candidatesToAdd = action.addedItems
|
||||
if (candidatesToAdd.isEmpty()) return
|
||||
val currentTokens = store.state.tokensState.addedWallets.toTokens()
|
||||
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains()
|
||||
|
||||
val blockchains = candidatesToAdd.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
.map { it.blockchain }
|
||||
val tokens = candidatesToAdd.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
.map { it.token }
|
||||
if (blockchains.isEmpty() && tokens.isEmpty()) return
|
||||
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
|
||||
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
|
||||
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) }
|
||||
val tokensToRemove = currentTokens.filter { !action.addedTokens.contains(it) }
|
||||
|
||||
removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove)
|
||||
|
||||
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
return
|
||||
}
|
||||
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, blockchains, tokens)
|
||||
deriveMissingBlockchains(scanResponse, blockchainsToAdd, tokensToAdd)
|
||||
} else {
|
||||
submitAdd(blockchains, tokens)
|
||||
submitAdd(blockchainsToAdd, tokensToAdd)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
|
@ -158,4 +167,22 @@ class TokensMiddleware {
|
|||
WalletAction.MultiWallet.AddToken(it)
|
||||
}).forEach { store.dispatchOnMain(it) }
|
||||
}
|
||||
|
||||
private fun removeCurrenciesIfNeeded(blockchains: List<Blockchain>, tokens: List<Token>) {
|
||||
if (tokens.isNotEmpty()) {
|
||||
tokens.forEach { token ->
|
||||
store.state.walletState.getWalletData(token)?.let {
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockchains.isNotEmpty()) {
|
||||
blockchains.forEach { blockchain ->
|
||||
store.state.walletState.getWalletData(blockchain)?.let {
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
class TokensReducer {
|
||||
|
|
@ -19,35 +16,25 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies())
|
||||
tokensState.copy(
|
||||
addedBlockchains = action.wallets.toBlockchains(),
|
||||
addedTokens = action.wallets.toTokens(),
|
||||
addedWallets = action.wallets,
|
||||
)
|
||||
}
|
||||
is TokensAction.ToggleShowTokensForBlockchain -> {
|
||||
if (action.isShown) {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.removeTokensForBlockchain(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
} else {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.addTokensForBlockchain(action.blockchain, tokensState.currencies)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
}
|
||||
is TokensAction.SetNonRemovableCurrencies -> {
|
||||
tokensState.copy(
|
||||
nonRemovableBlockchains = action.wallets.toBlockchains(),
|
||||
nonRemovableTokens = action.wallets.toTokensContractAddresses(),
|
||||
)
|
||||
}
|
||||
is TokensAction.OpenAllTokens -> {
|
||||
val shownCurrencies = tokensState.currencies
|
||||
shownCurrencies.forEach { if (it is CurrencyListItem.TitleListItem) it.isContentShown = true}
|
||||
tokensState.copy(shownCurrencies = tokensState.currencies)
|
||||
|
||||
is TokensAction.AllowToAddTokens -> {
|
||||
tokensState.copy(allowToAdd = action.allow)
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<WalletData>.toCardCurrencies(): CardCurrencies {
|
||||
val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.distinct()
|
||||
val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.distinct()
|
||||
return CardCurrencies(tokens = tokens, blockchains = blockchains)
|
||||
}
|
||||
|
|
@ -1,18 +1,31 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class TokensState(
|
||||
val addedTokens: LinkedHashSet<TokenWithAmount> = LinkedHashSet(),
|
||||
val addedCurrencies: CardCurrencies? = null,
|
||||
val currencies: List<CurrencyListItem> = emptyList(),
|
||||
val shownCurrencies: List<CurrencyListItem> = emptyList(),
|
||||
val addedWallets: List<WalletData> = emptyList(),
|
||||
val addedTokens: List<Token> = emptyList(),
|
||||
val addedBlockchains: List<Blockchain> = emptyList(),
|
||||
val nonRemovableTokens: List<ContractAddress> = emptyList(),
|
||||
val nonRemovableBlockchains: List<Blockchain> = emptyList(),
|
||||
val currencies: List<Currency> = emptyList(),
|
||||
val allowToAdd: Boolean = true
|
||||
) : StateType
|
||||
|
||||
data class TokenWithAmount(
|
||||
val token: Token,
|
||||
val amount: Amount? = null
|
||||
)
|
||||
typealias ContractAddress = String
|
||||
|
||||
fun List<WalletData>.toTokensContractAddresses(): List<ContractAddress> {
|
||||
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token?.contractAddress }.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletData>.toTokens(): List<Token> {
|
||||
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletData>.toBlockchains(): List<Blockchain> {
|
||||
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Blockchain)?.blockchain }.distinct()
|
||||
}
|
||||
|
|
@ -8,15 +8,17 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
|
||||
import com.tangem.tap.features.tokens.ui.compose.CurrenciesScreen
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -27,10 +29,11 @@ import org.rekotlin.StoreSubscriber
|
|||
|
||||
|
||||
class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
||||
StoreSubscriber<TokensState> {
|
||||
StoreSubscriber<TokensState> {
|
||||
|
||||
private lateinit var viewAdapter: CurrenciesAdapter
|
||||
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
|
||||
private var tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState)
|
||||
private var searchInput = mutableStateOf("")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -64,27 +67,24 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
setupPopularTokensRecyclerView()
|
||||
btnTokensSaveChanges.setOnClickListener {
|
||||
store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPopularTokensRecyclerView() = with(binding) {
|
||||
viewAdapter = CurrenciesAdapter()
|
||||
viewAdapter.setOnItemAddListener {
|
||||
btnTokensSaveChanges.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
|
||||
cvCurrencies.setContent {
|
||||
AppCompatTheme {
|
||||
CurrenciesScreen(
|
||||
tokensState = tokensState,
|
||||
searchInput = searchInput
|
||||
) { tokens, blockchains ->
|
||||
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rvPopularTokens.layoutManager = LinearLayoutManager(context)
|
||||
rvPopularTokens.adapter = viewAdapter
|
||||
}
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
viewAdapter.addedCurrencies = state.addedCurrencies
|
||||
viewAdapter.submitUnfilteredList(state.shownCurrencies)
|
||||
val toolbarTitle =
|
||||
if (state.allowToAdd) R.string.add_tokens_title else R.string.search_tokens_title
|
||||
binding.toolbar.title = getString(toolbarTitle)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
|
@ -102,13 +102,13 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
searchView.queryHint = searchView.getString(R.string.add_token_search_hint)
|
||||
searchView.maxWidth = android.R.attr.width
|
||||
searchView.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
viewAdapter.filter(searchView.query)
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater);
|
||||
.debounce(400)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
searchInput.value = it.lowercase()
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -1,254 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.getTokensName
|
||||
import com.tangem.tap.features.tokens.redux.CurrencyListItem
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemCurrencySubtitleBinding
|
||||
import com.tangem.wallet.databinding.ItemPopularTokenBinding
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>(DiffUtilCallback) {
|
||||
|
||||
var addedCurrencies: CardCurrencies? = null
|
||||
|
||||
private var unfilteredList = listOf<CurrencyListItem>()
|
||||
|
||||
private val currentlyAddedItems = mutableListOf<CurrencyListItem>()
|
||||
|
||||
private var vhOnItemAddListener: ((CurrencyListItem) -> Unit) = {
|
||||
currentlyAddedItems.add(it)
|
||||
onItemAddListener?.invoke()
|
||||
}
|
||||
private var onItemAddListener: VoidCallback? = null
|
||||
|
||||
fun submitUnfilteredList(list: List<CurrencyListItem>) {
|
||||
unfilteredList = list
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
fun setOnItemAddListener(itemAddListener: VoidCallback) {
|
||||
onItemAddListener = itemAddListener
|
||||
if (currentlyAddedItems.isNotEmpty()) itemAddListener()
|
||||
}
|
||||
|
||||
fun getAddedItems(): List<CurrencyListItem> {
|
||||
return currentlyAddedItems.toList()
|
||||
}
|
||||
|
||||
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(
|
||||
binding = ItemCurrencySubtitleBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
1 -> CurrenciesViewHolder(
|
||||
binding = ItemPopularTokenBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
onItemAddListener = vhOnItemAddListener
|
||||
)
|
||||
else -> CurrenciesViewHolder(
|
||||
binding = ItemPopularTokenBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
onItemAddListener = vhOnItemAddListener
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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, currentlyAddedItems)
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
store.dispatch(TokensAction.OpenAllTokens)
|
||||
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)
|
||||
}
|
||||
is CurrencyListItem.TitleListItem -> true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
list.addAll(unfilteredList)
|
||||
}
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
class CurrenciesViewHolder(
|
||||
private val binding: ItemPopularTokenBinding,
|
||||
private val onItemAddListener: ((CurrencyListItem) -> Unit),
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(
|
||||
currency: CurrencyListItem,
|
||||
addedCurrencies: CardCurrencies?,
|
||||
currentlyAddedItems: MutableList<CurrencyListItem>
|
||||
) {
|
||||
when (currency) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val addedBlockchains = addedCurrencies?.blockchains ?: listOf()
|
||||
val currentlyAdded =
|
||||
currentlyAddedItems.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
bindBlockchain(currency, addedBlockchains, currentlyAdded)
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val addedTokens = addedCurrencies?.tokens ?: listOf()
|
||||
val currentlyAdded =
|
||||
currentlyAddedItems.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
bindToken(currency, addedTokens, currentlyAdded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBlockchain(
|
||||
currency: CurrencyListItem.BlockchainListItem,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
currentlyAdded: List<CurrencyListItem.BlockchainListItem>
|
||||
) = with(binding) {
|
||||
val blockchain = currency.blockchain
|
||||
Picasso.get().loadCurrenciesIcon(ivCurrency, tvTokenLetter, null, blockchain)
|
||||
|
||||
tvCurrencyName.text = blockchain.fullName
|
||||
tvCurrencySymbol.text = blockchain.currency
|
||||
btnAddToken.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedBlockchains.contains(blockchain)
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.blockchain == blockchain }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun bindToken(
|
||||
currency: CurrencyListItem.TokenListItem,
|
||||
addedTokens: List<Token>,
|
||||
currentlyAdded: List<CurrencyListItem.TokenListItem>
|
||||
) = with(binding) {
|
||||
val token = currency.token
|
||||
Picasso.get()
|
||||
.loadCurrenciesIcon(ivCurrency, tvTokenLetter, token, token.blockchain)
|
||||
|
||||
tvCurrencyName.text = token.name
|
||||
tvCurrencySymbol.text = tvCurrencySymbol.getString(
|
||||
R.string.token_symbol_address_format,
|
||||
token.symbol, token.contractAddress
|
||||
)
|
||||
btnAddToken.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedTokens.any { it == token }
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.token == token }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun modifyAddTokenButton(currency: CurrencyListItem) = with(binding) {
|
||||
if (currency.isLocked) {
|
||||
btnAddToken.setText(R.string.common_add)
|
||||
btnAddToken.isEnabled = false
|
||||
} else {
|
||||
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
|
||||
btnAddToken.setText(text)
|
||||
btnAddToken.isEnabled = !currency.isAdded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TitleViewHolder(val binding: ItemCurrencySubtitleBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(title: CurrencyListItem.TitleListItem) = with(binding) {
|
||||
if (title.blockchain != null) {
|
||||
val subtitle = root.getString(title.titleResId, title.blockchain.getTokensName())
|
||||
tvSubtitle.text = subtitle.uppercase()
|
||||
clSubtitleContainer.setOnClickListener {
|
||||
val rotation = if (title.isContentShown) -90f else 90f
|
||||
store.dispatch(
|
||||
TokensAction.ToggleShowTokensForBlockchain(
|
||||
title.isContentShown, title.blockchain
|
||||
)
|
||||
)
|
||||
ivToggleSublistVisibility.animate().rotation(rotation).withEndAction {
|
||||
showArrow(title.isContentShown)
|
||||
}
|
||||
}
|
||||
showArrow(title.isContentShown)
|
||||
} else {
|
||||
tvSubtitle.text = root.getString(title.titleResId).uppercase()
|
||||
ivToggleSublistVisibility.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showArrow(isContentShown: Boolean) = with(binding) {
|
||||
ivToggleSublistVisibility.show()
|
||||
val drawable = if (isContentShown) {
|
||||
R.drawable.ic_arrow_angle_down
|
||||
} else {
|
||||
R.drawable.ic_arrow_angle_right
|
||||
}
|
||||
ivToggleSublistVisibility.setImageResource(drawable)
|
||||
ivToggleSublistVisibility.rotation = 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun CollapsedCurrencyItem(
|
||||
currency: Currency,
|
||||
addedTokens: List<Token>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
onCurrencyClick: (String) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { onCurrencyClick(currency.id) })
|
||||
) {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val iconRes = if (blockchain == Blockchain.Unknown) {
|
||||
currency.iconUrl
|
||||
} else {
|
||||
blockchain.getRoundIconRes()
|
||||
}
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
model = iconRes,
|
||||
contentDescription = currency.id,
|
||||
loading = { CurrencyPlaceholderIcon(currency.id) },
|
||||
error = { CurrencyPlaceholderIcon(currency.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(16.dp)
|
||||
.size(46.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = currency.name,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1C1C1E),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(6.dp))
|
||||
Row {
|
||||
if (currency.contracts != null) {
|
||||
currency.contracts.map { contract ->
|
||||
val added =
|
||||
addedTokens.map { it.contractAddress }.contains(contract.address)
|
||||
val icon = if (added) {
|
||||
contract.blockchain.getRoundIconRes()
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
}
|
||||
} else {
|
||||
val added = addedBlockchains.contains(blockchain)
|
||||
val icon =
|
||||
if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes()
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_collapsed),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ExtendedFloatingActionButton
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.extensions.pixelsToDp
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun CurrenciesScreen(
|
||||
tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState),
|
||||
searchInput: MutableState<String>,
|
||||
onSaveChanges: (List<Token>, List<Blockchain>) -> Unit
|
||||
) {
|
||||
|
||||
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
|
||||
val addedBlockchainsState = remember { mutableStateOf(tokensState.value.addedBlockchains) }
|
||||
|
||||
val isKeyboardOpen by keyboardAsState()
|
||||
|
||||
val onAddCurrencyToggleClick = { currency: Currency, contract: Token? ->
|
||||
if (contract != null) {
|
||||
val mutableList = addedTokensState.value.toMutableList()
|
||||
if (mutableList.contains(contract)) {
|
||||
mutableList.remove(contract)
|
||||
} else {
|
||||
mutableList.add(contract)
|
||||
}
|
||||
addedTokensState.value = mutableList
|
||||
} else {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val mutableList = addedBlockchainsState.value.toMutableList()
|
||||
if (mutableList.contains(blockchain)) {
|
||||
mutableList.remove(blockchain)
|
||||
} else {
|
||||
mutableList.add(blockchain)
|
||||
}
|
||||
addedBlockchainsState.value = mutableList
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
// topBar =,
|
||||
floatingActionButton = {
|
||||
if (tokensState.value.allowToAdd) SaveChangesButton(isKeyboardOpen) {
|
||||
onSaveChanges(addedTokensState.value, addedBlockchainsState.value)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
|
||||
ListOfCurrencies(
|
||||
currencies = tokensState.value.currencies,
|
||||
nonRemovableTokens = tokensState.value.nonRemovableTokens,
|
||||
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
|
||||
addedTokens = addedTokensState.value,
|
||||
addedBlockchains = addedBlockchainsState.value,
|
||||
searchInput = searchInput.value,
|
||||
allowToAdd = tokensState.value.allowToAdd,
|
||||
onAddCurrencyToggled = onAddCurrencyToggleClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) {
|
||||
|
||||
val padding = if (keyboardState is Keyboard.Opened) {
|
||||
LocalContext.current.pixelsToDp(keyboardState.height)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_save_changes),
|
||||
)
|
||||
},
|
||||
onClick = onSaveChanges,
|
||||
backgroundColor = Color(0xFF1ACE80),
|
||||
contentColor = Color.White,
|
||||
modifier = Modifier.padding(bottom = padding.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
|
||||
@Composable
|
||||
fun CurrencyItem(
|
||||
currency: Currency,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<Token>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
expanded: Boolean,
|
||||
onCurrencyClick: (String) -> Unit,
|
||||
onAddCurrencyToggled: (Currency, Token?) -> Unit
|
||||
) {
|
||||
if (expanded) {
|
||||
ExpandedCurrencyItem(
|
||||
currency = currency,
|
||||
nonRemovableTokens = nonRemovableTokens,
|
||||
nonRemovableBlockchains = nonRemovableBlockchains,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled
|
||||
)
|
||||
} else {
|
||||
CollapsedCurrencyItem(
|
||||
currency = currency, addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains, onCurrencyClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun CurrencyPlaceholderIcon(id: String) {
|
||||
|
||||
val letterColor: Color = Color.White
|
||||
val circleColor: Color = Color.Black
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.background(circleColor, shape = CircleShape)
|
||||
)
|
||||
{
|
||||
Text(
|
||||
text = id.firstOrNull()?.titlecase() ?: "",
|
||||
textAlign = TextAlign.Center,
|
||||
color = letterColor,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun ExpandedCurrencyItem(
|
||||
currency: Currency,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<Token>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
onCurrencyClick: (String) -> Unit,
|
||||
onAddCurrencyToggled: (Currency, Token?) -> Unit
|
||||
) {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val iconRes = if (blockchain == Blockchain.Unknown) {
|
||||
currency.iconUrl
|
||||
} else {
|
||||
blockchain.getRoundIconRes()
|
||||
}
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { onCurrencyClick(currency.id) })
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = iconRes,
|
||||
contentDescription = currency.id,
|
||||
loading = { CurrencyPlaceholderIcon(currency.id) },
|
||||
error = { CurrencyPlaceholderIcon(currency.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||
.size(46.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.padding(top = 14.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = currency.name,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1C1C1E),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(6.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.currency_subtitle_expanded),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF8E8E93),
|
||||
)
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_extended),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
|
||||
val blockchains = currency.contracts?.map { it.blockchain } ?: listOf(
|
||||
Blockchain.fromNetworkId(currency.id)
|
||||
)
|
||||
|
||||
Row {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
) {
|
||||
if (blockchains.size > 1) {
|
||||
Divider(
|
||||
color = Color(0xFFDEDEDE),
|
||||
modifier = Modifier
|
||||
.height((43 * blockchains.size - 1).dp)
|
||||
.padding(start = 37.dp, top = 16.dp)
|
||||
.width(1.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
blockchains.map {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_link),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(
|
||||
start = 37.dp,
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(13.5.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 6.dp),
|
||||
) {
|
||||
blockchains.map { blockchain ->
|
||||
val contract = currency.contracts?.firstOrNull { it.blockchain == blockchain }
|
||||
val added = if (contract != null) {
|
||||
addedTokens.map { it.contractAddress }.contains(contract.address)
|
||||
} else {
|
||||
addedBlockchains.contains(blockchain)
|
||||
}
|
||||
val canBeRemoved = if (contract != null) {
|
||||
!nonRemovableTokens.contains(contract.address)
|
||||
} else {
|
||||
!nonRemovableBlockchains.contains(blockchain)
|
||||
}
|
||||
NetworkItem(
|
||||
currency = currency,
|
||||
contract = contract,
|
||||
blockchain = blockchain, allowToAdd = allowToAdd,
|
||||
added = added,
|
||||
canBeRemoved = canBeRemoved,
|
||||
onAddCurrencyToggled = onAddCurrencyToggled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
|
||||
|
||||
@Composable
|
||||
fun ListOfCurrencies(
|
||||
currencies: List<Currency>,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<Token>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
searchInput: String,
|
||||
allowToAdd: Boolean,
|
||||
onAddCurrencyToggled: (Currency, Token?) -> Unit
|
||||
) {
|
||||
|
||||
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
|
||||
|
||||
val onCurrencyClick = { currencyId: String ->
|
||||
val mutableList = expandedCurrencies.value.toMutableList()
|
||||
if (mutableList.contains(currencyId)) {
|
||||
mutableList.remove(currencyId)
|
||||
} else {
|
||||
mutableList.add(currencyId)
|
||||
}
|
||||
expandedCurrencies.value = mutableList
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
|
||||
val filteredCurrencies = if (searchInput.isBlank()) {
|
||||
currencies
|
||||
} else {
|
||||
currencies.asSequence().filter {
|
||||
it.name.lowercase().contains(searchInput) || it.symbol.lowercase()
|
||||
.contains(searchInput)
|
||||
}.toList()
|
||||
}
|
||||
items(filteredCurrencies) { currency ->
|
||||
CurrencyItem(
|
||||
currency = currency,
|
||||
nonRemovableTokens = nonRemovableTokens,
|
||||
nonRemovableBlockchains = nonRemovableBlockchains,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
expanded = expandedCurrencies.value.contains(currency.id),
|
||||
onCurrencyClick = onCurrencyClick,
|
||||
onAddCurrencyToggled = onAddCurrencyToggled
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
|
||||
@Composable
|
||||
fun NetworkItem(
|
||||
currency: Currency, contract: Contract?,
|
||||
blockchain: Blockchain, allowToAdd: Boolean,
|
||||
added: Boolean, canBeRemoved: Boolean,
|
||||
onAddCurrencyToggled: (Currency, Token?) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
|
||||
contentDescription = blockchain.fullName,
|
||||
loading = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
error = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Row(modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)) {
|
||||
Text(
|
||||
text = blockchain.name.uppercase(),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (added) Color.Black else Color(0xFF848488),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(3.dp))
|
||||
Text(
|
||||
text = if (contract != null) blockchain.getNetworkName().uppercase() else "MAIN",
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (contract != null) Color(0xFF8E8E93) else Color(0xFF1ACE80),
|
||||
)
|
||||
}
|
||||
|
||||
val token = if (contract != null) {
|
||||
Token(
|
||||
name = currency.name, symbol = currency.symbol, contractAddress = contract.address,
|
||||
decimals = contract.decimalCount, blockchain = contract.blockchain
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (allowToAdd) Switch(
|
||||
checked = added,
|
||||
enabled = canBeRemoved,
|
||||
onCheckedChange = { onAddCurrencyToggled(currency, token) },
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color(0xFF1ACE80)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -98,7 +98,11 @@ class MultiWalletView : WalletView {
|
|||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
store.dispatch(TokensAction.LoadCurrencies)
|
||||
store.dispatch(TokensAction.AllowToAddTokens(true))
|
||||
store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
|
||||
store.dispatch(TokensAction.SetNonRemovableCurrencies(
|
||||
state.wallets.filterNot { state.canBeRemoved(it) })
|
||||
)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ class OnramperService(
|
|||
) : ExchangeService, ExchangeUrlBuilder {
|
||||
|
||||
private val api: OnramperApi by lazy {
|
||||
createRetrofitInstance(OnramperApi.BASE_URL, listOf(AddKeyToHeaderInterceptor(apiKey)))
|
||||
createRetrofitInstance(OnramperApi.BASE_URL,
|
||||
listOf(AddKeyToHeaderInterceptor(apiKey))
|
||||
)
|
||||
.create(OnramperApi::class.java)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue