Updated on 2026-08-14
This commit is contained in:
parent
8312c85fa6
commit
fb1ed471af
7 changed files with 420 additions and 428 deletions
|
|
@ -23,18 +23,66 @@ import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
|||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.*
|
||||
import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestAddCustomTokenActions
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class AddCustomTokenScreen {} // for simple search
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AddCustomTokenScreen(
|
||||
state: MutableState<AddCustomTokenState>,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) }
|
||||
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
|
||||
)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } }
|
||||
|
||||
BottomSheetScaffold(
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
sheetContent = {
|
||||
Surface(color = colorResource(id = R.color.lightGray5)) {
|
||||
selectedTestCase.value.content(toggleBottomSheet)
|
||||
}
|
||||
},
|
||||
sheetPeekHeight = 0.dp,
|
||||
) {
|
||||
Column() {
|
||||
TestCasesList(onItemClick = {
|
||||
selectedTestCase.value = it
|
||||
toggleBottomSheet()
|
||||
})
|
||||
ScreenContent(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
|
||||
ComposeDialogManager()
|
||||
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
|
||||
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
private suspend fun BottomSheetScaffoldState.toggle() {
|
||||
if (bottomSheetState.isCollapsed) {
|
||||
bottomSheetState.expand()
|
||||
} else {
|
||||
bottomSheetState.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
state: MutableState<AddCustomTokenState>,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
|
|
@ -50,9 +98,8 @@ fun AddCustomTokenScreen(
|
|||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
contentPadding = PaddingValues(bottom = 90.dp),
|
||||
) {
|
||||
item { TestAddCustomTokenActions() }
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -71,11 +118,7 @@ fun AddCustomTokenScreen(
|
|||
item { Warnings(state.value.warnings.toList()) }
|
||||
}
|
||||
}
|
||||
ComposeDialogManager()
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
|
||||
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun ContractAddressTests(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
val casesInfo = listOf(
|
||||
"USDC on ETH" to "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
"BUSD on ETH" to "0x4fabb145d64652a948d72533023f6e7a623c7c53",
|
||||
"ETH on AVALANCHE" to "0xf20d962a6c8f70c731bd838a3a388d7d48fa6e15",
|
||||
"USDC on ETH (invalid - cut address)" to "0xa0b86991c6218b36c1d1",
|
||||
"Custom EVM" to "0x1111111111111111112111111111111111111113",
|
||||
"Supported by several networks" to "0xa1faa113cbe53436df28ff0aee54275c13b40975",
|
||||
"Invalid" to "!@#_ _-%%^&&*((){P P2iOWsdfFQLA",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SolanaAddressTests(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
val casesInfo = listOf(
|
||||
"USDT (full)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"USDT (valid - 2/3 of address)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8Ben",
|
||||
"USDT (invalid - 1/3 of address)" to "Es9vMFrzaCERmJ",
|
||||
"ETH (full)" to "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CasesListContent(
|
||||
casesList: List<Pair<String, String>>,
|
||||
onItemClick: VoidCallback,
|
||||
) {
|
||||
LazyColumn(content = {
|
||||
item {
|
||||
Row() {
|
||||
ResetContractAddressButton(onItemClick)
|
||||
Text("", modifier = Modifier.weight(1f))
|
||||
ResetAllFieldsButton(onItemClick)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
items(casesList.size) {
|
||||
val (info, address) = casesList[it]
|
||||
ContractAddressButton(info, address, onItemClick)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetAllFieldsButton(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(name = "Reset") {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetContractAddressButton(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(name = "Set empty address") {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContractAddressButton(
|
||||
name: String,
|
||||
address: String,
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
name = name,
|
||||
) {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
modifier: Modifier = Modifier,
|
||||
name: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
onClick = onClick
|
||||
) { Text(name, fontSize = 12.sp) }
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TestAddCustomTokenActions() {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Column() {
|
||||
// deep test
|
||||
// ActionRow("Invalid field value") { InvalidFieldValue() }
|
||||
// ActionRow("Active(true)") { ActiveTrue() }
|
||||
// ActionRow("Active(false) && decimalCount != null") { ActiveFalseDecimals() }
|
||||
// ActionRow("In several networks") { InSeveralNetworks() }
|
||||
// ActionRow("Address not found") { UnknownContracts() }
|
||||
|
||||
// test
|
||||
ActionRow("All in one") { AllInOne() }
|
||||
|
||||
// Any action
|
||||
// ActionRow("CustomActions - find coins active=false, decimals != null") { CustomActions() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AllInOne() {
|
||||
// validation error
|
||||
// ContractAddressButton(
|
||||
// name = "invalid",
|
||||
// address = "unk"
|
||||
// )
|
||||
// active = true
|
||||
ContractAddressButton(
|
||||
name = "true",
|
||||
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
)
|
||||
// more than one network
|
||||
ContractAddressButton(
|
||||
name = ">1 network",
|
||||
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
|
||||
)
|
||||
// unknown
|
||||
ContractAddressButton(
|
||||
name = "unknown",
|
||||
address = "0x1111111111111111112111111111111111111113"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "full custom",
|
||||
address = "0x3019BF2a2eF8040C242C9a4c5c4BD4C81678b2A1",
|
||||
tokenNetwork = Blockchain.Ethereum,
|
||||
tokenName = "Test unknown",
|
||||
tokenSymbol = "TU",
|
||||
tokenDecimals = "5",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvalidFieldValue() {
|
||||
ContractAddressButton(
|
||||
name = "unk",
|
||||
address = "unk"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "someText",
|
||||
address = "someText"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "alskdml...fa s",
|
||||
address = "alskdmlasd asdln alsdknflasd flasd fa s"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveTrue() {
|
||||
ContractAddressButton(
|
||||
name = "USDC- Ethereum",
|
||||
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "USDT- Avalanche",
|
||||
address = "0xc7198437980c041c805a1edcba50c1ce5db95118"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "VID- Fantom",
|
||||
address = "0x922d641a426dcffaef11680e5358f34d97d112e1"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveFalseDecimals() {
|
||||
ContractAddressButton(
|
||||
name = "ALPHA- avalanche",
|
||||
address = "0x2147efff675e4a4ee1c2f918d181cdbd7a8e208f"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "BETA- avalanche",
|
||||
address = "0x511d35c52a3c244e7b8bd92c0c297755fbd89212"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InSeveralNetworks() {
|
||||
ContractAddressButton(
|
||||
name = "ALPHA- Eth,Bsc",
|
||||
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "BETA- Eth,Bsc",
|
||||
address = "0xbe1a001fe942f96eea22ba08783140b9dcc09d28"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnknownContracts() {
|
||||
ContractAddressButton(
|
||||
name = "0x11...113",
|
||||
address = "0x1111111111111111112111111111111111111113"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "0xc7...111",
|
||||
address = "0xc7198437980c041c805a1edcba50c1ce5db95111"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomActions() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionRow(
|
||||
name: String,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Column() {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = name,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) { content() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionButton(
|
||||
name: String,
|
||||
onClick: VoidCallback,
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
onClick = onClick
|
||||
) { Text(name, fontSize = 8.sp) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContractAddressButton(
|
||||
name: String,
|
||||
address: String,
|
||||
tokenNetwork: Blockchain? = null,
|
||||
tokenName: String? = null,
|
||||
tokenSymbol: String? = null,
|
||||
tokenDecimals: String? = null,
|
||||
) {
|
||||
ActionButton(name = name) {
|
||||
resetTokenValues()
|
||||
domainStore.dispatch(OnTokenContractAddressChanged(Field.Data(address, false)))
|
||||
tokenNetwork?.let {
|
||||
domainStore.dispatch(OnTokenNetworkChanged(Field.Data(tokenNetwork, false)))
|
||||
}
|
||||
tokenName?.let {
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(tokenName, false)))
|
||||
}
|
||||
tokenSymbol?.let {
|
||||
domainStore.dispatch(OnTokenSymbolChanged(Field.Data(tokenSymbol, false)))
|
||||
}
|
||||
tokenDecimals?.let {
|
||||
domainStore.dispatch(OnTokenDecimalsChanged(Field.Data(tokenDecimals, false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomActionButton(
|
||||
name: String,
|
||||
action: suspend () -> Unit
|
||||
) {
|
||||
val startValue = 0
|
||||
val anyValue = remember { mutableStateOf(startValue) }
|
||||
LaunchedEffect(key1 = anyValue.value, block = {
|
||||
if (anyValue.value != startValue) {
|
||||
action()
|
||||
}
|
||||
})
|
||||
|
||||
ActionButton(name) { anyValue.value = anyValue.value + 1 }
|
||||
}
|
||||
|
||||
private fun resetTokenValues() {
|
||||
domainStore.dispatch(OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TestCasesList(
|
||||
onItemClick: (TestCase) -> Unit
|
||||
) {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Surface(
|
||||
color = colorResource(id = R.color.lightGray5)
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
listOf(
|
||||
TestCase.ContractAddress,
|
||||
TestCase.SolanaTokens,
|
||||
).map { TestCaseListItem(it, onItemClick) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TestCaseListItem(
|
||||
testCase: TestCase,
|
||||
onItemClick: (TestCase) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = testCase.description,
|
||||
)
|
||||
Button(
|
||||
onClick = { onItemClick(testCase) }
|
||||
) { Text("Start") }
|
||||
}
|
||||
}
|
||||
|
||||
enum class TestCase(val description: String, val content: @Composable (VoidCallback) -> Unit) {
|
||||
ContractAddress("Test contract address field", { ContractAddressTests(it) }),
|
||||
Auto("Test contract address field", { ContractAddressTests(it) }),
|
||||
SolanaTokens("Test Solana contract addresses", { SolanaAddressTests(it) }), ;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue