Updated on 2026-08-14

This commit is contained in:
Tangem 2022-08-04 12:49:37 +04:00
commit c62e02ae91
24 changed files with 755 additions and 451 deletions

View file

@ -104,56 +104,25 @@ class DemoConfig {
?: Amount(BigDecimal.ZERO, blockchain).copy()
private fun getReleaseIds(): List<String> {
return (releaseDemoCardIds +
releaseDemoCardIds_19042022 +
testDemoCardIds).distinct()
return (releaseDemoCardIds + testDemoCardIds).distinct()
}
private val releaseDemoCardIds = mutableListOf(
// Tangem Wallet:
"AC01000000041100",
"AC01000000042462",
"AC01000000041647",
"AC01000000041621",
"AC01000000041217",
// === Not from the Google Sheet table ===
"FB10000000000196", // Note BTC
"FB20000000000186", // Note ETH
"FB30000000000176", // Wallet
"AC01000000041225",
"AC01000000041209",
"AC01000000041092",
"AC01000000041472",
"AC01000000041662",
"AC01000000045754",
"AC01000000045960",
// Tangem Note BTC:
"AB01000000046530",
"AB01000000046720",
"AB01000000046746",
"AB01000000046498",
"AB01000000046753",
"AB01000000049608",
"AB01000000046761",
"AB01000000049574",
"AB01000000046605",
"AB01000000046571",
"AB01000000046704",
"AB01000000046647",
// Tangem Note Ethereum:
"AB02000000051000",
"AB02000000050986",
"AB02000000051026",
"AB02000000051042",
"AB02000000051091",
"AB02000000051083",
"AB02000000050960",
"AB02000000051034",
"AB02000000050911",
"AB02000000051133",
"AB02000000051158",
"AB02000000051059",
)
// https://tangem.slack.com/archives/GMXC6PP71/p1650360610759269
private val releaseDemoCardIds_19042022 = listOf(
// Wallet
// === Mvideo ===
// Wallet
"AC01000000045754",
"AC01000000041662",
"AC01000000041647",
@ -186,7 +155,58 @@ class DemoConfig {
"AC01000000013497",
"AC01000000013836",
"AC01000000013505",
// Note BTC
"AC03000000046693",
"AC03000000046685",
"AC03000000046677",
"AC03000000046669",
"AC03000000046651",
"AC03000000046644",
"AC03000000046636",
"AC03000000046628",
"AC03000000046610",
"AC03000000046602",
"AC03000000046594",
"AC03000000046586",
"AC03000000046578",
"AC03000000046560",
"AC03000000046552",
"AC03000000046545",
"AC03000000046537",
"AC03000000046529",
"AC03000000046511",
"AC03000000046800",
"AC03000000046792",
"AC03000000046784",
"AC03000000046776",
"AC03000000046768",
"AC03000000046750",
"AC03000000046743",
"AC03000000046735",
"AC03000000046727",
"AC03000000046446",
"AC03000000046438",
"AC03000000046412",
"AC03000000046388",
"AC03000000046370",
"AC03000000046354",
"AC03000000046347",
"AC03000000046339",
"AC03000000046321",
"AC03000000046172",
"AC03000000046396",
"AC03000000046404",
"AC03000000046701",
"AC03000000046420",
"AC03000000046719",
"AC03000000046503",
"AC03000000046495",
"AC03000000046487",
"AC03000000046362",
"AC03000000046479",
"AC03000000046461",
"AC03000000046453",
// Note BTC
"AB01000000059608",
"AB01000000046647",
"AB01000000046571",
@ -219,7 +239,58 @@ class DemoConfig {
"AB01000000015782",
"AB01000000022598",
"AB01000000022580",
// Note ETH
"AB01000000005688",
"AB07000000005696",
"AB07000000005902",
"AB07000000005910",
"AB07000000005928",
"AB07000000005936",
"AB07000000005944",
"AB07000000005993",
"AB07000000005985",
"AB07000000005977",
"AB07000000005969",
"AB07000000005951",
"AB07000000005605",
"AB07000000005803",
"AB07000000005811",
"AB07000000005829",
"AB07000000005837",
"AB07000000005845",
"AB07000000005852",
"AB07000000005860",
"AB07000000005878",
"AB07000000005886",
"AB07000000005894",
"AB07000000005704",
"AB07000000005712",
"AB07000000005720",
"AB07000000005738",
"AB07000000005746",
"AB07000000005514",
"AB07000000005522",
"AB07000000005563",
"AB07000000005571",
"AB07000000005589",
"AB07000000005597",
"AB07000000005613",
"AB07000000005621",
"AB07000000005639",
"AB07000000005647",
"AB07000000005654",
"AB07000000005662",
"AB07000000005670",
"AB07000000005530",
"AB07000000005548",
"AB07000000005555",
"AB07000000005753",
"AB07000000005761",
"AB07000000005779",
"AB07000000005787",
"AB07000000005795",
"AB07000000005506",
// Note ETH
"AB02000000051083",
"AB02000000051059",
"AB02000000051158",
@ -252,6 +323,123 @@ class DemoConfig {
"AB02000000020252",
"AB02000000018652",
"AB02000000018561",
"AB08000000009481",
"AB08000000009473",
"AB08000000009705",
"AB08000000009897",
"AB08000000009689",
"AB08000000009671",
"AB08000000009465",
"AB08000000009457",
"AB08000000009440",
"AB08000000009432",
"AB08000000009424",
"AB08000000009416",
"AB08000000009408",
"AB08000000009390",
"AB08000000009374",
"AB08000000009382",
"AB08000000009267",
"AB08000000009275",
"AB08000000009283",
"AB08000000009291",
"AB08000000009309",
"AB08000000009317",
"AB08000000009325",
"AB08000000009333",
"AB08000000009341",
"AB08000000009358",
"AB08000000009366",
"AB08000000009077",
"AB08000000009143",
"AB08000000009168",
"AB08000000009184",
"AB08000000009192",
"AB08000000009200",
"AB08000000009226",
"AB08000000009218",
"AB08000000009234",
"AB08000000009242",
"AB08000000008574",
"AB08000000009069",
"AB08000000008525",
"AB08000000009051",
"AB08000000009135",
"AB08000000009150",
"AB08000000009176",
"AB08000000009085",
"AB08000000009093",
"AB08000000009101",
"AB08000000009119",
"AB08000000009127",
"AB08000000009259",
// === Technopark ===
// Wallet
"AC01000000044120",
"AC01000000044997",
"AC01000000044989",
"AC01000000043494",
"AC01000000043486",
"AC01000000044187",
"AC01000000043148",
"AC01000000044013",
"AC01000000043973",
"AC01000000044815",
"AC01000000044807",
"AC01000000043809",
"AC01000000043833",
"AC01000000043460",
"AC01000000043064",
"AC01000000044138",
"AC01000000044500",
"AC01000000044492",
"AC01000000044260",
"AC01000000044278",
// Note BTC
"AB01000000049864",
"AB01000000053239",
"AB01000000053056",
"AB01000000054237",
"AB01000000054245",
"AB01000000054211",
"AB01000000054229",
"AB01000000053189",
"AB01000000054195",
"AB01000000050797",
"AB01000000053833",
"AB01000000052124",
"AB01000000051605",
"AB01000000052223",
"AB01000000052207",
"AB01000000052199",
"AB01000000047785",
"AB01000000047850",
"AB01000000047868",
"AB01000000048288",
// Note ETH
"AB02000000049715",
"AB02000000049848",
"AB02000000049814",
"AB02000000049863",
"AB02000000049871",
"AB02000000049855",
"AB02000000049285",
"AB02000000049277",
"AB02000000049558",
"AB02000000049889",
"AB02000000049988",
"AB02000000049707",
"AB02000000049699",
"AB02000000049897",
"AB02000000049905",
"AB02000000049913",
"AB02000000049251",
"AB02000000049533",
"AB02000000049541",
"AB02000000049830",
)
private val testDemoCardIds = listOf(
@ -281,7 +469,6 @@ class DemoTransactionSender(
}
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
val signerResponse = signer.sign(

View file

@ -74,9 +74,7 @@ fun StoriesScreen(
}
val isPressed = remember { mutableStateOf(false) }
val needsToBePaused =
remember(homeState) { mutableStateOf(homeState.value.btnScanState.progressState == ProgressState.Loading) }
val isPaused = isPressed.value || needsToBePaused.value
val isPaused = isPressed.value || homeState.value.btnScanState.progressState == ProgressState.Loading
val hideContent = remember { mutableStateOf(true) }

View file

@ -1,155 +0,0 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.platform.LocalContext
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 coil.request.ImageRequest
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
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.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R
@Composable
fun CollapsedCurrencyItem(
currency: Currency,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
onCurrencyClick: (String) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onCurrencyClick(currency.id) })
) {
val blockchain = Blockchain.fromNetworkId(currency.id)
SubcomposeAsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(currency.iconUrl)
.crossfade(true)
.build(),
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.fullName,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1C1C1E),
)
Spacer(modifier = Modifier.size(6.dp))
Row {
if (!currency.contracts.isNullOrEmpty()) {
currency.contracts.map { contract ->
if (contract.address == null) {
BlockchainNetworkItem(
blockchain = contract.blockchain,
isMainNetwork = true,
addedBlockchains = addedBlockchains
)
} else {
val added =
addedTokens.map { it.token.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 {
BlockchainNetworkItem(
blockchain = blockchain,
isMainNetwork = false,
addedBlockchains = addedBlockchains
)
}
}
}
Image(
painter = painterResource(id = R.drawable.ic_arrow_collapsed),
contentDescription = null,
Modifier
.padding(20.dp)
.align(Alignment.CenterVertically)
)
}
}
@Composable
fun BlockchainNetworkItem(
blockchain: Blockchain?,
isMainNetwork: Boolean,
addedBlockchains: List<Blockchain>
) {
val added = addedBlockchains.contains(blockchain)
val icon =
if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
if (icon != null) {
Box(Modifier
.size(20.dp)) {
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp)
)
if (isMainNetwork) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(7.dp)
.clip(CircleShape)
.background(Color.White)
) {
Box(
modifier = Modifier
.align(Alignment.Center)
.size(5.dp)
.clip(CircleShape)
.background(Color(0xFF1ACE80))
)
}
}
}
Spacer(modifier = Modifier.size(5.dp))
}
}

View file

@ -1,163 +0,0 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.platform.LocalContext
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 coil.request.ImageRequest
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R
@Composable
fun ExpandedCurrencyItem(
currency: Currency,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
allowToAdd: Boolean,
onCurrencyClick: (String) -> Unit,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onCurrencyClick(currency.id) })
) {
SubcomposeAsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(currency.iconUrl)
.crossfade(true)
.build(),
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.fullName,
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 }
Row {
Box(
modifier = Modifier
.fillMaxHeight()
) {
if (blockchains.size > 1) {
val dividerHeight = if (blockchains.size == 2) {
60
} else if (blockchains.size == 3) {
110
} else {
43 * blockchains.size
}
Divider(
color = Color(0xFFDEDEDE),
modifier = Modifier
.height(dividerHeight.dp)
.padding(start = 37.5.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 }
?: return@map
val added = if (contract.address != null) {
addedTokens.any {
it.token.contractAddress == contract.address && it.blockchain == contract.blockchain
}
} else {
addedBlockchains.contains(blockchain)
}
NetworkItem(
currency = currency,
contract = contract,
blockchain = blockchain,
allowToAdd = allowToAdd,
added = added,
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked,
)
}
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -16,7 +17,7 @@ import com.tangem.tap.common.compose.extensions.OnBottomReached
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.ui.compose.currencyItem.CurrencyItem
@Composable
fun ListOfCurrencies(
@ -27,7 +28,7 @@ fun ListOfCurrencies(
allowToAdd: Boolean,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit,
onLoadMore: () -> Unit
onLoadMore: () -> Unit,
) {
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
@ -50,7 +51,8 @@ fun ListOfCurrencies(
state = listState,
modifier = Modifier
.fillMaxSize(),
contentPadding = PaddingValues(bottom = 90.dp)
contentPadding = PaddingValues(bottom = 90.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
item { header() }
itemsIndexed(currencies) { index, currency ->
@ -59,10 +61,10 @@ fun ListOfCurrencies(
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
allowToAdd = allowToAdd,
expanded = expandedCurrencies.value.contains(currency.id),
isExpanded = expandedCurrencies.value.contains(currency.id),
onCurrencyClick = onCurrencyClick,
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked
onNetworkItemClicked = onNetworkItemClicked,
)
}
}

View file

@ -0,0 +1,60 @@
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@Composable
fun CurrencyExpandedContent(
currency: Currency,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
allowToAdd: Boolean,
isExpanded: Boolean,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit,
) {
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
val blockchains = currency.contracts.map { it.blockchain }
Column(
modifier = Modifier
.fillMaxWidth(),
) {
blockchains.mapIndexed { index, blockchain ->
val contract = currency.contracts.firstOrNull { it.blockchain == blockchain }
?: return@mapIndexed
val added = if (contract.address != null) {
addedTokens.map { it.token.contractAddress }.contains(contract.address)
} else {
addedBlockchains.contains(blockchain)
}
NetworkItem(
currency = currency,
contract = contract,
blockchain = blockchain,
allowToAdd = allowToAdd,
added = added,
index = index,
size = blockchains.size,
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked,
)
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.ui.compose
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.domain.tokens.Currency
@ -12,24 +13,27 @@ fun CurrencyItem(
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
allowToAdd: Boolean,
expanded: Boolean,
isExpanded: Boolean,
onCurrencyClick: (String) -> Unit,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
onNetworkItemClicked: (ContractAddress) -> Unit,
) {
if (expanded) {
ExpandedCurrencyItem(
Column {
CurrencyItemHeader(
currency = currency,
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
isExpanded = isExpanded,
onCurrencyClick = onCurrencyClick,
)
CurrencyExpandedContent(
currency = currency,
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
allowToAdd = allowToAdd,
onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked
)
} else {
CollapsedCurrencyItem(
currency = currency, addedTokens = addedTokens,
addedBlockchains = addedBlockchains, onCurrencyClick
isExpanded = isExpanded,
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked,
)
}
}

View file

@ -0,0 +1,91 @@
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun CurrencyItemArrow(
rowHeight: Dp,
isLastArrow: Boolean,
) {
Box(Modifier.height(rowHeight)) {
if (!isLastArrow) {
MiddleArrowView(rowHeight)
} else {
LastArrowView(rowHeight)
}
}
}
@Composable
private fun MiddleArrowView(rowHeight: Dp) {
Box {
Box(
modifier = Modifier
.padding(start = 0.5.dp),
) {
Box(
modifier = Modifier
.background(color = Color(0xFFDEDEDE))
.width(0.8.dp)
.fillMaxHeight(),
)
}
LastArrowView(rowHeight)
}
}
@Composable
private fun LastArrowView(rowHeight: Dp) {
Image(
modifier = Modifier
.height(rowHeight / 2 + 3.5.dp),
painter = painterResource(id = R.drawable.ic_link),
contentDescription = null,
)
}
@Preview
@Composable
fun ArrowViewPreview() {
Box(
Modifier
.background(color = Color.White),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 37.dp),
) {
CurrencyItemArrow(
rowHeight = 62.dp,
isLastArrow = false,
)
CurrencyItemArrow(
rowHeight = 62.dp,
isLastArrow = true,
)
}
}
}

View file

@ -0,0 +1,208 @@
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.platform.LocalContext
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 coil.request.ImageRequest
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
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.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon
import com.tangem.tap.features.tokens.ui.compose.fullName
import com.tangem.wallet.R
@Composable
fun CurrencyItemHeader(
currency: Currency,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
isExpanded: Boolean,
onCurrencyClick: (String) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(62.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { onCurrencyClick(currency.id) },
),
) {
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.align(Alignment.CenterVertically)
.clip(RoundedCornerShape(6.dp)),
) {
SubcomposeAsyncImage(
modifier = Modifier
.size(46.dp),
model = ImageRequest.Builder(LocalContext.current)
.data(currency.iconUrl)
.crossfade(true)
.build(),
contentDescription = currency.id,
loading = { CurrencyPlaceholderIcon(currency.id) },
error = { CurrencyPlaceholderIcon(currency.id) },
)
}
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically),
) {
Text(
text = currency.fullName,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1C1C1E),
)
Spacer(modifier = Modifier.size(6.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
) {
if (isExpanded) {
Text(
modifier = Modifier.align(Alignment.TopStart),
text = stringResource(id = R.string.currency_subtitle_expanded),
fontSize = 13.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF8E8E93),
)
} else {
NetworksRow(
currency = currency,
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
)
}
}
}
val expandCollapseArrow = if (isExpanded) R.drawable.ic_arrow_extended else R.drawable.ic_arrow_collapsed
Image(
painter = painterResource(id = expandCollapseArrow),
contentDescription = null,
Modifier
.padding(20.dp)
.align(Alignment.CenterVertically),
)
}
}
@Composable
private fun NetworksRow(
currency: Currency,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
) {
Row {
if (currency.contracts.isNotEmpty()) {
currency.contracts.map { contract ->
if (contract.address == null) {
BlockchainNetworkItem(
blockchain = contract.blockchain,
isMainNetwork = true,
addedBlockchains = addedBlockchains,
)
} else {
val isAdded = addedTokens.map { it.token.contractAddress }.contains(contract.address)
val iconResId = if (isAdded) {
contract.blockchain.getRoundIconRes()
} else {
contract.blockchain.getGreyedOutIconRes()
}
Image(
modifier = Modifier
.size(20.dp),
painter = painterResource(id = iconResId),
contentDescription = null,
)
Spacer(Modifier.size(5.dp))
}
}
} else {
BlockchainNetworkItem(
blockchain = Blockchain.fromNetworkId(currency.id),
isMainNetwork = false,
addedBlockchains = addedBlockchains,
)
}
}
}
@Composable
private fun BlockchainNetworkItem(
blockchain: Blockchain?,
isMainNetwork: Boolean,
addedBlockchains: List<Blockchain>,
) {
val added = addedBlockchains.contains(blockchain)
val icon =
if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
if (icon != null) {
Box(
Modifier
.size(20.dp),
) {
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp),
)
if (isMainNetwork) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(7.dp)
.clip(CircleShape)
.background(Color.White),
) {
Box(
modifier = Modifier
.align(Alignment.Center)
.size(5.dp)
.clip(CircleShape)
.background(Color(0xFF1ACE80)),
)
}
}
}
Spacer(modifier = Modifier.size(5.dp))
}
}

View file

@ -1,10 +1,18 @@
package com.tangem.tap.features.tokens.ui.compose
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
@ -31,6 +39,7 @@ import com.tangem.tap.domain.tokens.Contract
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon
@OptIn(ExperimentalFoundationApi::class)
@Composable
@ -40,13 +49,17 @@ fun NetworkItem(
blockchain: Blockchain,
allowToAdd: Boolean,
added: Boolean,
index: Int,
size: Int,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
onNetworkItemClicked: (ContractAddress) -> Unit,
) {
val rowHeight = 53.dp
Row(
modifier = Modifier
.fillMaxWidth()
.height(rowHeight)
.combinedClickable(
enabled = allowToAdd,
onLongClick = {
@ -54,13 +67,22 @@ fun NetworkItem(
},
onClick = {},
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
interactionSource = remember { MutableInteractionSource() },
),
) {
Box(
Modifier
.width(78.dp)
.padding(start = 38.dp),
) {
CurrencyItemArrow(
rowHeight = rowHeight,
isLastArrow = index == size - 1,
)
}
Box(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
.align(Alignment.CenterVertically),
) {
SubcomposeAsyncImage(
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
@ -68,7 +90,7 @@ fun NetworkItem(
loading = { CurrencyPlaceholderIcon(blockchain.id) },
error = { CurrencyPlaceholderIcon(blockchain.id) },
modifier = Modifier
.size(20.dp)
.size(20.dp),
)
if (contract.address == null) {
Box(
@ -76,34 +98,31 @@ fun NetworkItem(
.align(Alignment.TopEnd)
.size(7.dp)
.clip(CircleShape)
.background(Color.White)
.background(Color.White),
) {
Box(
modifier = Modifier
.align(Alignment.Center)
.size(5.dp)
.clip(CircleShape)
.background(Color(0xFF1ACE80))
.background(Color(0xFF1ACE80)),
)
}
}
}
Row(
Spacer(Modifier.width(6.dp))
Text(
modifier = Modifier
.fillMaxHeight()
.weight(1f)
.align(Alignment.CenterVertically)
) {
Text(
text = prepareNetworkNameSpannableText(
blockchain = blockchain,
contractAddress = contract.address
),
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
color = if (added) Color.Black else Color(0xFF848488),
)
}
.align(Alignment.CenterVertically),
text = prepareNetworkNameSpannableText(
blockchain = blockchain,
contractAddress = contract.address,
),
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
color = if (added) Color.Black else Color(0xFF848488),
)
if (allowToAdd) {
val token = if (contract.address != null) {
@ -117,8 +136,7 @@ fun NetworkItem(
} else {
null
}
val tokenWithBlockchain =
token?.let { TokenWithBlockchain(token, contract.blockchain) }
val tokenWithBlockchain = token?.let { TokenWithBlockchain(it, contract.blockchain) }
val currencyToSave = if (contract.address == null) {
currency.copy(id = contract.networkId)
@ -127,22 +145,23 @@ fun NetworkItem(
}
Switch(
modifier = Modifier
.fillMaxHeight()
.padding(start = 16.dp, end = 16.dp),
checked = added,
onCheckedChange = { onAddCurrencyToggled(currencyToSave, tokenWithBlockchain) },
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
colors = SwitchDefaults.colors(
checkedThumbColor = Color(0xFF1ACE80)
)
checkedThumbColor = Color(0xFF1ACE80),
),
)
}
}
}
@Composable
fun prepareNetworkNameSpannableText(
private fun prepareNetworkNameSpannableText(
blockchain: Blockchain,
contractAddress: String?
contractAddress: String?,
): AnnotatedString {
val blockchainName = blockchain.fullNameWithoutTestnet.uppercase()
@ -158,11 +177,11 @@ fun prepareNetworkNameSpannableText(
AnnotatedString.Range(
SpanStyle(
fontWeight = FontWeight.Normal,
color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80)
color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80),
),
start = startOfAdditionalText,
end = text.length
)
end = text.length,
),
)
return AnnotatedString(text = text, spanStyles = spanStyles)
}

View file

@ -10,8 +10,8 @@ data class PendingTransaction(
val type: PendingTransactionType,
) {
val address: String? = when (type) {
PendingTransactionType.Incoming -> transactionData.destinationAddress
PendingTransactionType.Outgoing -> transactionData.sourceAddress
PendingTransactionType.Incoming -> transactionData.sourceAddress
PendingTransactionType.Outgoing -> transactionData.destinationAddress
PendingTransactionType.Unknown -> null
}

View file

@ -4,7 +4,6 @@ sealed class WalletWarning(
val showingPosition: Int,
) {
object TransactionInProgress : WalletWarning(10)
object SolanaTokensUnsupported : WalletWarning(20)
data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30)
data class Rent(val walletRent: WalletRent) : WalletWarning(40)
}

View file

@ -8,6 +8,8 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
@ -395,19 +397,10 @@ data class WalletData(
}
fun assembleWarnings(): List<WalletWarning> {
val blockchain = currency.blockchain
val walletWarnings = mutableListOf<WalletWarning>()
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress)
}
if (currency.isBlockchain()) {
if (blockchain == Blockchain.Solana || blockchain == Blockchain.SolanaTestnet) {
val card = store.state.globalState.scanResponse?.card
if (card?.canHandleToken(blockchain) == false) {
walletWarnings.add(WalletWarning.SolanaTokensUnsupported)
}
}
}
if (walletRent != null) {
walletWarnings.add(WalletWarning.Rent(walletRent))
}

View file

@ -21,9 +21,6 @@ class WalletWarningConverter(
message.blockchainFullName, message.blockchainFullName
)
}
WalletWarning.SolanaTokensUnsupported -> {
context.getString(R.string.warning_token_send_unsupported_message)
}
WalletWarning.TransactionInProgress -> {
context.getString(R.string.wallet_pending_transaction_warning)
}

