Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-15 00:19:31 +03:00
parent aa3783b32e
commit 59b4045b71
6 changed files with 205 additions and 98 deletions

View file

@ -2,7 +2,9 @@ package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@ -83,6 +85,18 @@ fun PasteButton(
}
}
/**
* Used for disable ripple if button is enable = false
*/
@Composable
fun ToggledRippleTheme(
isEnabled: Boolean,
content: @Composable () -> Unit,
) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
}
@Preview
@Composable
fun ButtonTest() {

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.compose
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
/**
[REDACTED_AUTHOR]
*/
class NoRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}

View file

@ -25,6 +25,7 @@ import com.tangem.domain.features.addCustomToken.redux.ScreenState
import com.tangem.domain.features.addCustomToken.redux.ViewStates
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.ComposeDialogManager
import com.tangem.tap.common.compose.ToggledRippleTheme
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
import com.tangem.wallet.R
@ -107,7 +108,7 @@ fun Warnings(warnings: List<AddCustomTokenWarning>) {
Column {
warnings.forEachIndexed { index, item ->
val modifier = when (index) {
0 -> Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)
0 -> Modifier.padding(16.dp, 0.dp, 16.dp, 0.dp)
warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
}
@ -120,7 +121,7 @@ fun Warnings(warnings: List<AddCustomTokenWarning>) {
Text(
modifier = Modifier.padding(16.dp),
text = warningConverter.convertError(item),
color = colorResource(id = R.color.lightGray0),
color = colorResource(id = R.color.white),
fontSize = 14.sp
)
}
@ -138,36 +139,34 @@ private fun AddButton(state: MutableState<AddCustomTokenState>) {
}
@Composable
fun AddCustomTokenFab(
private fun AddCustomTokenFab(
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
onClick: () -> Unit
) {
val contentColor = if (isEnabled) {
Color.White
val contentColor = Color.White
val backgroundColor = if (isEnabled) {
Color(0xFF1ACE80)
} else {
colorResource(id = R.color.darkGray1)
Color(0xFFB9E6D3)
}
val backgroundColor = Color(0xFF1ACE80)
ExtendedFloatingActionButton(
modifier = modifier,
icon = {
Icon(
imageVector = Icons.Filled.Add,
tint = contentColor,
contentDescription = "Add",
)
},
text = {
Text(
text = stringResource(id = R.string.common_add),
)
},
onClick = onClick,
backgroundColor = backgroundColor,
contentColor = contentColor,
)
ToggledRippleTheme(isEnabled) {
ExtendedFloatingActionButton(
modifier = modifier,
icon = {
Icon(
imageVector = Icons.Filled.Add,
tint = contentColor,
contentDescription = "Add",
)
},
text = { Text(text = stringResource(id = R.string.common_add)) },
onClick = { if (isEnabled) onClick() },
backgroundColor = backgroundColor,
contentColor = contentColor,
)
}
}
data class ScreenFieldData(

View file

@ -26,7 +26,7 @@ class StringIsNotEmptyValidator : CustomTokenValidator<String>() {
class TokenContractAddressValidator : CustomTokenValidator<String>() {
override fun validate(data: String?): AddCustomTokenError? {
if (data == null || data.isEmpty()) return null
if (data == null || data.isEmpty()) return AddCustomTokenError.FieldIsEmpty
return if (EthereumAddressService().validate(data)) {
null

View file

@ -2,12 +2,10 @@ package com.tangem.domain.features.addCustomToken.redux
import android.webkit.ValueCallback
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.services.Result
import com.tangem.domain.DomainDialog
import com.tangem.domain.DomainException
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.form.*
@ -41,6 +39,17 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
return storeState.copy(addCustomTokensState = newHubState)
}
private val contractAddressValidator: TokenContractAddressValidator
get() = hubState.getValidator(ContractAddress)
private val nameValidator: TokenNameValidator
get() = hubState.getValidator(Name)
private val symbolValidator: TokenSymbolValidator
get() = hubState.getValidator(Symbol)
private val decimalsValidator: TokenDecimalsValidator
get() = hubState.getValidator(Decimals)
val networkValidator: TokenNetworkValidator
get() = hubState.getValidator(Network)
override suspend fun handleAction(
action: Action,
storeState: DomainState,
@ -52,76 +61,70 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
is Init.SetAddedCurrencies -> {}
is Init.SetOnAddTokenCallback -> {}
is OnCreate -> {
hubState.addedCurrencies.guard {
return throwUnAppropriateInitialization("addedTokens")
}
// hubState.addedCurrencies.guard {
// return throwUnAppropriateInitialization("addedTokens")
// }
}
is OnDestroy -> hubScope.cancel()
is OnTokenContractAddressChanged -> {
dispatchOnMain(
Screen.UpdateAddButton(
ViewStates.AddButton(!hubState.allFieldsIsEmpty())
)
)
val contractAddress = action.contractAddress.value
val validator: TokenContractAddressValidator = hubState.getValidator(ContractAddress)
val error = validator.validate(contractAddress)
addOrRemoveError(ContractAddress, error)
if (error != null || contractAddress.isEmpty()) {
dispatchOnMain(unlockTokenFields())
return
val address = action.contractAddress.value
when (val error = ContractAddress.validate(address)) {
null -> {
ContractAddress.removeError()
dispatchOnMain(unlockTokenFieldsAction())
}
AddCustomTokenError.FieldIsEmpty -> {
ContractAddress.removeError()
dispatchOnMain(lockTokenFieldsAction())
return
}
AddCustomTokenError.InvalidContractAddress -> {
ContractAddress.addError(error)
dispatchOnMain(unlockTokenFieldsAction())
return
}
else -> {}
}
if (!action.contractAddress.isUserInput) return
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
manageTokenChanges(requestInfoAboutContractAddress(address))
}
is OnTokenNetworkChanged -> {
if (!action.blockchainNetwork.isUserInput) return
val contractAddress = hubState.getField<TokenField>(ContractAddress).data.value
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
val contractAddress = ContractAddress.getFieldValue<String>()
val error = ContractAddress.validate(contractAddress)
if (error == null) {
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
} else {
}
}
is OnTokenNameChanged -> {
val validator: TokenNameValidator = hubState.getValidator(Name)
addOrRemoveError(Name, validator.validate(action.tokenName.value))
Name.addOrRemoveError(Name.validate(action.tokenName.value))
}
is OnTokenSymbolChanged -> {
val validator: TokenSymbolValidator = hubState.getValidator(Symbol)
addOrRemoveError(Symbol, validator.validate(action.tokenSymbol.value))
Symbol.addOrRemoveError(Symbol.validate(action.tokenSymbol.value))
}
is OnTokenDecimalsChanged -> {
val validator: TokenDecimalsValidator = hubState.getValidator(Decimals)
addOrRemoveError(Decimals, validator.validate(action.tokenDecimals.value))
Decimals.addOrRemoveError(Decimals.validate(action.tokenDecimals.value))
}
// is OnTokenDerivationPathChanged -> {
// val validator: TokenDerivationPathValidator = getValidator(DerivationPath, hubState)
// addOrRemoveError(DerivationPath, validator.validate(action.value.value))
// }
is ClearTokenFields -> {
val nameField = hubState.getField<TokenField>(Name)
val symbolField = hubState.getField<TokenField>(Symbol)
val decimalsField = hubState.getField<TokenField>(Decimals)
nameField.data = Field.Data("", false)
symbolField.data = Field.Data("", false)
decimalsField.data = Field.Data("", false)
Name.setFieldValue(Field.Data("", false))
Symbol.setFieldValue(Field.Data("", false))
Decimals.setFieldValue(Field.Data("", false))
dispatchOnMain(UpdateForm(hubState))
}
is FillTokenFields -> {
val networkField = hubState.getField<TokenBlockchainField>(Network)
val nameField = hubState.getField<TokenField>(Name)
val symbolField = hubState.getField<TokenField>(Symbol)
val decimalsField = hubState.getField<TokenField>(Decimals)
val token = action.token
val contract = action.contract
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown
networkField.data = Field.Data(blockchain, false)
nameField.data = Field.Data(token.name, false)
symbolField.data = Field.Data(token.symbol, false)
decimalsField.data = Field.Data(contract.decimalCount.toString(), false)
Network.setFieldValue(Field.Data(blockchain, false))
Name.setFieldValue(Field.Data(token.name, false))
Symbol.setFieldValue(Field.Data(token.symbol, false))
Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false))
dispatchOnMain(UpdateForm(hubState))
}
@ -129,7 +132,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
// if (hubState.allFieldsIsEmpty()) {
dispatchOnMain(
DomainGlobalAction.ShowDialog(DomainDialog.DialogError(
AddCustomTokenError.FieldIsEmpty
AddCustomTokenError.InvalidDerivationPath
)))
return
// }
@ -185,7 +188,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(unlockTokenFields())
dispatchOnMain(unlockTokenFieldsAction())
}
else -> {
val token = foundTokens[0]
@ -204,7 +207,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
dispatchOnMain(lockTokenFields())
dispatchOnMain(lockTokenFieldsAction())
} else {
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
@ -214,11 +217,11 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
if (tokenContract.active && isStandardDerivation) {
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(FillTokenFields(token, contract))
dispatchOnMain(lockTokenFields())
dispatchOnMain(lockTokenFieldsAction())
} else {
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(unlockTokenFields())
dispatchOnMain(unlockTokenFieldsAction())
}
}
}
@ -236,7 +239,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
hubScope.launch {
// find how to connect to the upper coroutineContext and dispatch through them
dispatchOnMain(FillTokenFields(token, selectedContract))
dispatchOnMain(lockTokenFields())
dispatchOnMain(lockTokenFieldsAction())
}
},
)
@ -256,15 +259,69 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
contract: Coins.CheckAddressResponse.Token.Contract
): Boolean = false
private suspend fun addOrRemoveError(id: CustomTokenFieldId, error: AddCustomTokenError?) {
if (error == null) {
dispatchOnMain(FieldError.Remove(id))
} else {
dispatchOnMain(FieldError.Add(id, error))
private suspend fun CustomTokenFieldId.addError(error: AddCustomTokenError) {
dispatchOnMain(FieldError.Add(this, error))
}
private suspend fun CustomTokenFieldId.removeError() {
dispatchOnMain(FieldError.Remove(this))
}
private suspend fun CustomTokenFieldId.addOrRemoveError(error: AddCustomTokenError?) {
when (error) {
null -> removeError()
else -> addError(error)
}
}
private fun lockTokenFields(): Action {
private inline fun <reified T> CustomTokenFieldId.getField(): T {
val state = hubState
val value = when (this) {
ContractAddress -> state.getField<TokenField>(this)
Network -> state.getField<TokenBlockchainField>(this)
Name -> state.getField<TokenField>(this)
Symbol -> state.getField<TokenField>(this)
Decimals -> state.getField<TokenField>(this)
DerivationPath -> state.getField<TokenDerivationPathField>(this)
}
return value as T
}
private inline fun <reified T> CustomTokenFieldId.getFieldValue(): T {
val value = when (this) {
ContractAddress -> getField<TokenField>().data.value
Network -> getField<TokenBlockchainField>().data.value
Name -> getField<TokenField>().data.value
Symbol -> getField<TokenField>().data.value
Decimals -> getField<TokenField>().data.value
DerivationPath -> getField<TokenDerivationPathField>().data.value
}
return value as T
}
private fun CustomTokenFieldId.setFieldValue(fieldData: Field.Data<*>) {
when (this) {
ContractAddress -> getField<TokenField>().data = fieldData as Field.Data<String>
Network -> getField<TokenBlockchainField>().data = fieldData as Field.Data<Blockchain>
Name -> getField<TokenField>().data = fieldData as Field.Data<String>
Symbol -> getField<TokenField>().data = fieldData as Field.Data<String>
Decimals -> getField<TokenField>().data = fieldData as Field.Data<String>
DerivationPath -> getField<TokenDerivationPathField>().data = fieldData as Field.Data<Blockchain>
}
}
private fun CustomTokenFieldId.validate(value: Any): AddCustomTokenError? {
return when (this) {
ContractAddress -> contractAddressValidator.validate(value as String)
Network -> networkValidator.validate(value as Blockchain)
Name -> nameValidator.validate(value as String)
Symbol -> symbolValidator.validate(value as String)
Decimals -> decimalsValidator.validate(value as String)
DerivationPath -> networkValidator.validate(value as Blockchain)
}
}
private fun lockTokenFieldsAction(): Action {
val state = hubState
return Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = false),
@ -274,7 +331,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
))
}
private fun unlockTokenFields(): Action {
private fun unlockTokenFieldsAction(): Action {
val state = hubState
return Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = true),
@ -284,6 +341,19 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
))
}
private suspend fun toggleAddButtonAction(enable: Boolean) = when (enable) {
true -> unlockAddButtonAction()
else -> lockAddButtonAction()
}
private suspend fun lockAddButtonAction() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
}
private suspend fun unlockAddButtonAction() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
}
override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState {
return when (action) {
is Init.SetAddedCurrencies -> {
@ -293,11 +363,12 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
state.copy(onTokenAddCallback = action.callback)
}
is OnCreate -> {
val card = requireNotNull(globalState.scanResponse?.card)
// val card = requireNotNull(globalState.scanResponse?.card)
val tangemTechServiceManager = TangemTechServiceManager(TangemTechService())
tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString())
// tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString())
state.copy(
derivationStyle = card.derivationStyle,
// derivationStyle = card.derivationStyle,
derivationStyle = DerivationStyle.LEGACY,
tangemTechServiceManager = tangemTechServiceManager
)
}
@ -435,4 +506,8 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
"AddCustomTokenHub", "$objName must be not NULL"
)
}
companion object {
}
}

View file

@ -73,12 +73,14 @@ data class AddCustomTokenState(
// val errors = mutableMapOf<>()
fieldsToCheck.forEach { field ->
val error = validator.validate(field.data.value?.toString())
if (error != null) return true
if (error != null) return false
}
return false
return true
}
fun allFieldsIsEmpty(): Boolean = networkIsEmpty() && customTokensFieldsIsEmpty()
fun allFieldsIsEmpty(): Boolean {
return networkIsEmpty() && customTokensFieldsIsEmpty()
}
private fun getToken(): CompleteData.CustomToken {
return CompleteData.CustomToken.Converter(tokenId)
@ -138,11 +140,11 @@ data class AddCustomTokenState(
return ScreenState(
contractAddressField = ViewStates.TokenField(),
network = ViewStates.TokenField(),
name = ViewStates.TokenField(),
symbol = ViewStates.TokenField(),
decimals = ViewStates.TokenField(),
name = ViewStates.TokenField(isEnabled = false),
symbol = ViewStates.TokenField(isEnabled = false),
decimals = ViewStates.TokenField(isEnabled = false),
derivationPath = ViewStates.TokenField(),
addButton = ViewStates.AddButton()
addButton = ViewStates.AddButton(isEnabled = false)
)
}
}