diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 5294908fc5..6edc1d9d21 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt
index ebd6da9985..977869feb2 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt
@@ -16,6 +16,7 @@ object FoundTokenConverter : Converter {
id = value.id,
name = value.name,
symbol = value.symbol,
+ isActive = value.active,
network = value.networks.firstOrNull()?.let { network ->
FoundToken.Network(
id = network.networkId,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
index 25c3cb5836..6ca52f5eb5 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
@@ -3,14 +3,21 @@ package com.tangem.tap.features.customtoken.impl.domain.models
/**
* Found token model
*
- * @property id id
- * @property name name
- * @property symbol symbol
- * @property network network
+ * @property id id
+ * @property name name
+ * @property symbol symbol
+ * @property isActive flag that determines status of token
+ * @property network network
*
[REDACTED_AUTHOR]
*/
-data class FoundToken(val id: String, val name: String, val symbol: String, val network: Network) {
+data class FoundToken(
+ val id: String,
+ val name: String,
+ val symbol: String,
+ val isActive: Boolean,
+ val network: Network,
+) {
/**
* Found token network
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
index ee63fc97e0..f4478fc4fc 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.transition.TransitionInflater
@@ -24,6 +25,8 @@ import dagger.hilt.android.AndroidEntryPoint
internal class AddCustomTokenFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
+
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.fade)
exitTransition = inflateTransition(R.transition.fade)
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
index 90dcb8a583..a43c5a84c4 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
@@ -1,8 +1,6 @@
package com.tangem.tap.features.customtoken.impl.presentation.models
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.wallet.R
@@ -94,106 +92,94 @@ internal sealed interface AddCustomTokenInputField {
/** Label */
val label: TextReference
- /** Input availability */
- val isEnabled: Boolean
-
- /** Flag that determine if current value has error */
- val isError: Boolean
-
/** Placeholder (hint) */
val placeholder: TextReference
- /** Flag that determine the processing of current value */
- val isLoading: Boolean
-
/**
* Input field model to enter the contract address
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isError flag that determine if current value has error
- * @property isLoading flag that determine the processing of current value
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isLoading flag that determine the processing of current value
+ * @property isError flag that determine if current value has error
+ * @property error error description
*/
data class ContactAddress(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isError: Boolean,
- override val isLoading: Boolean,
- ) : AddCustomTokenInputField {
- override val isEnabled = true
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_contract_address_input_title)
- override val placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000")
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isLoading: Boolean,
+ val isError: Boolean,
+ val error: TextReference? = null,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token name
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class TokenName(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_name_input_title)
- override val placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder)
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token symbol
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class TokenSymbol(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_token_symbol_input_title)
- override val placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder)
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token decimals
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class Decimals(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_decimals_input_title)
- override val placeholder = TextReference.Str(value = "8")
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
}
/** Base selector field model of add custom token screen */
internal sealed interface AddCustomTokenSelectorField {
- /** Selection availability */
- val isEnabled: Boolean
-
- /** Label string resource id */
+ /** Label */
val label: TextReference
/** Selected menu item */
@@ -208,35 +194,34 @@ internal sealed interface AddCustomTokenSelectorField {
/**
* Network selector model
*
+ * @property label label
* @property selectedItem selected menu item
* @property items menu items
* @property onMenuItemClick lambda be invoked when menu item is been selected
*/
data class Network(
+ override val label: TextReference,
override val selectedItem: SelectorItem.Title,
override val items: List,
override val onMenuItemClick: (Int) -> Unit,
- ) : AddCustomTokenSelectorField {
- override val isEnabled = true
- override val label = TextReference.Res(R.string.custom_token_network_input_title)
- }
+ ) : AddCustomTokenSelectorField
/**
* Derivation path selector model
*
- * @property isEnabled selection availability
+ * @property label label
* @property selectedItem selected menu item
* @property items menu items
* @property onMenuItemClick lambda be invoked when menu item is been selected
+ * @property isEnabled selection availability
*/
data class DerivationPath(
- override val isEnabled: Boolean,
+ override val label: TextReference,
override val selectedItem: SelectorItem.TitleWithSubtitle,
override val items: List,
override val onMenuItemClick: (Int) -> Unit,
- ) : AddCustomTokenSelectorField {
- override val label = TextReference.Res(R.string.custom_token_derivation_path_input_title)
- }
+ val isEnabled: Boolean,
+ ) : AddCustomTokenSelectorField
/** Base menu item model */
sealed interface SelectorItem {
@@ -259,17 +244,40 @@ internal sealed interface AddCustomTokenSelectorField {
* Menu item with title ans subtitle
*
* @property title title text
- * @property subtitle subtitle text
* @property blockchain blockchain
+ * @property subtitle subtitle text
*/
data class TitleWithSubtitle(
override val title: TextReference,
- val subtitle: TextReference,
override val blockchain: Blockchain,
+ val subtitle: TextReference,
) : SelectorItem
}
}
+/**
+ * Warning model of add custom token screen
+ *
+ * @property description warning description
+ */
+internal sealed class AddCustomTokenWarning(val description: TextReference) {
+
+ /** Potential scam warning */
+ object PotentialScamToken : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.custom_token_validation_error_not_found),
+ )
+
+ /** Token already added warning */
+ object TokenAlreadyAdded : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.custom_token_validation_error_already_added),
+ )
+
+ /** Unsupported Solana token warning */
+ object UnsupportedSolanaToken : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message),
+ )
+}
+
/**
* Floating button of add custom token screen
*
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt
new file mode 100644
index 0000000000..b36fe58ce3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt
@@ -0,0 +1,4 @@
+package com.tangem.tap.features.customtoken.impl.presentation.models
+
+/** Custom token type */
+enum class CustomTokenType { TOKEN, BLOCKCHAIN }
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
index 933bda4bc7..bcd32893fe 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
@@ -9,4 +9,7 @@ internal interface CustomTokenRouter {
/** Return to last screen */
fun popBackStack()
+
+ /** Open wallet (main) screen */
+ fun openWalletScreen()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
index 9460879f92..25eb163e9b 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers
+import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.store
@@ -9,4 +10,8 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
}
+
+ override fun openWalletScreen() {
+ store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
index 17b0f92c9c..32eba291ef 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
@@ -4,8 +4,8 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
/**
* State holder of add custom token screen
@@ -24,7 +24,7 @@ internal sealed interface AddCustomTokenStateHolder {
val form: AddCustomTokenForm
/** Warnings */
- val warnings: List
+ val warnings: Set
/** Floating button model */
val floatingButton: AddCustomTokenFloatingButton
@@ -42,7 +42,7 @@ internal sealed interface AddCustomTokenStateHolder {
onBackButtonClick: () -> Unit = this.onBackButtonClick,
toolbar: AddCustomTokensToolbar = this.toolbar,
form: AddCustomTokenForm = this.form,
- warnings: List = this.warnings,
+ warnings: Set = this.warnings,
floatingButton: AddCustomTokenFloatingButton = this.floatingButton,
): AddCustomTokenStateHolder {
return when (this) {
@@ -64,7 +64,7 @@ internal sealed interface AddCustomTokenStateHolder {
override val onBackButtonClick: () -> Unit,
override val toolbar: AddCustomTokensToolbar,
override val form: AddCustomTokenForm,
- override val warnings: List,
+ override val warnings: Set,
override val floatingButton: AddCustomTokenFloatingButton,
) : AddCustomTokenStateHolder
@@ -83,7 +83,7 @@ internal sealed interface AddCustomTokenStateHolder {
override val onBackButtonClick: () -> Unit,
override val toolbar: AddCustomTokensToolbar,
override val form: AddCustomTokenForm,
- override val warnings: List,
+ override val warnings: Set,
override val floatingButton: AddCustomTokenFloatingButton,
val testBlock: AddCustomTokenTestBlock,
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
index 7b746c3ec4..e57d82cc15 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
@@ -9,22 +9,21 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.key
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.blockchain.common.Blockchain
+import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
-import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarning
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
-import com.tangem.wallet.R
+import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
/**
* Add custom token content
@@ -37,6 +36,7 @@ import com.tangem.wallet.R
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
BackHandler(onBack = state.onBackButtonClick)
+ var floatingButtonHeight by remember { mutableStateOf(0.dp) }
Scaffold(
topBar = {
AddCustomTokenToolbar(
@@ -44,78 +44,36 @@ internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
onBackButtonClick = state.toolbar.onBackButtonClick,
)
},
- floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
+ floatingActionButton = {
+ val density = LocalDensity.current
+ val verticalPadding = TangemTheme.dimens.spacing32
+ AddCustomTokenFloatingButton(
+ model = state.floatingButton,
+ modifier = Modifier.onSizeChanged {
+ floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
+ },
+ )
+ },
floatingActionButtonPosition = FabPosition.Center,
) {
Column(
modifier = Modifier
+ .verticalScroll(rememberScrollState())
.padding(paddingValues = it)
- .fillMaxSize()
- .verticalScroll(rememberScrollState()),
+ .padding(bottom = floatingButtonHeight)
+ .fillMaxSize(),
) {
AddCustomTokenForm(model = state.form)
- state.warnings.forEach { description ->
- key(description) {
- AddCustomTokenWarning(description)
- }
- }
+ AddCustomTokenWarnings(warnings = state.warnings)
}
}
}
-@Preview(showSystemUi = true)
+@Preview
@Composable
private fun Preview_AddCustomTokenContent() {
TangemTheme {
- AddCustomTokenContent(
- state = AddCustomTokenStateHolder.Content(
- onBackButtonClick = {},
- toolbar = AddCustomTokensToolbar(
- title = TextReference.Res(R.string.add_custom_token_title),
- onBackButtonClick = {},
- ),
- form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str("Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- warnings = listOf(),
- floatingButton = AddCustomTokenFloatingButton(
- isEnabled = false,
- onClick = {},
- ),
- ),
- )
+ AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
new file mode 100644
index 0000000000..e463c90ae9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
@@ -0,0 +1,124 @@
+package com.tangem.tap.features.customtoken.impl.presentation.ui
+
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
+import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
+import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+internal object AddCustomTokenPreviewData {
+
+ fun createWarnings(): Set {
+ return setOf(
+ AddCustomTokenWarning.PotentialScamToken,
+ AddCustomTokenWarning.TokenAlreadyAdded,
+ AddCustomTokenWarning.UnsupportedSolanaToken,
+ )
+ }
+
+ fun createDefaultForm(): AddCustomTokenForm {
+ return AddCustomTokenForm(
+ contractAddressInputField = AddCustomTokenInputField.ContactAddress(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_contract_address_input_title),
+ placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
+ isLoading = false,
+ isError = false,
+ error = null,
+ ),
+ networkSelectorField = AddCustomTokenSelectorField.Network(
+ label = TextReference.Res(R.string.custom_token_network_input_title),
+ selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
+ title = TextReference.Res(R.string.custom_token_network_input_not_selected),
+ blockchain = Blockchain.Unknown,
+ ),
+ items = emptyList(),
+ onMenuItemClick = {},
+ ),
+ tokenNameInputField = AddCustomTokenInputField.TokenName(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_name_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
+ isEnabled = false,
+ ),
+ tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
+ isEnabled = false,
+ ),
+ decimalsInputField = AddCustomTokenInputField.Decimals(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_decimals_input_title),
+ placeholder = TextReference.Str(value = "8"),
+ isEnabled = false,
+ ),
+ derivationPathSelectorField = AddCustomTokenSelectorField.DerivationPath(
+ label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
+ selectedItem = AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ title = TextReference.Res(R.string.custom_token_derivation_path_default),
+ subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
+ blockchain = Blockchain.Unknown,
+ ),
+ items = emptyList(),
+ onMenuItemClick = {},
+ isEnabled = true,
+ ),
+ )
+ }
+
+ fun createTestContent(): AddCustomTokenStateHolder.TestContent {
+ return AddCustomTokenStateHolder.TestContent(
+ onBackButtonClick = {},
+ toolbar = AddCustomTokensToolbar(
+ title = TextReference.Res(R.string.add_custom_token_title),
+ onBackButtonClick = {},
+ ),
+ form = createDefaultForm(),
+ warnings = createWarnings(),
+ floatingButton = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ testBlock = AddCustomTokenTestBlock(
+ chooseTokenButtonText = "Choose token",
+ clearButtonText = "Clear address",
+ resetButtonText = "Reset",
+ onClearAddressButtonClick = {},
+ onResetButtonClick = {},
+ ),
+ bottomSheet = AddCustomTokenChooseTokenBottomSheet(categoriesBlocks = emptyList(), onTestTokenClick = {}),
+ )
+ }
+
+ fun createContent(): AddCustomTokenStateHolder.Content {
+ return AddCustomTokenStateHolder.Content(
+ onBackButtonClick = {},
+ toolbar = AddCustomTokensToolbar(
+ title = TextReference.Res(R.string.add_custom_token_title),
+ onBackButtonClick = {},
+ ),
+ form = createDefaultForm(),
+ warnings = createWarnings(),
+ floatingButton = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
index 82f284fb98..71d079e899 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui
import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
/**
@@ -10,11 +14,27 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
*
[REDACTED_AUTHOR]
*/
-@Suppress("UnusedPrivateMember")
@Composable
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) {
when (stateHolder) {
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder)
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder)
}
-}
\ No newline at end of file
+}
+
+@Preview(showSystemUi = true)
+@Composable
+private fun Preview_AddCustomTokenScreen(
+ @PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
+) {
+ TangemTheme {
+ AddCustomTokenScreen(stateHolder)
+ }
+}
+
+private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ AddCustomTokenPreviewData.createContent(),
+ AddCustomTokenPreviewData.createTestContent(),
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
index dce924a655..c23fa86835 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
@@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -19,33 +20,33 @@ import androidx.compose.material.FabPosition
import androidx.compose.material.Text
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
-import com.tangem.wallet.R
+import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -74,6 +75,7 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
},
)
+ var floatingButtonHeight by remember { mutableStateOf(0.dp) }
BottomSheetScaffold(
sheetContent = {
SheetContent(
@@ -95,7 +97,16 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
},
)
},
- floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
+ floatingActionButton = {
+ val density = LocalDensity.current
+ val verticalPadding = TangemTheme.dimens.spacing32
+ AddCustomTokenFloatingButton(
+ model = state.floatingButton,
+ modifier = Modifier.onSizeChanged {
+ floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
+ },
+ )
+ },
floatingActionButtonPosition = FabPosition.Center,
sheetBackgroundColor = TangemTheme.colors.background.secondary,
sheetPeekHeight = TangemTheme.dimens.size0,
@@ -104,15 +115,19 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
- .padding(it),
+ .padding(paddingValues = it)
+ .padding(bottom = floatingButtonHeight)
+ .fillMaxSize(),
) {
TestBlock(
- model = state.testBlock,
+ state.testBlock,
coroutineScope,
bottomSheetScaffoldState,
)
AddCustomTokenForm(model = state.form)
+
+ AddCustomTokenWarnings(warnings = state.warnings)
}
}
}
@@ -243,69 +258,10 @@ private fun TestBlock(
}
}
-@Preview(showSystemUi = true)
+@Preview
@Composable
private fun Preview_AddCustomTokenTestContent() {
TangemTheme {
- AddCustomTokenTestContent(
- state = AddCustomTokenStateHolder.TestContent(
- onBackButtonClick = {},
- toolbar = AddCustomTokensToolbar(
- title = TextReference.Res(R.string.add_custom_token_title),
- onBackButtonClick = {},
- ),
- form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(value = "Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- warnings = listOf(),
- floatingButton = AddCustomTokenFloatingButton(
- isEnabled = false,
- onClick = {},
- ),
- testBlock = AddCustomTokenTestBlock(
- chooseTokenButtonText = "Choose token",
- clearButtonText = "Clear address",
- resetButtonText = "Reset",
- onClearAddressButtonClick = {},
- onResetButtonClick = {},
- ),
- bottomSheet = AddCustomTokenChooseTokenBottomSheet(
- categoriesBlocks = listOf(),
- onTestTokenClick = {},
- ),
- ),
- )
+ AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
index 082bfe4939..618a46fd70 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
@@ -8,6 +8,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
@@ -16,14 +18,15 @@ import com.tangem.wallet.R
/**
* Add custom token floating button. Attached above the keyboard.
*
- * @param model button model
+ * @param model button model
+ * @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
+internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
PrimaryButtonIconLeft(
- modifier = Modifier
+ modifier = modifier
.imePadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
@@ -36,16 +39,17 @@ internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
@Preview
@Composable
-private fun Preview_AddCustomTokenFloatingButton_Enabled() {
+private fun Preview_AddCustomTokenFloatingButton(
+ @PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton,
+) {
TangemTheme {
- AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = true, onClick = {}))
+ AddCustomTokenFloatingButton(model)
}
}
-@Preview
-@Composable
-private fun Preview_AddCustomTokenFloatingButton_Disabled() {
- TangemTheme {
- AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}))
- }
-}
\ No newline at end of file
+private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider(
+ listOf(
+ AddCustomTokenFloatingButton(isEnabled = true, onClick = {}),
+ AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
index 44904ac319..6b2e38d732 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -14,6 +18,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ExposedDropdownMenuBox
import androidx.compose.material.ExposedDropdownMenuDefaults
import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -26,13 +31,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.blockchain.common.Blockchain
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.TangemTextFieldsDefault
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
/**
@@ -68,7 +74,37 @@ internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
@Composable
private fun InputField(model: AddCustomTokenInputField) {
+ Column {
+ val isError = (model as? AddCustomTokenInputField.ContactAddress)?.isError ?: false
+
+ TextField(model, isError)
+
+ (model as? AddCustomTokenInputField.ContactAddress)?.error?.resolveReference()?.let {
+ AnimatedVisibility(
+ visible = isError,
+ enter = fadeIn() + slideInVertically(),
+ exit = slideOutVertically() + fadeOut(),
+ ) {
+ Text(
+ text = it,
+ color = MaterialTheme.colors.error,
+ style = TangemTheme.typography.body2,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun TextField(model: AddCustomTokenInputField, isError: Boolean) {
Box {
+ val isEnabled = when (model) {
+ is AddCustomTokenInputField.ContactAddress -> true
+ is AddCustomTokenInputField.Decimals -> model.isEnabled
+ is AddCustomTokenInputField.TokenName -> model.isEnabled
+ is AddCustomTokenInputField.TokenSymbol -> model.isEnabled
+ }
+
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = model.value,
@@ -79,8 +115,8 @@ private fun InputField(model: AddCustomTokenInputField) {
text = model.label.resolveReference(),
style = TangemTheme.typography.caption,
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
- enabled = model.isEnabled,
- error = model.isError,
+ enabled = isEnabled,
+ error = isError,
interactionSource = remember { MutableInteractionSource() },
).value,
)
@@ -90,20 +126,20 @@ private fun InputField(model: AddCustomTokenInputField) {
text = model.placeholder.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTextFieldsDefault.defaultTextFieldColors
- .placeholderColor(enabled = model.isEnabled)
+ .placeholderColor(enabled = isEnabled)
.value,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
singleLine = true,
- enabled = model.isEnabled,
- isError = model.isError,
+ enabled = isEnabled,
+ isError = isError,
colors = TangemTextFieldsDefault.defaultTextFieldColors,
)
AnimatedVisibility(
- visible = model.isLoading,
+ visible = (model as? AddCustomTokenInputField.ContactAddress)?.isLoading ?: false,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
@@ -124,6 +160,7 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
expanded = isExpanded,
onExpandedChange = { isExpanded = !isExpanded },
) {
+ val isEnabled = (model as? AddCustomTokenSelectorField.DerivationPath)?.isEnabled ?: true
OutlinedTextField(
value = when (val item = model.selectedItem) {
is AddCustomTokenSelectorField.SelectorItem.Title -> item.title
@@ -132,14 +169,14 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
modifier = Modifier.fillMaxWidth(),
onValueChange = {},
readOnly = true,
- enabled = model.isEnabled,
+ enabled = isEnabled,
label = { Text(text = model.label.resolveReference()) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
colors = TangemTextFieldsDefault.defaultTextFieldColors,
)
ExposedDropdownMenu(
- expanded = isExpanded && model.isEnabled,
+ expanded = isExpanded && isEnabled,
onDismissRequest = { isExpanded = false },
) {
FocusRequester
@@ -173,44 +210,18 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
@Preview
@Composable
-private fun Preview_AddCustomTokenForm() {
+private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) {
TangemTheme {
- AddCustomTokenForm(
- AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(value = "Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- )
+ AddCustomTokenForm(model)
}
-}
\ No newline at end of file
+}
+
+private class AddCustomTokenFormProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ AddCustomTokenPreviewData.createDefaultForm(),
+ AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null),
+ AddCustomTokenPreviewData.createDefaultForm().let { form ->
+ form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true))
+ },
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
similarity index 53%
rename from app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt
rename to app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
index 3e1b99796e..7627e3c6af 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
@@ -2,38 +2,56 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
+import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.wallet.R
/**
- * Add custom token warning component
- * FIXME("Incorrect typography. Replace with typography from design system")
+ * Add custom token warnings
*
- * @param description warning description
- * @param modifier modifier
+ * @param warnings warnings descriptions set
*
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifier = Modifier) {
+internal fun AddCustomTokenWarnings(warnings: Set) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ ) {
+ warnings.forEach { warning ->
+ key(warning) { AddCustomTokenWarning(warning) }
+ }
+ }
+}
+
+@Composable
+private fun AddCustomTokenWarning(warning: AddCustomTokenWarning) {
Card(
- modifier = modifier,
+ modifier = Modifier.fillMaxSize(),
shape = TangemTheme.shapes.roundedCornersSmall2,
backgroundColor = TangemColorPalette.Tangerine,
contentColor = TangemColorPalette.White,
elevation = TangemTheme.dimens.elevation4,
) {
+ // FIXME("Incorrect typography. Replace with typography from design system")
Column(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
@@ -44,10 +62,18 @@ internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifie
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
)
Text(
- text = description.resolveReference(),
+ text = warning.description.resolveReference(),
fontSize = 13.sp,
lineHeight = 18.sp,
)
}
}
+}
+
+@Preview
+@Composable
+private fun Preview_AddCustomTokenWarnings() {
+ TangemTheme {
+ AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings())
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
new file mode 100644
index 0000000000..e9641c119f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
+
+import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.tap.common.analytics.events.ManageTokens
+import com.tangem.tap.features.wallet.models.Currency
+
+/** Analytics sender for tokens list screen */
+class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
+
+ fun sendWhenScreenOpened() {
+ analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
+ }
+
+ fun sendWhenAddTokenButtonClicked(currency: Currency, address: String) {
+ analyticsEventHandler.send(
+ when (currency) {
+ is Currency.Blockchain -> {
+ ManageTokens.CustomToken.TokenWasAdded.Blockchain(
+ derivationPath = currency.derivationPath,
+ blockchain = currency.blockchain,
+ )
+ }
+
+ is Currency.Token -> {
+ ManageTokens.CustomToken.TokenWasAdded.Token(
+ symbol = currency.currencySymbol,
+ derivationPath = currency.derivationPath,
+ blockchain = currency.blockchain,
+ contractAddress = address,
+ )
+ }
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
index 22b13b1216..5e2020d8ef 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
@@ -1,8 +1,11 @@
package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
@@ -14,12 +17,14 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.TapWorkarounds.derivationStyle
-import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.isSupportedInApp
import com.tangem.domain.common.extensions.supportedBlockchains
-import com.tangem.tap.common.analytics.events.ManageTokens
+import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
+import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TokensCategoryBlock
@@ -27,8 +32,11 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField.SelectorItem
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
+import com.tangem.tap.features.customtoken.impl.presentation.models.CustomTokenType
import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
@@ -36,6 +44,7 @@ import com.tangem.tap.features.customtoken.impl.presentation.validators.Contract
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
+import com.tangem.tap.store
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import com.tangem.wallet.BuildConfig
@@ -49,34 +58,37 @@ import javax.inject.Inject
/**
* ViewModel for add custom token screen
*
- * @param featureRouter feature router
- * @property featureInteractor feature interactor
- * @property dispatchers coroutine dispatchers provider
- * @property reduxStateHolder redux state holder
- * @property analyticsEventHandler analytics event handler
+ * @param analyticsEventHandler analytics event handler
+ * @param featureRouter feature router
+ * @property featureInteractor feature interactor
+ * @property dispatchers coroutine dispatchers provider
+ * @property reduxStateHolder redux state holder
*
[REDACTED_AUTHOR]
*/
+@Suppress("LargeClass")
@HiltViewModel
internal class AddCustomTokenViewModel @Inject constructor(
+ analyticsEventHandler: AnalyticsEventHandler,
featureRouter: CustomTokenRouter,
private val featureInteractor: CustomTokenInteractor,
private val dispatchers: AppCoroutineDispatcherProvider,
private val reduxStateHolder: AppStateHolder,
- private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), DefaultLifecycleObserver {
+ private val analyticsSender = AddCustomTokenAnalyticsSender(analyticsEventHandler)
private val actionsHandler = ActionsHandler(featureRouter)
private val testActionsHandler = TestActionsHandler()
+ private val formStateBuilder = FormStateBuilder()
/** Screen state */
var uiState by mutableStateOf(getInitialUiState())
private set
- private var foundTokenId: String? = null
+ private var foundToken: FoundToken? = null
override fun onCreate(owner: LifecycleOwner) {
- analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
+ analyticsSender.sendWhenScreenOpened()
}
private fun getInitialUiState(): AddCustomTokenStateHolder {
@@ -84,8 +96,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
AddCustomTokenStateHolder.TestContent(
onBackButtonClick = actionsHandler::onBackButtonClick,
toolbar = createToolbar(),
- form = createForm(),
- warnings = listOf(),
+ form = formStateBuilder.createForm(),
+ warnings = emptySet(),
floatingButton = createFloatingButton(),
testBlock = AddCustomTokenTestBlock(
chooseTokenButtonText = "Choose token",
@@ -106,8 +118,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
AddCustomTokenStateHolder.Content(
onBackButtonClick = actionsHandler::onBackButtonClick,
toolbar = createToolbar(),
- form = createForm(),
- warnings = listOf(),
+ form = formStateBuilder.createForm(),
+ warnings = emptySet(),
floatingButton = createFloatingButton(),
)
}
@@ -120,155 +132,32 @@ internal class AddCustomTokenViewModel @Inject constructor(
)
}
- private fun createForm(): AddCustomTokenForm {
- return AddCustomTokenForm(
- contractAddressInputField = createContractAddressInputField(),
- networkSelectorField = createNetworkSelectorField(),
- tokenNameInputField = createTokenNameInputField(),
- tokenSymbolInputField = createTokenSymbolInputField(),
- decimalsInputField = createDecimalsInputField(),
- derivationPathSelectorField = createDerivationPathsSelectorField(),
- )
+ private fun createFloatingButton(): AddCustomTokenFloatingButton {
+ return AddCustomTokenFloatingButton(isEnabled = false, onClick = actionsHandler::onAddCustomTokenClick)
}
- private fun createContractAddressInputField(): AddCustomTokenInputField.ContactAddress {
- return AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = actionsHandler::onContactAddressValueChange,
- isError = false,
- isLoading = false,
- )
- }
+ private inner class FormStateBuilder {
- private fun createNetworkSelectorField(): AddCustomTokenSelectorField.Network {
- val selectorItems = getNetworkSelectorItems()
- return AddCustomTokenSelectorField.Network(
- selectedItem = requireNotNull(selectorItems.firstOrNull()),
- items = selectorItems,
- onMenuItemClick = {
- actionsHandler.onNetworkSelectorItemClick(
- selectedItem = requireNotNull(selectorItems.getOrNull(it)),
- )
- },
- )
- }
-
- private fun getNetworkSelectorItems(): List {
- val card = reduxStateHolder.scanResponse?.card
- val evmBlockchains = Blockchain.values().filter { card?.isTestCard == it.isTestnet() && it.isEvm() }
-
- val additionalBlockchains = listOf(
- Blockchain.Binance,
- Blockchain.BinanceTestnet,
- Blockchain.Solana,
- Blockchain.SolanaTestnet,
- Blockchain.Tron,
- Blockchain.TronTestnet,
- )
-
- return (evmBlockchains + additionalBlockchains)
- .filter { card?.supportedBlockchains()?.contains(it) == true }
- .map(::createNetworkSelectorItem)
- .toMutableList()
- .apply {
- add(index = 0, element = createNetworkSelectorItem(blockchain = Blockchain.Unknown))
- }
- }
-
- private fun createNetworkSelectorItem(blockchain: Blockchain): AddCustomTokenSelectorField.SelectorItem.Title {
- return when (blockchain) {
- Blockchain.Unknown -> {
- AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Res(R.string.custom_token_network_input_not_selected),
- blockchain = Blockchain.Unknown,
- )
- }
-
- else -> {
- AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(blockchain.fullName),
- blockchain = blockchain,
- )
- }
- }
- }
-
- private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName {
- return AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = actionsHandler::onTokenNameValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createTokenSymbolInputField(): AddCustomTokenInputField.TokenSymbol {
- return AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = actionsHandler::onTokenSymbolValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createDecimalsInputField(): AddCustomTokenInputField.Decimals {
- return AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = actionsHandler::onDecimalsValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createDerivationPathsSelectorField(): AddCustomTokenSelectorField.DerivationPath? {
- if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null
-
- val selectorItems = getDerivationPathsSelectorItems()
- return AddCustomTokenSelectorField.DerivationPath(
- isEnabled = true,
- selectedItem = requireNotNull(selectorItems.firstOrNull()),
- items = selectorItems,
- onMenuItemClick = {
- val field = requireNotNull(uiState.form.derivationPathSelectorField)
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = field.copy(
- selectedItem = requireNotNull(selectorItems.getOrNull(it)),
- ),
- ),
- )
- },
- )
- }
-
- private fun getDerivationPathsSelectorItems(): List {
- val evmBlockchains = Blockchain.values().filter {
- reduxStateHolder.scanResponse?.card?.isTestCard == it.isTestnet() && it.isEvm() && it.isSupportedInApp()
+ fun createForm(): AddCustomTokenForm {
+ return AddCustomTokenForm(
+ contractAddressInputField = createContractAddressInputField(),
+ networkSelectorField = createNetworkSelectorField(),
+ tokenNameInputField = createTokenNameInputField(),
+ tokenSymbolInputField = createTokenSymbolInputField(),
+ decimalsInputField = createDecimalsInputField(),
+ derivationPathSelectorField = createDerivationPathsSelectorField(),
+ )
}
- return evmBlockchains
- .sortedBy(Blockchain::fullName)
- .map(::createDerivationPathSelectorItem)
- .toMutableList()
- .apply {
- add(index = 0, element = createDerivationPathSelectorItem(Blockchain.Unknown))
- }
- }
-
- private fun createDerivationPathSelectorItem(
- blockchain: Blockchain,
- ): AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle {
- return when (blockchain) {
- Blockchain.Unknown -> {
- AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ fun createDerivationPathSelectorItem(blockchain: Blockchain): SelectorItem.TitleWithSubtitle {
+ return if (blockchain == Blockchain.Unknown) {
+ SelectorItem.TitleWithSubtitle(
title = TextReference.Res(R.string.custom_token_derivation_path_default),
subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
blockchain = Blockchain.Unknown,
)
- }
-
- else -> {
- AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ } else {
+ SelectorItem.TitleWithSubtitle(
title = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath?.let(TextReference::Str)
?: TextReference.Res(R.string.custom_token_derivation_path_default),
subtitle = TextReference.Str(blockchain.fullName),
@@ -276,10 +165,396 @@ internal class AddCustomTokenViewModel @Inject constructor(
)
}
}
+
+ fun createNetworkSelectorItem(blockchain: Blockchain): SelectorItem.Title {
+ return if (blockchain == Blockchain.Unknown) {
+ SelectorItem.Title(
+ title = TextReference.Res(R.string.custom_token_network_input_not_selected),
+ blockchain = Blockchain.Unknown,
+ )
+ } else {
+ SelectorItem.Title(
+ title = TextReference.Str(blockchain.fullName),
+ blockchain = blockchain,
+ )
+ }
+ }
+
+ private fun createContractAddressInputField(): AddCustomTokenInputField.ContactAddress {
+ return AddCustomTokenInputField.ContactAddress(
+ value = "",
+ onValueChange = actionsHandler::onContactAddressValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_contract_address_input_title),
+ placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
+ isLoading = false,
+ isError = false,
+ error = null,
+ )
+ }
+
+ private fun createNetworkSelectorField(): AddCustomTokenSelectorField.Network {
+ val selectorItems = getNetworkSelectorItems()
+ return AddCustomTokenSelectorField.Network(
+ label = TextReference.Res(R.string.custom_token_network_input_title),
+ selectedItem = requireNotNull(selectorItems.firstOrNull()),
+ items = selectorItems,
+ onMenuItemClick = actionsHandler::onNetworkSelectorItemClick,
+ )
+ }
+
+ private fun getNetworkSelectorItems(): List {
+ val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown)
+ return listOf(defaultNetwork) + Blockchain.values()
+ .filter { blockchain ->
+ (blockchain.isEvm() || blockchain.canHandleTokens()) &&
+ reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true
+ }
+ .map(::createNetworkSelectorItem)
+ }
+
+ private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName {
+ return AddCustomTokenInputField.TokenName(
+ value = "",
+ onValueChange = actionsHandler::onTokenNameValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_name_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
+ isEnabled = false,
+ )
+ }
+
+ private fun createTokenSymbolInputField(): AddCustomTokenInputField.TokenSymbol {
+ return AddCustomTokenInputField.TokenSymbol(
+ value = "",
+ onValueChange = actionsHandler::onTokenSymbolValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
+ isEnabled = false,
+ )
+ }
+
+ private fun createDecimalsInputField(): AddCustomTokenInputField.Decimals {
+ return AddCustomTokenInputField.Decimals(
+ value = "",
+ onValueChange = actionsHandler::onDecimalsValueChange,
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_decimals_input_title),
+ placeholder = TextReference.Str(value = "8"),
+ isEnabled = false,
+ )
+ }
+
+ private fun createDerivationPathsSelectorField(): AddCustomTokenSelectorField.DerivationPath? {
+ if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null
+
+ val selectorItems = getDerivationPathsSelectorItems()
+ return AddCustomTokenSelectorField.DerivationPath(
+ label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
+ selectedItem = requireNotNull(selectorItems.firstOrNull()),
+ items = selectorItems,
+ onMenuItemClick = actionsHandler::onDerivationPathSelectorItemClick,
+ isEnabled = true,
+ )
+ }
+
+ private fun getDerivationPathsSelectorItems(): List {
+ val defaultDerivationPath = createDerivationPathSelectorItem(Blockchain.Unknown)
+ return listOf(defaultDerivationPath) + Blockchain.values()
+ .filter { blockchain -> blockchain.isEvm() && blockchain.isSupportedInApp() }
+ .sortedBy(Blockchain::fullName)
+ .map(::createDerivationPathSelectorItem)
+ }
}
- private fun createFloatingButton(): AddCustomTokenFloatingButton {
- return AddCustomTokenFloatingButton(isEnabled = false, onClick = actionsHandler::onAddCustomTokenClick)
+ private fun updateForm(address: String, selectedNetwork: Blockchain) {
+ viewModelScope.launch(dispatchers.main) {
+ runCatching(dispatchers.io) {
+ featureInteractor.findToken(address = address, blockchain = selectedNetwork)
+ }
+ .onSuccess { token ->
+ foundToken = token
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isLoading = false,
+ ),
+ networkSelectorField = uiState.form.networkSelectorField.copy(
+ selectedItem = formStateBuilder.createNetworkSelectorItem(
+ blockchain = Blockchain.fromNetworkId(token.network.id)
+ ?: Blockchain.Unknown,
+ ),
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(
+ value = token.name,
+ isEnabled = false,
+ ),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ value = token.symbol,
+ isEnabled = false,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(
+ value = token.network.decimalCount,
+ isEnabled = false,
+ ),
+ ),
+ )
+ }
+ .onFailure {
+ foundToken = null
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isLoading = false,
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(isEnabled = true),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(isEnabled = true),
+ decimalsInputField = uiState.form.decimalsInputField.copy(isEnabled = true),
+ ),
+ )
+ Timber.e(it)
+ }
+
+ updateDerivationPathSelector()
+ updateWarnings()
+ updateFloatingButton()
+ }
+ }
+
+ private fun updateDerivationPathSelector() {
+ val derivationPathSelectorField = uiState.form.derivationPathSelectorField ?: return
+ val selectedValue = derivationPathSelectorField.selectedItem.blockchain
+ val isSupported = selectedValue.isEvm() || !isDerivationPathSelected()
+
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
+ isEnabled = if (derivationPathSelectorField.isEnabled != isSupported) {
+ isSupported
+ } else {
+ derivationPathSelectorField.isEnabled
+ },
+ selectedItem = if (isDerivationPathSelected() && !isSupported) {
+ formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown)
+ } else {
+ derivationPathSelectorField.selectedItem
+ },
+ ),
+ ),
+ )
+ }
+
+ private fun isDerivationPathSelected(): Boolean {
+ return uiState.form.derivationPathSelectorField?.selectedItem?.blockchain != Blockchain.Unknown
+ }
+
+ private fun updateWarnings() {
+ uiState = uiState.copySealed(
+ warnings = buildSet {
+ when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> {
+ addAll(getTokenWarningSet())
+ }
+
+ CustomTokenType.BLOCKCHAIN -> {
+ if (isCustomTokenAlreadyAdded()) add(AddCustomTokenWarning.TokenAlreadyAdded)
+ if (isDerivationPathSelected()) add(AddCustomTokenWarning.PotentialScamToken)
+ }
+ }
+ },
+ )
+ }
+
+ private fun getCustomTokenType(): CustomTokenType {
+ return if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) {
+ CustomTokenType.TOKEN
+ } else {
+ CustomTokenType.BLOCKCHAIN
+ }
+ }
+
+ private fun isAnyTokenFieldsFilled(): Boolean {
+ return with(uiState.form) {
+ contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() ||
+ tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty()
+ }
+ }
+
+ private fun isAllTokenFieldsFilled(): Boolean {
+ return with(uiState.form) {
+ contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() &&
+ tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty()
+ }
+ }
+
+ private fun getTokenWarningSet(): Set {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+
+ val isContractAddressFieldEmpty = ContactAddressValidator.validate(
+ address = uiState.form.contractAddressInputField.value,
+ blockchain = networkSelectorValue,
+ ).let {
+ it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FieldIsEmpty
+ }
+
+ val isSupportedToken = if (!isNetworkSelected()) {
+ true
+ } else {
+ reduxStateHolder.scanResponse?.card?.canHandleToken(networkSelectorValue) ?: false
+ }
+
+ return buildSet {
+ if (!isSupportedToken && !isContractAddressFieldEmpty) add(AddCustomTokenWarning.UnsupportedSolanaToken)
+ if (isCustomTokenAlreadyAdded()) add(AddCustomTokenWarning.TokenAlreadyAdded)
+ if (foundToken == null && isAnyTokenFieldsFilled() || foundToken?.isActive == false) {
+ add(AddCustomTokenWarning.PotentialScamToken)
+ }
+ }
+ }
+
+ private fun isNetworkSelected(): Boolean {
+ return uiState.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown
+ }
+
+ private fun updateFloatingButton() {
+ if (isCustomTokenAlreadyAdded()) {
+ uiState = uiState.copySealed(
+ warnings = uiState.warnings + AddCustomTokenWarning.TokenAlreadyAdded,
+ floatingButton = uiState.floatingButton.copy(isEnabled = false),
+ )
+ return
+ }
+
+ val state = when {
+ isAllTokenFieldsFilled() && isNetworkSelected() -> {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ val error = ContactAddressValidator.validate(
+ address = uiState.form.contractAddressInputField.value,
+ blockchain = networkSelectorValue,
+ )
+ val isSupportedToken = reduxStateHolder.scanResponse?.card
+ ?.canHandleToken(networkSelectorValue)
+ ?: false
+
+ uiState.copySealed(
+ floatingButton = uiState.floatingButton.copy(
+ isEnabled = error is ContractAddressValidatorResult.Success && isSupportedToken,
+ ),
+ )
+ }
+
+ isAnyTokenFieldsFilled() -> {
+ uiState.copySealed(floatingButton = uiState.floatingButton.copy(isEnabled = false))
+ }
+
+ else -> {
+ uiState.copySealed(
+ floatingButton = uiState.floatingButton.copy(
+ isEnabled = if (isNetworkSelected()) !isBlockchainAlreadyAdded() else false,
+ ),
+ )
+ }
+ }
+
+ uiState = state.copySealed(
+ warnings = uiState.warnings - AddCustomTokenWarning.TokenAlreadyAdded,
+ )
+ }
+
+ private fun isCustomTokenAlreadyAdded(): Boolean {
+ return when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> isTokenAlreadyAdded()
+ CustomTokenType.BLOCKCHAIN -> isBlockchainAlreadyAdded()
+ }
+ }
+
+ private fun isTokenAlreadyAdded(): Boolean {
+ return store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .filterIsInstance()
+ .any { wrappedCurrency ->
+ val contractAddress = uiState.form.contractAddressInputField.value
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ val sameId = foundToken?.id == wrappedCurrency.token.id
+ val sameAddress = contractAddress == wrappedCurrency.token.contractAddress
+ val sameBlockchain =
+ Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain
+ val isSameDerivationPath = getDerivationPath()?.rawPath == wrappedCurrency.derivationPath
+ sameId && sameAddress && sameBlockchain && isSameDerivationPath
+ }
+ }
+
+ private fun isBlockchainAlreadyAdded(): Boolean {
+ return store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .filterIsInstance()
+ .any {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ networkSelectorValue == it.blockchain && getDerivationPath()?.rawPath == it.derivationPath
+ }
+ }
+
+ private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
+ when {
+ isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {
+ val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isError = true,
+ error = TextReference.Res(
+ id = R.string.custom_token_creation_error_invalid_contract_address,
+ ),
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ ),
+ )
+ }
+
+ !isNetworkSelected() || type == AddCustomTokenError.FieldIsEmpty -> {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(isError = false),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(value = "", isEnabled = false),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ value = "",
+ isEnabled = false,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(value = "", isEnabled = false),
+ ),
+ )
+ }
+
+ else -> Unit
+ }
+ }
+
+ private fun getDerivationPath(): DerivationPath? {
+ val isNotDerivationPathSelected = !isDerivationPathSelected()
+ val network = if (isNotDerivationPathSelected) {
+ uiState.form.networkSelectorField.selectedItem.blockchain
+ } else {
+ uiState.form.derivationPathSelectorField?.selectedItem?.blockchain
+ }
+
+ return network?.derivationPath(
+ style = if (isNotDerivationPathSelected) {
+ reduxStateHolder.scanResponse?.card?.derivationStyle
+ } else {
+ DerivationStyle.LEGACY
+ },
+ )
}
private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) {
@@ -288,85 +563,47 @@ internal class AddCustomTokenViewModel @Inject constructor(
featureRouter.popBackStack()
}
- fun onAddCustomTokenClick() {
- if (uiState.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown) {
- val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
-
- val currency = if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) {
- Currency.Token(
- token = Token(
- name = uiState.form.tokenNameInputField.value,
- symbol = uiState.form.tokenSymbolInputField.value,
- contractAddress = uiState.form.contractAddressInputField.value,
- decimals = uiState.form.decimalsInputField.value.toInt(),
- id = foundTokenId,
- ),
- blockchain = selectedNetwork,
- derivationPath = getDerivationPath(
- mainNetwork = selectedNetwork,
- derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
- derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
- )?.rawPath,
- )
- } else {
- Currency.Blockchain(
- blockchain = selectedNetwork,
- derivationPath = getDerivationPath(
- mainNetwork = selectedNetwork,
- derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
- derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
- )?.rawPath,
- )
- }
-
- sendOnAddTokenButtonClick(currency = currency, address = uiState.form.contractAddressInputField.value)
-
- viewModelScope.launch(dispatchers.io) {
- featureInteractor.saveToken(
- currency = currency,
- address = uiState.form.contractAddressInputField.value,
- )
- }
- }
- }
-
fun onContactAddressValueChange(enteredValue: String) {
- with(uiState.form) {
- val selectedNetwork = networkSelectorField.selectedItem.blockchain
- val isValid = ContactAddressValidator.validate(
- address = enteredValue,
- blockchain = selectedNetwork,
- )
-
- when (isValid) {
- is ContractAddressValidatorResult.Success -> {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- contractAddressInputField = contractAddressInputField.copy(
- isError = false,
- isLoading = true,
- ),
- ),
- )
- updateForm(address = enteredValue, selectedNetwork = selectedNetwork)
- }
-
- is ContractAddressValidatorResult.Error -> {
- handleContractAddressErrorValidation(type = isValid.type)
- }
- }
-
- updateDerivationPathSelector()
-
- // TODO("[REDACTED_TASK_KEY] Update warnings")
- // TODO("[REDACTED_TASK_KEY] Update floating button")
- }
- }
-
- fun onNetworkSelectorItemClick(selectedItem: AddCustomTokenSelectorField.SelectorItem.Title) {
uiState = uiState.copySealed(
form = uiState.form.copy(
- networkSelectorField = uiState.form.networkSelectorField.copy(selectedItem = selectedItem),
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(value = enteredValue),
+ ),
+ )
+
+ val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
+ val validatorResult = ContactAddressValidator.validate(
+ address = enteredValue,
+ blockchain = selectedNetwork,
+ )
+
+ when (validatorResult) {
+ is ContractAddressValidatorResult.Success -> {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isError = false,
+ isLoading = true,
+ ),
+ ),
+ )
+ updateForm(address = enteredValue, selectedNetwork = selectedNetwork)
+ }
+
+ is ContractAddressValidatorResult.Error -> {
+ handleContractAddressErrorValidation(type = validatorResult.type)
+ updateDerivationPathSelector()
+ updateWarnings()
+ updateFloatingButton()
+ }
+ }
+ }
+
+ fun onNetworkSelectorItemClick(index: Int) {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ networkSelectorField = uiState.form.networkSelectorField.copy(
+ selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)),
+ ),
),
)
onContactAddressValueChange(uiState.form.contractAddressInputField.value)
@@ -378,7 +615,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
tokenNameInputField = uiState.form.tokenNameInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
fun onTokenSymbolValueChange(enteredValue: String) {
@@ -387,7 +624,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
fun onDecimalsValueChange(enteredValue: String) {
@@ -396,156 +633,53 @@ internal class AddCustomTokenViewModel @Inject constructor(
decimalsInputField = uiState.form.decimalsInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
- private fun getDerivationPath(
- mainNetwork: Blockchain,
- derivationNetwork: Blockchain?,
- derivationStyle: DerivationStyle?,
- ): DerivationPath? {
- val network = if (derivationNetwork == Blockchain.Unknown) mainNetwork else derivationNetwork
-
- return network?.derivationPath(
- style = if (derivationNetwork == Blockchain.Unknown) derivationStyle else DerivationStyle.LEGACY,
+ fun onDerivationPathSelectorItemClick(index: Int) {
+ val field = requireNotNull(uiState.form.derivationPathSelectorField)
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ derivationPathSelectorField = field.copy(
+ selectedItem = requireNotNull(field.items.getOrNull(index)),
+ ),
+ ),
)
}
- private fun sendOnAddTokenButtonClick(currency: Currency, address: String) {
- when (currency) {
- is Currency.Blockchain -> {
- analyticsEventHandler.send(
- ManageTokens.CustomToken.TokenWasAdded.Blockchain(
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- ),
- )
- }
+ fun onAddCustomTokenClick() {
+ if (!isNetworkSelected()) return
- is Currency.Token -> {
- analyticsEventHandler.send(
- ManageTokens.CustomToken.TokenWasAdded.Token(
- symbol = currency.currencySymbol,
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- contractAddress = address,
- ),
- )
- }
- }
- }
-
- private fun isAnyTokenFieldsFilled(): Boolean {
- return with(uiState.form) {
- contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() ||
- tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty()
- }
- }
-
- private fun isAllTokenFieldsFilled(): Boolean {
- return with(uiState.form) {
- contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() &&
- tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty()
- }
- }
-
- private fun updateForm(address: String, selectedNetwork: Blockchain) {
viewModelScope.launch(dispatchers.main) {
- runCatching(dispatchers.io) {
- featureInteractor.findToken(address = address, blockchain = selectedNetwork)
- }
- .onSuccess { token ->
- with(uiState.form) {
- uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(isLoading = false),
- networkSelectorField = networkSelectorField.copy(
- selectedItem = createNetworkSelectorItem(
- blockchain = Blockchain.fromNetworkId(token.network.id)
- ?: Blockchain.Unknown,
- ),
- ),
- tokenNameInputField = tokenNameInputField.copy(
- value = token.name,
- isEnabled = false,
- ),
- tokenSymbolInputField = tokenSymbolInputField.copy(
- value = token.symbol,
- isEnabled = false,
- ),
- decimalsInputField = decimalsInputField.copy(
- value = token.network.decimalCount,
- isEnabled = false,
- ),
- ),
- )
- }
- }
- .onFailure {
- foundTokenId = null
- Timber.e(it)
- }
- }
- }
-
- private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
- with(uiState.form) {
- val isNetworkSelectorFilled = networkSelectorField.selectedItem.blockchain != Blockchain.Unknown
- val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
-
- when {
- isNetworkSelectorFilled && type == AddCustomTokenError.InvalidContractAddress -> {
- // TODO("[REDACTED_TASK_KEY] Add error")
-
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- tokenNameInputField = tokenNameInputField.copy(isEnabled = isAnotherTokenFieldsFilled),
- tokenSymbolInputField = tokenSymbolInputField.copy(
- isEnabled = isAnotherTokenFieldsFilled,
- ),
- decimalsInputField = decimalsInputField.copy(isEnabled = isAnotherTokenFieldsFilled),
+ val address = uiState.form.contractAddressInputField.value
+ val currency = when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> {
+ Currency.Token(
+ token = Token(
+ name = uiState.form.tokenNameInputField.value,
+ symbol = uiState.form.tokenSymbolInputField.value,
+ contractAddress = address,
+ decimals = requireNotNull(uiState.form.decimalsInputField.value.toIntOrNull()),
+ id = foundToken?.id,
),
+ blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath()?.rawPath,
)
}
- !isNetworkSelectorFilled || type == AddCustomTokenError.FieldIsEmpty -> {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- contractAddressInputField = contractAddressInputField.copy(isError = false),
- tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
- tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
- decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
- ),
+ CustomTokenType.BLOCKCHAIN -> {
+ Currency.Blockchain(
+ blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath()?.rawPath,
)
}
-
- else -> Unit
}
- }
- }
- private fun updateDerivationPathSelector() {
- val selectedValue = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain ?: return
- val isSupported = selectedValue.isEvm() || selectedValue == Blockchain.Unknown
+ analyticsSender.sendWhenAddTokenButtonClicked(currency, address)
- if (selectedValue != Blockchain.Unknown && !isSupported) {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
- selectedItem = createDerivationPathSelectorItem(Blockchain.Unknown),
- ),
- ),
- )
- }
-
- if (uiState.form.derivationPathSelectorField?.isEnabled != isSupported) {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
- isEnabled = isSupported,
- ),
- ),
- )
+ runCatching(dispatchers.io) { featureInteractor.saveToken(currency, address) }
+ .onSuccess { featureRouter.openWalletScreen() }
+ .onFailure(Timber::e)
}
}
}
@@ -553,31 +687,37 @@ internal class AddCustomTokenViewModel @Inject constructor(
private inner class TestActionsHandler {
fun onClearAddressButtonClick() {
- with(uiState.form) {
- uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(value = ""),
- tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
- tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
- decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ value = "",
+ isLoading = false,
+ isError = false,
+ error = null,
),
- )
- }
+ ),
+ )
}
fun onResetButtonClick() {
with(uiState.form) {
uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(value = ""),
+ form = uiState.form.copy(
+ contractAddressInputField = contractAddressInputField.copy(
+ value = "",
+ isLoading = false,
+ isError = false,
+ error = null,
+ ),
networkSelectorField = networkSelectorField.copy(
- selectedItem = requireNotNull(networkSelectorField.items.firstOrNull()),
+ selectedItem = formStateBuilder.createNetworkSelectorItem(blockchain = Blockchain.Unknown),
),
tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
derivationPathSelectorField = derivationPathSelectorField?.copy(
- selectedItem = requireNotNull(derivationPathSelectorField.items.firstOrNull()),
+ isEnabled = true,
+ selectedItem = formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown),
),
),
)
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
index 88ba8be37d..3ac2828354 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
@@ -174,12 +174,14 @@ fun Warnings(warnings: List) {
Column {
warnings.forEachIndexed { index, item ->
val modifier = when (index) {
- 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)
+ 0 -> Modifier.padding(vertical = 0.dp)
+ warnings.lastIndex -> Modifier.padding(top = 8.dp, bottom = 16.dp)
+ else -> Modifier.padding(top = 8.dp, bottom = 0.dp)
}
AddCustomTokenWarning(
- modifier = modifier.fillMaxWidth(),
+ modifier = modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
warning = item,
converter = warningConverter,
)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
index 2a7da5cca4..07a7a3412d 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.impl.di
+import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.tokens.impl.presentation.router.DefaultTokensListRouter
import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter
import dagger.Module
@@ -17,5 +18,7 @@ internal object TokensListRouterModule {
@Provides
@ViewModelScoped
- fun provideTokensListRouter(): TokensListRouter = DefaultTokensListRouter()
+ fun provideTokensListRouter(customTokenFeatureToggles: CustomTokenFeatureToggles): TokensListRouter {
+ return DefaultTokensListRouter(customTokenFeatureToggles)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
index 73e09a433e..cbfe64e505 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
@@ -3,7 +3,9 @@ package com.tangem.tap.features.tokens.impl.presentation.router
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
@@ -15,15 +17,20 @@ import com.tangem.wallet.R
*
[REDACTED_AUTHOR]
*/
-internal class DefaultTokensListRouter : TokensListRouter {
+internal class DefaultTokensListRouter(
+ private val customTokenFeatureToggles: CustomTokenFeatureToggles,
+) : TokensListRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
- store.dispatch(TokensAction.ResetState)
}
override fun openAddCustomTokenScreen() {
- store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
+ if (customTokenFeatureToggles.isRedesignedScreenEnabled) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
+ } else {
+ store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
+ }
}
override fun showAddressCopiedNotification() {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
index be311deb0a..cab038d65e 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
@@ -31,6 +31,7 @@ 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.model.WalletDataModel
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
@@ -316,21 +317,23 @@ class TokensMiddleware {
}
}
- val addedCurrencies = store.state.walletState.walletsStores.map { walletStore ->
- walletStore.walletsData.map { walletData -> walletData.currency }
- }.flatten().map {
- when (it) {
- is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
- it.blockchain,
- it.derivationPath,
- )
- is Currency.Token -> DomainWrapped.Currency.Token(
- it.token,
- it.blockchain,
- it.derivationPath,
- )
+ val addedCurrencies = store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .map { currency ->
+ when (currency) {
+ is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
+ currency.blockchain,
+ currency.derivationPath,
+ )
+
+ is Currency.Token -> DomainWrapped.Currency.Token(
+ currency.token,
+ currency.blockchain,
+ currency.derivationPath,
+ )
+ }
}
- }
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(onAddCustomToken))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
diff --git a/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt b/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
index a94866c343..902244560d 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
@@ -1,13 +1,12 @@
package com.tangem.domain
-import com.tangem.blockchain.common.DerivationStyle
-
/**
[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
+@Deprecated("After refactoring they should be unwrapped and moved to appropriate parts of module")
sealed interface DomainWrapped {
// Mirror reflection ot the com.tangem.tap.features.wallet.redux.Currency
@@ -30,10 +29,5 @@ sealed interface DomainWrapped {
) : Currency {
override val currencySymbol: String = blockchain.currency
}
-
- fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
- if (derivationPath == null || derivationStyle == null) return false
- return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
- }
}
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
index 17f110b952..30085691d3 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
@@ -251,11 +251,9 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
when (state.getCustomTokenType()) {
CustomTokenType.Blockchain -> {
warningsRemove.add(UnsupportedSolanaToken)
- if (alreadyAdded) {
- warningsAdd.add(TokenAlreadyAdded)
- } else {
- warningsRemove.add(TokenAlreadyAdded)
- }
+
+ if (alreadyAdded) warningsAdd.add(TokenAlreadyAdded) else warningsRemove.add(TokenAlreadyAdded)
+
if (state.derivationPathIsSelected()) {
warningsAdd.add(PotentialScamToken)
} else {
@@ -266,12 +264,11 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
if (tokenIsSupported) {
warningsRemove.add(UnsupportedSolanaToken)
} else {
- val error = ContractAddress.validateValue(ContractAddress.getFieldValue())
- when (error) {
- AddCustomTokenError.FieldIsEmpty -> warningsRemove.add(UnsupportedSolanaToken)
- else -> {
- warningsAdd.add(UnsupportedSolanaToken)
- }
+ val validationResult = ContractAddress.validateValue(ContractAddress.getFieldValue())
+ if (validationResult == AddCustomTokenError.FieldIsEmpty) {
+ warningsRemove.add(UnsupportedSolanaToken)
+ } else {
+ warningsAdd.add(UnsupportedSolanaToken)
}
}
@@ -329,12 +326,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
// blockchain
else -> {
if (state.networkIsSelected()) {
- val alreadyAdded = isBlockchainPersistIntoAppSavedTokensList()
- if (alreadyAdded) {
- disableAddButton()
- } else {
- enableAddButton()
- }
+ if (isBlockchainPersistIntoAppSavedTokensList()) disableAddButton() else enableAddButton()
} else {
disableAddButton()
}
@@ -369,18 +361,18 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
CustomTokenType.Token -> isTokenPersistIntoAppSavedTokensList()
}
- private fun isTokenPersistIntoAppSavedTokensList(
- tokenId: String? = hubState.foundToken?.id,
- tokenContractAddress: String = ContractAddress.getFieldValue(),
- tokenNetworkId: String = Network.getFieldValue().toNetworkId(),
- selectedDerivation: Blockchain = DerivationPath.getFieldValue(),
- ): Boolean {
+ private fun isTokenPersistIntoAppSavedTokensList(): Boolean {
val savedCurrencies = hubState.appSavedCurrencies ?: return false
+ val tokenId = hubState.foundToken?.id
+ val tokenContractAddress = ContractAddress.getFieldValue()
+ val tokenNetworkId = Network.getFieldValue().toNetworkId()
+ val selectedDerivation = DerivationPath.getFieldValue()
+
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
- is DomainWrapped.Currency.Blockchain -> {}
+ is DomainWrapped.Currency.Blockchain -> Unit
is DomainWrapped.Currency.Token -> {
val sameId = tokenId == wrappedCurrency.token.id
val sameAddress = tokenContractAddress == wrappedCurrency.token.contractAddress
@@ -396,14 +388,12 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
return false
}
- private fun isBlockchainPersistIntoAppSavedTokensList(
- selectedNetwork: Blockchain = Network.getFieldValue(),
- selectedDerivation: Blockchain = DerivationPath.getFieldValue(),
- ): Boolean {
- val state = hubState
- val savedCurrencies = state.appSavedCurrencies ?: return false
-
+ private fun isBlockchainPersistIntoAppSavedTokensList(): Boolean {
+ val savedCurrencies = hubState.appSavedCurrencies ?: return false
+ val selectedNetwork = Network.getFieldValue()
+ val selectedDerivation = DerivationPath.getFieldValue()
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
+
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {
@@ -411,7 +401,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (isSameBlockchain && isSameDerivationPath) return true
}
- is DomainWrapped.Currency.Token -> {}
+
+ is DomainWrapped.Currency.Token -> Unit
}
}
return false