View file

@ -0,0 +1,58 @@
package com.tangem.tap.features.wallet.ui.images
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import androidx.annotation.Px
import androidx.core.graphics.applyCanvas
import coil.size.Size
import coil.size.pxOrElse
import coil.transform.Transformation
class RoundedCornersCropTransformation(
@Px val radiusPx: Float,
) : Transformation {
override val cacheKey: String = "${javaClass.name}-$radiusPx"
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
val outputWidth = size.width.pxOrElse { Int.MAX_VALUE }
val outputHeight = size.height.pxOrElse { Int.MAX_VALUE }
val outputSize = minOf(outputWidth, outputHeight)
val output = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888)
val rect = RectF(
/* left = */ 0f,
/* top = */ 0f,
/* right = */ outputWidth.toFloat(),
/* bottom = */ outputHeight.toFloat()
)
val matrix = Matrix().apply {
setTranslate(
/* dx = */ (outputWidth - input.width) * .5f,
/* dy = */ (outputHeight - input.height) * .5f
)
}
val bitmapShader = BitmapShader(
/* bitmap = */ input,
/* tileX = */ Shader.TileMode.DECAL,
/* tileY = */ Shader.TileMode.DECAL
).apply {
setLocalMatrix(matrix)
}
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply {
shader = bitmapShader
}
return output.applyCanvas {
drawRoundRect(
/* rect = */ rect,
/* rx = */ radiusPx,
/* ry = */ radiusPx,
/* paint = */ paint
)
}
}
}