Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-04 21:00:14 +08:00
parent 40209bc1c3
commit 2e797e8027
24 changed files with 1041 additions and 735 deletions

View file

@ -17,6 +17,7 @@
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />

View file

@ -16,6 +16,7 @@ object FoundTokenConverter : Converter<CoinsResponse.Coin, FoundToken> {
id = value.id,
name = value.name,
symbol = value.symbol,
isActive = value.active,
network = value.networks.firstOrNull()?.let { network ->
FoundToken.Network(
id = network.networkId,

View file

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

View file

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

View file

@ -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<SelectorItem.Title>,
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<SelectorItem.TitleWithSubtitle>,
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
*

View file

@ -0,0 +1,4 @@
package com.tangem.tap.features.customtoken.impl.presentation.models
/** Custom token type */
enum class CustomTokenType { TOKEN, BLOCKCHAIN }

View file

@ -9,4 +9,7 @@ internal interface CustomTokenRouter {
/** Return to last screen */
fun popBackStack()
/** Open wallet (main) screen */
fun openWalletScreen()
}

View file

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

View file

@ -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<TextReference>
val warnings: Set<AddCustomTokenWarning>
/** 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<TextReference> = this.warnings,
warnings: Set<AddCustomTokenWarning> = 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<TextReference>,
override val warnings: Set<AddCustomTokenWarning>,
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<TextReference>,
override val warnings: Set<AddCustomTokenWarning>,
override val floatingButton: AddCustomTokenFloatingButton,
val testBlock: AddCustomTokenTestBlock,
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,

View file

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

View file

@ -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<AddCustomTokenWarning> {
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 = {}),
)
}
}

View file

@ -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)
}
}
}
@Preview(showSystemUi = true)
@Composable
private fun Preview_AddCustomTokenScreen(
@PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
) {
TangemTheme {
AddCustomTokenScreen(stateHolder)
}
}
private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider<AddCustomTokenStateHolder>(
collection = listOf(
AddCustomTokenPreviewData.createContent(),
AddCustomTokenPreviewData.createTestContent(),
),
)

View file

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

View file

@ -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 = {}))
}
}
private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider<AddCustomTokenFloatingButton>(
listOf(
AddCustomTokenFloatingButton(isEnabled = true, onClick = {}),
AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
),
)

View file

@ -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)
}
}
}
private class AddCustomTokenFormProvider : CollectionPreviewParameterProvider<AddCustomTokenForm>(
collection = listOf(
AddCustomTokenPreviewData.createDefaultForm(),
AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null),
AddCustomTokenPreviewData.createDefaultForm().let { form ->
form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true))
},
),
)

View file

@ -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<AddCustomTokenWarning>) {
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())
}
}

View file

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

View file

@ -174,12 +174,14 @@ fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
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,
)

View file

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

View file

@ -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() {

View file

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

View file

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

View file

@ -251,11 +251,9 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("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<AddCustomTokenState>("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<AddCustomTokenState>("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<AddCustomTokenState>("AddCustomT
CustomTokenType.Token -> isTokenPersistIntoAppSavedTokensList()
}
private fun isTokenPersistIntoAppSavedTokensList(
tokenId: String? = hubState.foundToken?.id,
tokenContractAddress: String = ContractAddress.getFieldValue(),
tokenNetworkId: String = Network.getFieldValue<Blockchain>().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<String>()
val tokenNetworkId = Network.getFieldValue<Blockchain>().toNetworkId()
val selectedDerivation = DerivationPath.getFieldValue<Blockchain>()
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<AddCustomTokenState>("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<Blockchain>()
val selectedDerivation = DerivationPath.getFieldValue<Blockchain>()
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {
@ -411,7 +401,8 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (isSameBlockchain && isSameDerivationPath) return true
}
is DomainWrapped.Currency.Token -> {}
is DomainWrapped.Currency.Token -> Unit
}
}
return false