Updated on 2026-08-14

This commit is contained in:
Tangem 2022-05-31 14:20:10 +03:00
parent 8312c85fa6
commit fb1ed471af
7 changed files with 420 additions and 428 deletions

View file

@ -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

View file

@ -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) }
}

View file

@ -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)))
}

View file

@ -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) }), ;
}