Updated on 2026-08-14
This commit is contained in:
parent
787d354d3d
commit
a07f371456
36 changed files with 666 additions and 469 deletions
|
|
@ -19,7 +19,7 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Button(
|
||||
fun RectangleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
|
|
@ -90,7 +90,7 @@ fun ButtonTest() {
|
|||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
PreviewItem("Button") {
|
||||
Button(text = "Some button") {}
|
||||
RectangleButton(text = "Some button") {}
|
||||
}
|
||||
PreviewItem("PasteButton") {
|
||||
PasteButton(onClick = {})
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -22,7 +21,9 @@ import com.tangem.domain.DomainStateDialog
|
|||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.domain.redux.global.DomainGlobalState
|
||||
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Composable
|
||||
|
|
@ -51,12 +52,19 @@ fun ComposeDialogManager() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
||||
private fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
||||
if (dialogState.value == null) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { DomainErrorConverter(context) }
|
||||
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
|
||||
|
||||
when (val dialog = dialogState.value) {
|
||||
is DomainDialog.DialogError -> ErrorDialog(
|
||||
title = stringResource(id = R.string.common_error),
|
||||
body = errorConverter.convertError(dialog.error),
|
||||
onDismissRequest
|
||||
)
|
||||
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,17 +91,7 @@ fun <T> SimpleDialog(
|
|||
Column(
|
||||
modifier = Modifier.padding(22.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = LocalTextStyle.provides(
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
).value
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
DialogTitle(title = title)
|
||||
LazyColumn() {
|
||||
items(items) { item ->
|
||||
Row(
|
||||
|
|
@ -111,4 +109,36 @@ fun <T> SimpleDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogTitle(title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
style = LocalTextStyle.provides(
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
).value
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorDialog(
|
||||
title: String,
|
||||
body: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
title = { DialogTitle(title) },
|
||||
text = { Text(body) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
Button(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(id = R.string.common_ok))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
sealed class Keyboard {
|
||||
data class Opened(val height: Int): Keyboard()
|
||||
data class Opened(val height: Int) : Keyboard()
|
||||
object Closed : Keyboard()
|
||||
}
|
||||
|
||||
|
|
@ -14,12 +14,21 @@ sealed class Keyboard {
|
|||
fun keyboardAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
val view = LocalView.current
|
||||
val discrepancy = remember {
|
||||
mutableStateOf(0)
|
||||
}
|
||||
DisposableEffect(view) {
|
||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
val keypadHeight: Int = screenHeight - (rect.bottom + rect.top) - discrepancy.value
|
||||
if (discrepancy.value == 0) {
|
||||
discrepancy.value = keypadHeight;
|
||||
if (keypadHeight == 0) discrepancy.value = 1
|
||||
}
|
||||
|
||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
||||
Keyboard.Opened(keypadHeight)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun copyToClipboard(value: Any, label: String = "") {
|
||||
LocalContext.current.copyToClipboard(value, label)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
return LocalContext.current.getFromClipboard(default)
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
|
||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -82,6 +83,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.AddTokens -> AddTokensFragment()
|
||||
AppScreen.AddCustomToken -> AddCustomTokenFragment()
|
||||
AppScreen.WalletDetails -> WalletDetailsFragment()
|
||||
AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import com.squareup.picasso.Transformation
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.IconsUtil
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.tap.domain.tokens.toNetworkId
|
||||
import com.tangem.wallet.R
|
||||
|
||||
fun Picasso.loadCurrenciesIcon(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ enum class AppScreen {
|
|||
Wallet, WalletDetails,
|
||||
Send,
|
||||
Details, DetailsConfirm, DetailsSecurity,
|
||||
AddTokens,
|
||||
AddTokens, AddCustomToken,
|
||||
WalletConnectSessions,
|
||||
QrScan
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
|
|
@ -74,62 +75,4 @@ data class Contract(
|
|||
|
||||
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 -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalaunche"
|
||||
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 -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
}
|
||||
|
|
@ -10,40 +10,40 @@ import com.tangem.wallet.R
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CustomTokenErrorConverter(
|
||||
class DomainErrorConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val errorMessage = when (error) {
|
||||
is AddCustomTokenError -> AddCustomTokenConverter(context).convertError(error)
|
||||
else -> null
|
||||
}
|
||||
return errorMessage?.let { it } ?: "Unknown error: ${error::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val customTokenError = (error as? AddCustomTokenError) ?: throw UnsupportedOperationException()
|
||||
|
||||
val resId = when (customTokenError) {
|
||||
val rawMessage = when (customTokenError) {
|
||||
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
|
||||
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
|
||||
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
|
||||
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
|
||||
AddCustomTokenError.InvalidDecimalsCount -> R.string.custom_token_creation_error_wrong_decimals
|
||||
AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_empty_fields
|
||||
else -> null
|
||||
}
|
||||
return resId?.let { context.getString(it) } ?: "Unknown error: ${customTokenError::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTokenWarningConverter(
|
||||
private val context: Context
|
||||
) : ErrorConverter<String> {
|
||||
|
||||
override fun convertError(error: DomainError): String {
|
||||
val customTokenWarning = (error as? AddCustomTokenWarning) ?: throw UnsupportedOperationException()
|
||||
|
||||
val rawMessage = when (customTokenWarning) {
|
||||
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
|
||||
AddCustomTokenWarning.Network.CheckAddressRequestError -> "CheckAddressRequestError"
|
||||
}
|
||||
return when (rawMessage) {
|
||||
is Int -> context.getString(rawMessage)
|
||||
is String -> rawMessage
|
||||
else -> "Unknown error: ${customTokenWarning::class.java.simpleName}"
|
||||
// is String -> rawMessage
|
||||
else -> "Unknown error: ${customTokenError::class.java.simpleName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,34 +2,31 @@ package com.tangem.tap.features.tokens.addCustomToken.compose
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.domain.ErrorConverter
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
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.*
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenErrorConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.CustomTokenWarningConverter
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -44,12 +41,18 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
backgroundColor = colorResource(id = R.color.backgroundLightGray),
|
||||
floatingActionButton = {
|
||||
HangingOverKeyboardView(keyboardState = keyboardAsState()) {
|
||||
AddButton(state)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
item { AddCustomTokenDebugActions() }
|
||||
// item { AddCustomTokenDebugActions() }
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -67,18 +70,6 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
}
|
||||
item { Warnings(state.value.warnings.toList()) }
|
||||
}
|
||||
HangingOverKeyboardView(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter),
|
||||
keyboardState = keyboardAsState(),
|
||||
defaultBottomPadding = 30.dp,
|
||||
spaceBetweenKeyboard = 20.dp,
|
||||
) {
|
||||
AddButton(
|
||||
isEnabled = state.value.screenState.addButton.isEnabled
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ComposeDialogManager()
|
||||
}
|
||||
|
|
@ -90,7 +81,7 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
@Composable
|
||||
private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { CustomTokenErrorConverter(context) }
|
||||
val errorConverter = remember { DomainErrorConverter(context) }
|
||||
|
||||
val stateValue = state.value
|
||||
stateValue.form.fieldList.forEach { field ->
|
||||
|
|
@ -107,124 +98,11 @@ private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_contract_address_input_title,
|
||||
placeholder = "0x0000000000000000",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
isLoading = screenFieldData.viewState.isLoading,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenContractAddressChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenNameView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_name_input_title,
|
||||
placeholderId = R.string.custom_token_name_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
) { domainStore.dispatch(OnTokenNetworkChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_token_symbol_input_title,
|
||||
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(OnTokenSymbolChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_decimals_input_title,
|
||||
placeholder = "8",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
) { domainStore.dispatch(OnTokenDecimalsChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
|
||||
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
|
||||
TitleSubtitle(derivationPathLabel, blockchainName)
|
||||
}
|
||||
) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
||||
fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
||||
if (warnings.isEmpty()) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val warningConverter = remember { CustomTokenWarningConverter(context) }
|
||||
val warningConverter = remember { DomainErrorConverter(context) }
|
||||
|
||||
Column {
|
||||
warnings.forEachIndexed { index, item ->
|
||||
|
|
@ -236,8 +114,8 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.darkGray2),
|
||||
contentColor = colorResource(id = R.color.darkGray3)
|
||||
color = colorResource(id = R.color.warning_warning),
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -251,30 +129,48 @@ private fun Warnings(warnings: List<AddCustomTokenWarning>) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(
|
||||
private fun AddButton(state: MutableState<AddCustomTokenState>) {
|
||||
AddCustomTokenFab(
|
||||
modifier = Modifier
|
||||
.widthIn(210.dp, 280.dp),
|
||||
isEnabled = state.value.screenState.addButton.isEnabled
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddCustomTokenFab(
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean,
|
||||
textId: Int = R.string.common_add,
|
||||
onClick: () -> Unit,
|
||||
isEnabled: Boolean = true,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
textId = textId,
|
||||
isEnabled = isEnabled,
|
||||
modifier = modifier
|
||||
.height(52.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
leadingView = {
|
||||
val contentColor = if (isEnabled) {
|
||||
Color.White
|
||||
} else {
|
||||
colorResource(id = R.color.darkGray1)
|
||||
}
|
||||
val backgroundColor = Color(0xFF1ACE80)
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
tint = contentColor,
|
||||
contentDescription = "Add",
|
||||
)
|
||||
},
|
||||
onClick = onClick
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_add),
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
backgroundColor = backgroundColor,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
}
|
||||
|
||||
private data class ScreenFieldData(
|
||||
data class ScreenFieldData(
|
||||
val field: DataField<*>,
|
||||
val error: AddCustomTokenError?,
|
||||
val errorConverter: ErrorConverter<String>,
|
||||
|
|
@ -284,7 +180,7 @@ private data class ScreenFieldData(
|
|||
fun fromState(
|
||||
field: DataField<*>,
|
||||
state: AddCustomTokenState,
|
||||
errorConverter: CustomTokenErrorConverter
|
||||
errorConverter: DomainErrorConverter
|
||||
): ScreenFieldData {
|
||||
return ScreenFieldData(
|
||||
field = field,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.TangemTechServiceManager
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -145,7 +147,7 @@ private fun CustomActions() {
|
|||
CustomActionButton(
|
||||
name = "Find tokens in several networks",
|
||||
action = {
|
||||
val manager = domainStore.state.addCustomTokensState.addCustomTokenManager
|
||||
val manager = TangemTechServiceManager(TangemTechService())
|
||||
val currencies = manager.tokens()
|
||||
val asdfsd = mutableMapOf<String, MutableList<Any>>()
|
||||
val contractAddresses = currencies.mapNotNull { currency ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
|
||||
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
|
||||
import com.tangem.domain.features.addCustomToken.TokenField
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.BlockchainSpinner
|
||||
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
|
||||
import com.tangem.tap.common.compose.SpacerH8
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_contract_address_input_title,
|
||||
placeholder = "0x0000000000000000",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
isLoading = screenFieldData.viewState.isLoading,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNameView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_name_input_title,
|
||||
placeholderId = R.string.custom_token_name_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data(it)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_token_symbol_input_title,
|
||||
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
textFieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_decimals_input_title,
|
||||
placeholder = "8",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
|
||||
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
|
||||
TitleSubtitle(derivationPathLabel, blockchainName)
|
||||
}
|
||||
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(it))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -21,36 +19,19 @@ import com.tangem.tap.common.compose.Keyboard
|
|||
fun HangingOverKeyboardView(
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardState: State<Keyboard>,
|
||||
defaultBottomPadding: Dp = 0.dp,
|
||||
spaceBetweenKeyboard: Dp = 10.dp,
|
||||
calculateWithActionBarHeight: Boolean = true,
|
||||
spaceBetweenKeyboard: Dp = 0.dp,
|
||||
content: @Composable() (BoxScope.() -> Unit)
|
||||
) {
|
||||
fun getActionBarHeight(context: Context): Int {
|
||||
val typedValue = TypedValue()
|
||||
return if (context.theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
|
||||
val data = typedValue.data
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
TypedValue.complexToDimensionPixelSize(data, displayMetrics)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val calculatedPadding = when (keyboardState.value) {
|
||||
Keyboard.Closed -> defaultBottomPadding
|
||||
val padding = when (keyboardState.value) {
|
||||
Keyboard.Closed -> 0.dp
|
||||
is Keyboard.Opened -> {
|
||||
val keyboardHeight = (keyboardState.value as Keyboard.Opened).height
|
||||
val keyboardPadding = context.pxToDp(keyboardHeight.toFloat()).dp
|
||||
if (calculateWithActionBarHeight) {
|
||||
val actionBarHeight = context.pxToDp(getActionBarHeight(context).toFloat()).dp
|
||||
keyboardPadding + spaceBetweenKeyboard - actionBarHeight
|
||||
} else {
|
||||
keyboardPadding + spaceBetweenKeyboard
|
||||
}
|
||||
|
||||
keyboardPadding + spaceBetweenKeyboard
|
||||
}
|
||||
}
|
||||
Box(modifier.padding(bottom = calculatedPadding)) { content() }
|
||||
|
||||
Box(modifier.padding(bottom = padding)) { content() }
|
||||
}
|
||||
|
|
@ -18,11 +18,13 @@ sealed class TokensAction : Action {
|
|||
|
||||
data class SetAddedCurrencies(
|
||||
val wallets: List<WalletData>, val derivationStyle: DerivationStyle?
|
||||
) : TokensAction()
|
||||
) : TokensAction()
|
||||
data class SetNonRemovableCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class SaveChanges(
|
||||
val addedTokens: List<TokenWithBlockchain>,
|
||||
val addedBlockchains: List<Blockchain>
|
||||
) : TokensAction()
|
||||
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction()
|
||||
}
|
||||
|
|
@ -8,16 +8,22 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.DomainWrapped
|
||||
import com.tangem.domain.common.KeyWalletPublicKey
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.features.addCustomToken.CompleteData
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddedCurrencies
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
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.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
|
|
@ -26,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
class TokensMiddleware {
|
||||
|
||||
|
|
@ -35,6 +42,7 @@ class TokensMiddleware {
|
|||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
@ -80,6 +88,22 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) {
|
||||
val tokensState = store.state.tokensState
|
||||
val addedTokensList = tokensState.addedTokens.map {
|
||||
DomainWrapped.TokenWithBlockchain(it.token.copy(), it.blockchain)
|
||||
}
|
||||
val addedBlockchains = tokensState.addedBlockchains.map { it }
|
||||
val addedCurrencies = AddedCurrencies(addedTokensList, addedBlockchains)
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
|
||||
|
||||
val callback = fun(data: CompleteData) {
|
||||
Timber.e("Yoooohhhoooo")
|
||||
}
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(callback))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
|
||||
}
|
||||
|
||||
private fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.features.tokens.redux
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_search -> true
|
||||
R.id.menu_navigate_add_custom_token -> {
|
||||
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ 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.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.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ 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.domain.common.extensions.fromNetworkId
|
||||
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.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ 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.domain.common.extensions.fromNetworkId
|
||||
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.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
|
|
|||
|
|
@ -9,4 +9,11 @@
|
|||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_navigate_add_custom_token"
|
||||
android:icon="@drawable/ic_add"
|
||||
android:title="@string/common_add"
|
||||
app:iconTint="@color/darkGray2"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
|
|
@ -17,8 +17,17 @@
|
|||
<color name="warning">#FFB71B</color>
|
||||
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="darkGray1">#8E8E93</color>
|
||||
<color name="darkGray2">#636366</color>
|
||||
|
||||
<color name="lightGray0">#F3F3F3</color>
|
||||
<color name="lightGray1">#F8F8FB</color>
|
||||
<color name="lightGray2">#DADADF</color>
|
||||
<color name="lightGray3">#D0D0D5</color>
|
||||
<color name="lightGray4">#C7C7CC</color>
|
||||
<color name="lightGray5">#D1D1D6</color>
|
||||
<color name="lightGray6">#CACACC</color>
|
||||
<color name="darkGray0">#B6B6B8</color>
|
||||
<color name="darkGray1">#8E8E90</color>
|
||||
<color name="darkGray2">#666668</color>
|
||||
<color name="darkGray3">#48484A</color>
|
||||
<color name="darkGray4">#3A3A3C</color>
|
||||
<color name="darkGray5">#2C2C2E</color>
|
||||
|
|
@ -37,14 +46,6 @@
|
|||
<color name="twins_dark">#14181D</color>
|
||||
|
||||
|
||||
<color name="lightGray0">#F3F3F3</color>
|
||||
<color name="lightGray1">#F8F8FB</color>
|
||||
<color name="lightGray2">#DADADF</color>
|
||||
<color name="lightGray3">#D0D0D5</color>
|
||||
<color name="lightGray4">#C7C7CC</color>
|
||||
<color name="lightGray5">#D1D1D6</color>
|
||||
<color name="lightGray6">#C9C9CD</color>
|
||||
|
||||
<color name="separatorGrey1">#1F000000</color>
|
||||
<color name="separatorGrey2">#14212121</color>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package com.tangem.domain
|
|||
* @property message the error description
|
||||
* @property data any data that can help in the part where this error is being handled
|
||||
*/
|
||||
interface DomainError {
|
||||
interface DomainError : DomainMessage {
|
||||
val code: Int
|
||||
val message: String
|
||||
val data: Any?
|
||||
|
|
|
|||
|
|
@ -10,4 +10,8 @@ sealed class DomainException(message: String?) : Throwable(message), DomainInter
|
|||
data class SelectTokeNetworkException(val networkId: String) : DomainException(
|
||||
"Unknown network [$networkId] should not be included in the network selection dialog."
|
||||
)
|
||||
|
||||
data class UnAppropriateInitializationException(val of: String, val info: String? = null) : DomainException(
|
||||
"The [$of], must be properly initialized. Info []"
|
||||
)
|
||||
}
|
||||
15
domain/src/main/java/com/tangem/domain/DomainMessage.kt
Normal file
15
domain/src/main/java/com/tangem/domain/DomainMessage.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface DomainMessage
|
||||
|
||||
sealed interface DomainNotification : DomainMessage {
|
||||
interface Toast : DomainNotification {}
|
||||
|
||||
interface Snackbar : DomainNotification {}
|
||||
|
||||
interface Dialog : DomainNotification {}
|
||||
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ interface DomainStateDialog
|
|||
|
||||
sealed class DomainDialog : DomainStateDialog {
|
||||
|
||||
data class DialogError(val error: DomainError) : DomainDialog()
|
||||
|
||||
data class SelectTokenDialog(
|
||||
val items: List<Coins.CheckAddressResponse.Token.Contract>,
|
||||
val networkIdConverter: (String) -> String,
|
||||
|
|
|
|||
18
domain/src/main/java/com/tangem/domain/DomainWrapped.kt
Normal file
18
domain/src/main/java/com/tangem/domain/DomainWrapped.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* Provides a temporary copies of the app module classes, data structures, etc.
|
||||
*/
|
||||
//TODO: refactoring: : after refactoring they should be unwrapped and moved
|
||||
// to appropriate parts of module
|
||||
sealed interface DomainWrapped {
|
||||
|
||||
data class TokenWithBlockchain(
|
||||
val token: Token,
|
||||
val blockchain: Blockchain
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.domain.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
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 -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalaunche"
|
||||
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 -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.domain.features.addCustomToken
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.form.BaseFieldDataConverter
|
||||
import com.tangem.domain.common.form.FieldDataConverter
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
|
||||
/**
|
||||
|
|
@ -14,47 +14,44 @@ enum class CompleteDataType {
|
|||
|
||||
sealed class CompleteData() {
|
||||
|
||||
companion object {
|
||||
fun createDataConverter(completeDataType: CompleteDataType): FieldDataConverter<out CompleteData> =
|
||||
when (completeDataType) {
|
||||
CompleteDataType.Blockchain -> CustomBlockchain.Converter()
|
||||
CompleteDataType.Token -> CustomToken.Converter()
|
||||
}
|
||||
}
|
||||
|
||||
class CustomBlockchain(
|
||||
val selectedNetwork: Blockchain,
|
||||
val network: Blockchain,
|
||||
val derivationPath: String?
|
||||
) : CompleteData() {
|
||||
|
||||
class Converter : BaseFieldDataConverter<CustomBlockchain>() {
|
||||
override fun getConvertedData(): CustomBlockchain = CustomBlockchain(
|
||||
collectedData[CustomTokenFieldId.Network] as Blockchain,
|
||||
collectedData[CustomTokenFieldId.DerivationPath] as? String,
|
||||
)
|
||||
override fun getConvertedData(): CustomBlockchain {
|
||||
val network = collectedData[CustomTokenFieldId.Network] as Blockchain
|
||||
val derivationPath = collectedData[CustomTokenFieldId.DerivationPath] as? String
|
||||
return CustomBlockchain(network, derivationPath)
|
||||
}
|
||||
|
||||
override fun getIdToCollect(): List<FieldId> = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
class CustomToken(
|
||||
val contractAddress: String,
|
||||
val selectedNetwork: Blockchain,
|
||||
val name: String,
|
||||
val tokenSymbol: String,
|
||||
val decimals: Int,
|
||||
val token: Token,
|
||||
val network: Blockchain,
|
||||
val derivationPath: String?,
|
||||
) : CompleteData() {
|
||||
|
||||
class Converter : BaseFieldDataConverter<CustomToken>() {
|
||||
override fun getConvertedData(): CustomToken = CustomToken(
|
||||
collectedData[CustomTokenFieldId.ContractAddress] as String,
|
||||
collectedData[CustomTokenFieldId.Network] as Blockchain,
|
||||
collectedData[CustomTokenFieldId.Name] as String,
|
||||
collectedData[CustomTokenFieldId.Symbol] as String,
|
||||
collectedData[CustomTokenFieldId.Decimals] as Int,
|
||||
collectedData[CustomTokenFieldId.DerivationPath] as? String,
|
||||
)
|
||||
class Converter(val tokenId: String?) : BaseFieldDataConverter<CustomToken>() {
|
||||
|
||||
override fun getConvertedData(): CustomToken {
|
||||
val token = Token(
|
||||
name = collectedData[CustomTokenFieldId.Name] as String,
|
||||
symbol = collectedData[CustomTokenFieldId.Symbol] as String,
|
||||
contractAddress = collectedData[CustomTokenFieldId.ContractAddress] as String,
|
||||
decimals = (collectedData[CustomTokenFieldId.Decimals] as String).toInt(),
|
||||
id = tokenId,
|
||||
)
|
||||
return CustomToken(
|
||||
token,
|
||||
collectedData[CustomTokenFieldId.Network] as Blockchain,
|
||||
collectedData[CustomTokenFieldId.DerivationPath] as? String,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getIdToCollect(): List<FieldId> = CustomTokenFieldId.values().toList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ sealed class AddCustomTokenError : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add cus
|
|||
object NetworkIsNotSelected : AddCustomTokenError()
|
||||
object InvalidDecimalsCount : AddCustomTokenError()
|
||||
object InvalidDerivationPath : AddCustomTokenError()
|
||||
}
|
||||
|
||||
sealed class AddCustomTokenWarning : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add custom token - warning") {
|
||||
object PotentialScamToken : AddCustomTokenWarning()
|
||||
object TokenAlreadyAdded : AddCustomTokenWarning()
|
||||
|
||||
sealed class Network : AddCustomTokenWarning() {
|
||||
object CheckAddressRequestError : Network()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AddCustomTokenWarning : AddCustomTokenError() {
|
||||
object PotentialScamToken : AddCustomTokenWarning()
|
||||
object TokenAlreadyAdded : AddCustomTokenWarning()
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.network.common.AddHeaderInterceptor
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddCustomTokenManager(
|
||||
class TangemTechServiceManager(
|
||||
private val tangemTechService: TangemTechService
|
||||
) {
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.common.form.Field
|
|||
import com.tangem.domain.common.form.FieldId
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
|
||||
import com.tangem.domain.features.addCustomToken.CompleteData
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -14,6 +15,12 @@ import org.rekotlin.Action
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class AddCustomTokenAction : Action {
|
||||
sealed class Init : AddCustomTokenAction() {
|
||||
data class SetAddedCurrencies(val addedCurrencies: AddedCurrencies) : AddCustomTokenAction()
|
||||
|
||||
data class SetOnAddTokenCallback(val callback: (CompleteData) -> Unit) : AddCustomTokenAction()
|
||||
}
|
||||
|
||||
object OnCreate : AddCustomTokenAction() {
|
||||
data class SetDerivationStyle(val derivationStyle: DerivationStyle?) : AddCustomTokenAction()
|
||||
}
|
||||
|
|
@ -27,10 +34,12 @@ sealed class AddCustomTokenAction : Action {
|
|||
data class OnTokenSymbolChanged(val tokenSymbol: Field.Data<String>) : AddCustomTokenAction()
|
||||
data class OnTokenDerivationPathChanged(val blockchainDerivationPath: Field.Data<Blockchain>) : AddCustomTokenAction()
|
||||
data class OnTokenDecimalsChanged(val tokenDecimals: Field.Data<String>) : AddCustomTokenAction()
|
||||
object OnAddCustomTokenClicked : AddCustomTokenAction()
|
||||
|
||||
// form fields
|
||||
data class UpdateForm(val state: AddCustomTokenState) : AddCustomTokenAction()
|
||||
object ClearTokenFields : AddCustomTokenAction()
|
||||
|
||||
data class FillTokenFields(
|
||||
val token: Coins.CheckAddressResponse.Token,
|
||||
val contract: Coins.CheckAddressResponse.Token.Contract,
|
||||
|
|
@ -41,6 +50,8 @@ sealed class AddCustomTokenAction : Action {
|
|||
data class Remove(val id: CustomTokenFieldId) : FieldError()
|
||||
}
|
||||
|
||||
data class SetTokenId(val id: String) : AddCustomTokenAction()
|
||||
|
||||
// warnings
|
||||
sealed class Warning : AddCustomTokenAction() {
|
||||
data class Add(val warnings: Set<AddCustomTokenWarning>) : Warning()
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package com.tangem.domain.features.addCustomToken.redux
|
|||
|
||||
import android.webkit.ValueCallback
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toHexString
|
||||
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.*
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
|
|
@ -18,7 +20,9 @@ import com.tangem.domain.redux.dispatchOnMain
|
|||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import timber.log.Timber
|
||||
|
|
@ -31,9 +35,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
private val hubState: AddCustomTokenState
|
||||
get() = domainStore.state.addCustomTokensState
|
||||
|
||||
override fun getHubState(storeState: DomainState): AddCustomTokenState {
|
||||
return storeState.addCustomTokensState
|
||||
}
|
||||
override fun getHubState(storeState: DomainState): AddCustomTokenState = hubState
|
||||
|
||||
override fun updateStoreState(storeState: DomainState, newHubState: AddCustomTokenState): DomainState {
|
||||
return storeState.copy(addCustomTokensState = newHubState)
|
||||
|
|
@ -45,47 +47,51 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
cancel: ValueCallback<Action>
|
||||
) {
|
||||
if (action !is AddCustomTokenAction) return
|
||||
val card = storeState.globalState.scanResponse?.card
|
||||
?: throw IllegalStateException("ScanResponse must be set before showing the AddCustomToken screen")
|
||||
|
||||
when (action) {
|
||||
is Init.SetAddedCurrencies -> {}
|
||||
is Init.SetOnAddTokenCallback -> {}
|
||||
is OnCreate -> {
|
||||
hubState.addCustomTokenManager.attachAuthKey(card.cardPublicKey.toHexString())
|
||||
dispatchOnMain(OnCreate.SetDerivationStyle(card.derivationStyle))
|
||||
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 = getValidator(ContractAddress, hubState)
|
||||
val validator: TokenContractAddressValidator = hubState.getValidator(ContractAddress)
|
||||
val error = validator.validate(contractAddress)
|
||||
addOrRemoveError(ContractAddress, error)
|
||||
|
||||
if (error != null || contractAddress.isEmpty()) {
|
||||
dispatchOnMain(actionsUnlockTokenFields())
|
||||
dispatchOnMain(unlockTokenFields())
|
||||
return
|
||||
}
|
||||
if (!action.contractAddress.isUserInput) return
|
||||
|
||||
val foundTokens = requestInfoAboutContractAddress(contractAddress, hubState)
|
||||
manageTokenChanges(null, foundTokens)
|
||||
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
|
||||
}
|
||||
is OnTokenNetworkChanged -> {
|
||||
if (!action.blockchainNetwork.isUserInput) return
|
||||
|
||||
val contractAddress = getField<TokenField>(ContractAddress, hubState).data.value
|
||||
val foundTokens = requestInfoAboutContractAddress(contractAddress, hubState)
|
||||
manageTokenChanges(null, foundTokens)
|
||||
val contractAddress = hubState.getField<TokenField>(ContractAddress).data.value
|
||||
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
|
||||
}
|
||||
is OnTokenNameChanged -> {
|
||||
val validator: TokenNameValidator = getValidator(Name, hubState)
|
||||
val validator: TokenNameValidator = hubState.getValidator(Name)
|
||||
addOrRemoveError(Name, validator.validate(action.tokenName.value))
|
||||
}
|
||||
is OnTokenSymbolChanged -> {
|
||||
val validator: TokenSymbolValidator = getValidator(Symbol, hubState)
|
||||
val validator: TokenSymbolValidator = hubState.getValidator(Symbol)
|
||||
addOrRemoveError(Symbol, validator.validate(action.tokenSymbol.value))
|
||||
}
|
||||
is OnTokenDecimalsChanged -> {
|
||||
val validator: TokenDecimalsValidator = getValidator(Decimals, hubState)
|
||||
val validator: TokenDecimalsValidator = hubState.getValidator(Decimals)
|
||||
addOrRemoveError(Decimals, validator.validate(action.tokenDecimals.value))
|
||||
}
|
||||
// is OnTokenDerivationPathChanged -> {
|
||||
|
|
@ -93,9 +99,9 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
// addOrRemoveError(DerivationPath, validator.validate(action.value.value))
|
||||
// }
|
||||
is ClearTokenFields -> {
|
||||
val nameField = getField<TokenField>(Name, hubState)
|
||||
val symbolField = getField<TokenField>(Symbol, hubState)
|
||||
val decimalsField = getField<TokenField>(Decimals, hubState)
|
||||
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)
|
||||
|
|
@ -104,14 +110,14 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
dispatchOnMain(UpdateForm(hubState))
|
||||
}
|
||||
is FillTokenFields -> {
|
||||
val networkField = getField<TokenBlockchainField>(Network, hubState)
|
||||
val nameField = getField<TokenField>(Name, hubState)
|
||||
val symbolField = getField<TokenField>(Symbol, hubState)
|
||||
val decimalsField = getField<TokenField>(Decimals, hubState)
|
||||
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)
|
||||
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)
|
||||
|
|
@ -119,23 +125,46 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
|
||||
dispatchOnMain(UpdateForm(hubState))
|
||||
}
|
||||
is OnAddCustomTokenClicked -> {
|
||||
// if (hubState.allFieldsIsEmpty()) {
|
||||
dispatchOnMain(
|
||||
DomainGlobalAction.ShowDialog(DomainDialog.DialogError(
|
||||
AddCustomTokenError.FieldIsEmpty
|
||||
)))
|
||||
return
|
||||
// }
|
||||
when {
|
||||
!hubState.customTokensFieldsIsEmpty() && !hubState.networkIsEmpty() -> {
|
||||
hubState.getCompleteData(CompleteDataType.Token)
|
||||
}
|
||||
// !hubState.customTokensFieldsIsEmpty() && -> {
|
||||
// }
|
||||
}
|
||||
// if (true) {
|
||||
// dispatchOnMain(NavigationAction.PopBackTo())
|
||||
// hubState.onTokenAddCallback?.invoke()
|
||||
// }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestInfoAboutContractAddress(
|
||||
contractAddress: String,
|
||||
hubState: AddCustomTokenState
|
||||
): List<Coins.CheckAddressResponse.Token> {
|
||||
val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager)
|
||||
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true))))
|
||||
val tokenManager = hubState.addCustomTokenManager
|
||||
val field = getField<TokenBlockchainField>(Network, hubState)
|
||||
val field = hubState.getField<TokenBlockchainField>(Network)
|
||||
val selectedNetworkId: String? = field.data.value.let {
|
||||
if (it == Blockchain.Unknown) null else it
|
||||
}?.toNetworkId()
|
||||
|
||||
// delay(1000)
|
||||
val result = when (val foundTokensResult = tokenManager.checkAddress(contractAddress, selectedNetworkId)) {
|
||||
// simulate loading effect. It would be better if the delay would only run if tokenManager.checkAddress()
|
||||
// got the result faster than 500ms and the delay would only be the difference between them.
|
||||
delay(500)
|
||||
|
||||
val foundTokensResult = tangemTechServiceManager.checkAddress(contractAddress, selectedNetworkId)
|
||||
val result = when (foundTokensResult) {
|
||||
is Result.Success -> foundTokensResult.data
|
||||
is Result.Failure -> {
|
||||
// val warning = AddCustomTokenWarning.Network.CheckAddressRequestError
|
||||
|
|
@ -147,10 +176,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
return result
|
||||
}
|
||||
|
||||
private suspend fun manageTokenChanges(
|
||||
card: Card?,
|
||||
foundTokens: List<Coins.CheckAddressResponse.Token>,
|
||||
) {
|
||||
private suspend fun manageTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
|
||||
val toAddWarnings = mutableSetOf<AddCustomTokenWarning>()
|
||||
val toRemoveWarnings = mutableSetOf<AddCustomTokenWarning>()
|
||||
|
||||
|
|
@ -159,7 +185,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
|
||||
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
|
||||
dispatchOnMain(ClearTokenFields)
|
||||
dispatchOnMain(actionsUnlockTokenFields())
|
||||
dispatchOnMain(unlockTokenFields())
|
||||
}
|
||||
else -> {
|
||||
val token = foundTokens[0]
|
||||
|
|
@ -178,7 +204,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
|
||||
|
||||
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
|
||||
dispatchOnMain(actionsLockTokenFields())
|
||||
dispatchOnMain(lockTokenFields())
|
||||
} else {
|
||||
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
|
||||
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
|
||||
|
|
@ -188,11 +214,11 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
if (tokenContract.active && isStandardDerivation) {
|
||||
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
|
||||
dispatchOnMain(FillTokenFields(token, contract))
|
||||
dispatchOnMain(actionsLockTokenFields())
|
||||
dispatchOnMain(lockTokenFields())
|
||||
} else {
|
||||
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
|
||||
dispatchOnMain(ClearTokenFields)
|
||||
dispatchOnMain(actionsUnlockTokenFields())
|
||||
dispatchOnMain(unlockTokenFields())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +227,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
items = contracts,
|
||||
networkIdConverter = { networkId ->
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
if (blockchain == null || blockchain == Blockchain.Unknown) {
|
||||
throw DomainException.SelectTokeNetworkException(networkId)
|
||||
}
|
||||
hubState.convertBlockchainName(blockchain, "")
|
||||
|
|
@ -210,7 +236,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(actionsLockTokenFields())
|
||||
dispatchOnMain(lockTokenFields())
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -238,7 +264,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
}
|
||||
}
|
||||
|
||||
private fun actionsLockTokenFields(): Action {
|
||||
private fun lockTokenFields(): Action {
|
||||
val state = hubState
|
||||
return Screen.UpdateTokenFields(listOf(
|
||||
Network to state.screenState.network.copy(isEnabled = false),
|
||||
|
|
@ -248,7 +274,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
))
|
||||
}
|
||||
|
||||
private fun actionsUnlockTokenFields(): Action {
|
||||
private fun unlockTokenFields(): Action {
|
||||
val state = hubState
|
||||
return Screen.UpdateTokenFields(listOf(
|
||||
Network to state.screenState.network.copy(isEnabled = true),
|
||||
|
|
@ -258,46 +284,54 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
))
|
||||
}
|
||||
|
||||
private inline fun <reified T> getField(id: FieldId, state: AddCustomTokenState): T {
|
||||
return state.form.getField(id) as T
|
||||
}
|
||||
|
||||
private inline fun <reified T> getValidator(id: FieldId, state: AddCustomTokenState): T {
|
||||
return state.getValidator(id) as T
|
||||
}
|
||||
|
||||
override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState {
|
||||
return when (action) {
|
||||
is Init.SetAddedCurrencies -> {
|
||||
state.copy(addedCurrencies = action.addedCurrencies)
|
||||
}
|
||||
is Init.SetOnAddTokenCallback -> {
|
||||
state.copy(onTokenAddCallback = action.callback)
|
||||
}
|
||||
is OnCreate -> {
|
||||
val card = requireNotNull(globalState.scanResponse?.card)
|
||||
val tangemTechServiceManager = TangemTechServiceManager(TangemTechService())
|
||||
tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString())
|
||||
state.copy(
|
||||
derivationStyle = card.derivationStyle,
|
||||
tangemTechServiceManager = tangemTechServiceManager
|
||||
)
|
||||
}
|
||||
is OnDestroy -> state.reset()
|
||||
is UpdateForm -> {
|
||||
updateFormState(action.state)
|
||||
}
|
||||
is OnTokenContractAddressChanged -> {
|
||||
val field: TokenField = getField(ContractAddress, state)
|
||||
val field: TokenField = state.getField(ContractAddress)
|
||||
field.data = action.contractAddress
|
||||
updateFormState(state)
|
||||
}
|
||||
is OnTokenNetworkChanged -> {
|
||||
val field: TokenBlockchainField = getField(Network, state)
|
||||
val field: TokenBlockchainField = state.getField(Network)
|
||||
field.data = action.blockchainNetwork
|
||||
updateFormState(state)
|
||||
}
|
||||
is OnTokenNameChanged -> {
|
||||
val field: TokenField = getField(Name, state)
|
||||
val field: TokenField = state.getField(Name)
|
||||
field.data = action.tokenName
|
||||
updateFormState(state)
|
||||
}
|
||||
is OnTokenSymbolChanged -> {
|
||||
val field: TokenField = getField(Symbol, state)
|
||||
val field: TokenField = state.getField(Symbol)
|
||||
field.data = action.tokenSymbol
|
||||
updateFormState(state)
|
||||
}
|
||||
is OnTokenDecimalsChanged -> {
|
||||
val field: TokenField = getField(Decimals, state)
|
||||
val field: TokenField = state.getField(Decimals)
|
||||
field.data = action.tokenDecimals
|
||||
updateFormState(state)
|
||||
}
|
||||
is OnTokenDerivationPathChanged -> {
|
||||
val field: TokenDerivationPathField = getField(DerivationPath, state)
|
||||
val field: TokenDerivationPathField = state.getField(DerivationPath)
|
||||
field.data = action.blockchainDerivationPath
|
||||
updateFormState(state)
|
||||
}
|
||||
|
|
@ -309,6 +343,9 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
val newMap = state.formErrors.toMutableMap().apply { remove(action.id) }
|
||||
state.copy(formErrors = newMap)
|
||||
}
|
||||
is SetTokenId -> {
|
||||
state.copy(tokenId = action.id)
|
||||
}
|
||||
is Warning.Add -> {
|
||||
val newList = state.warnings.toMutableSet().apply { addAll(action.warnings) }
|
||||
state.copy(warnings = newList.toSet())
|
||||
|
|
@ -391,62 +428,11 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
private fun updateFormState(state: AddCustomTokenState): AddCustomTokenState {
|
||||
return state.copy(form = Form(state.form.fieldList))
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: refactoring: replace by Blockchain.Companion.fromNetworkId
|
||||
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 -> null
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalanche"
|
||||
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 -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
@Throws
|
||||
private fun throwUnAppropriateInitialization(objName: String) {
|
||||
throw DomainException.UnAppropriateInitializationException(
|
||||
"AddCustomTokenHub", "$objName must be not NULL"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,35 +5,39 @@ import com.tangem.blockchain.common.DerivationStyle
|
|||
import com.tangem.domain.common.form.*
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class AddCustomTokenState(
|
||||
val addedCurrencies: AddedCurrencies? = null,
|
||||
val onTokenAddCallback: ((CompleteData) -> Unit)? = null,
|
||||
val derivationStyle: DerivationStyle? = null,
|
||||
val form: Form = Form(createFormFields()),
|
||||
val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<*>> = createFormValidators(),
|
||||
val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<out Any>> = createFormValidators(),
|
||||
val formErrors: Map<CustomTokenFieldId, AddCustomTokenError> = emptyMap(),
|
||||
val tokenId: String? = null,
|
||||
val warnings: Set<AddCustomTokenWarning> = emptySet(),
|
||||
val screenState: ScreenState = createInitialScreenState(),
|
||||
val addCustomTokenManager: AddCustomTokenManager = AddCustomTokenManager(TangemTechService()),
|
||||
val derivationStyle: DerivationStyle? = null
|
||||
val tangemTechServiceManager: TangemTechServiceManager? = null
|
||||
) : StateType {
|
||||
|
||||
val completeDataType: CompleteDataType
|
||||
get() = calculateDataType()
|
||||
inline fun <reified T> getField(id: FieldId): T = form.getField(id) as T
|
||||
|
||||
inline fun <reified T> getValidator(id: FieldId): T = formValidators[id] as T
|
||||
|
||||
fun getError(id: FieldId): AddCustomTokenError? = formErrors[id]
|
||||
|
||||
fun hasError(id: FieldId): Boolean = formErrors[id] != null
|
||||
|
||||
fun getCompleteData(type: CompleteDataType): CompleteData = when (type) {
|
||||
CompleteDataType.Token -> getToken()
|
||||
CompleteDataType.Blockchain -> getBlockchain()
|
||||
}
|
||||
|
||||
inline fun <reified T> visitDataConverter(converter: FieldDataConverter<T>): T {
|
||||
form.visitDataConverter(converter)
|
||||
return converter.getConvertedData()
|
||||
}
|
||||
|
||||
fun getValidator(id: FieldId): CustomTokenValidator<*> = formValidators[id]!!
|
||||
|
||||
fun hasError(id: FieldId): Boolean = formErrors[id] != null
|
||||
|
||||
fun getError(id: FieldId): AddCustomTokenError? {
|
||||
return formErrors[id]
|
||||
}
|
||||
|
||||
fun convertBlockchainName(blockchain: Blockchain, unknown: String): String = when (blockchain) {
|
||||
Blockchain.Unknown -> unknown
|
||||
else -> blockchain.fullName
|
||||
|
|
@ -43,18 +47,49 @@ data class AddCustomTokenState(
|
|||
return blockchain.derivationPath(derivationStyle)?.rawPath ?: unknown
|
||||
}
|
||||
|
||||
private fun calculateDataType(): CompleteDataType {
|
||||
fun reset(): AddCustomTokenState {
|
||||
return this.copy(
|
||||
addedCurrencies = null,
|
||||
onTokenAddCallback = null,
|
||||
derivationStyle = null,
|
||||
form = Form(createFormFields()),
|
||||
formErrors = emptyMap(),
|
||||
tokenId = null,
|
||||
warnings = emptySet(),
|
||||
screenState = createInitialScreenState(),
|
||||
tangemTechServiceManager = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun networkIsEmpty(): Boolean {
|
||||
val network = getField<TokenBlockchainField>(Network)
|
||||
return network.data.value != Blockchain.Unknown
|
||||
}
|
||||
|
||||
fun customTokensFieldsIsEmpty(): Boolean {
|
||||
val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals)
|
||||
val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) }
|
||||
|
||||
val isEmptyValidator = StringIsEmptyValidator()
|
||||
fieldsToCheck.map { data -> data.toString() }.forEach {
|
||||
// if one of the fields has error -> then it
|
||||
val error = isEmptyValidator.validate(it)
|
||||
if (error != null) return CompleteDataType.Token
|
||||
val validator = StringIsEmptyValidator()
|
||||
// val errors = mutableMapOf<>()
|
||||
fieldsToCheck.forEach { field ->
|
||||
val error = validator.validate(field.data.value?.toString())
|
||||
if (error != null) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return CompleteDataType.Blockchain
|
||||
fun allFieldsIsEmpty(): Boolean = networkIsEmpty() && customTokensFieldsIsEmpty()
|
||||
|
||||
private fun getToken(): CompleteData.CustomToken {
|
||||
return CompleteData.CustomToken.Converter(tokenId)
|
||||
.apply { visitDataConverter(this) }
|
||||
.getConvertedData()
|
||||
}
|
||||
|
||||
private fun getBlockchain(): CompleteData.CustomBlockchain {
|
||||
return CompleteData.CustomBlockchain.Converter()
|
||||
.apply { visitDataConverter(this) }
|
||||
.getConvertedData()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
@ -69,7 +104,7 @@ data class AddCustomTokenState(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createFormValidators(): Map<CustomTokenFieldId, CustomTokenValidator<*>> {
|
||||
private fun createFormValidators(): Map<CustomTokenFieldId, CustomTokenValidator<out Any>> {
|
||||
return mapOf(
|
||||
ContractAddress to TokenContractAddressValidator(),
|
||||
Network to TokenNetworkValidator(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.domain.features.addCustomToken.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.DomainWrapped
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -24,4 +27,9 @@ sealed class ViewStates {
|
|||
data class AddButton(
|
||||
val isEnabled: Boolean = true
|
||||
) : ViewStates()
|
||||
}
|
||||
}
|
||||
|
||||
data class AddedCurrencies(
|
||||
val addedTokens: List<DomainWrapped.TokenWithBlockchain>,
|
||||
val addedBlockchains: List<Blockchain>
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ import android.webkit.ValueCallback
|
|||
import com.tangem.domain.common.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.redux.global.DomainGlobalState
|
||||
import kotlinx.coroutines.*
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
|
@ -32,14 +33,20 @@ internal interface HubReducer<StoreState> {
|
|||
* All action went from the middleware must be dispatched through ReStoreHub.dispatchOnMain(Actions) to prevent
|
||||
* concurrent modification in the Store
|
||||
* Only the changed hub State will change its state in the DomainState
|
||||
* Do not implement other states like as DomainGlobalState. Because it can dilute the responsibility of
|
||||
* states.
|
||||
* @param name - name of the Hub
|
||||
* @param dispatcher - main coroutine dispatcher for actions
|
||||
* @property globalState - state witch produce accessibility to global variables
|
||||
*/
|
||||
internal abstract class BaseStoreHub<State>(
|
||||
private val name: String,
|
||||
private val dispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
|
||||
) : ReStoreHub<DomainState, State> {
|
||||
|
||||
val globalState: DomainGlobalState
|
||||
get() = domainStore.state.globalState
|
||||
|
||||
val hubScope = CoroutineScope(
|
||||
Job() + dispatcher + CoroutineName(name) + FeatureCoroutineExceptionHandler.create(name)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue