Updated on 2026-08-14
This commit is contained in:
commit
d4b0b11320
105 changed files with 2579 additions and 1197 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
/** Custom token type */
|
||||
enum class CustomTokenType { TOKEN, BLOCKCHAIN }
|
||||
|
|
@ -9,4 +9,7 @@ internal interface CustomTokenRouter {
|
|||
|
||||
/** Return to last screen */
|
||||
fun popBackStack()
|
||||
|
||||
/** Open wallet (main) screen */
|
||||
fun openWalletScreen()
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
),
|
||||
)
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -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))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -2,39 +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.foundation.shape.RoundedCornerShape
|
||||
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,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius4),
|
||||
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),
|
||||
|
|
@ -45,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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.features.disclaimer
|
||||
|
||||
import com.tangem.tap.persistence.DisclaimerPrefStorage
|
||||
import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.features.disclaimer
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.persistence.DisclaimerPrefStorage
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
|||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.persistence.UsedCardsPrefStorage
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ private fun Icon(name: String, iconUrl: String, modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
// TODO(use CurrencyPlaceholderIcon here?)
|
||||
@Composable
|
||||
private fun PlaceholderIcon(name: String, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
|||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.data.source.preferences.model.DataSourceCurrency
|
||||
import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
|
||||
import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
|
@ -14,7 +16,6 @@ import com.tangem.tap.features.wallet.domain.WalletRepository
|
|||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.persistence.FiatCurrenciesPrefStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
|
|
@ -47,8 +48,10 @@ class AppCurrencyMiddleware(
|
|||
|
||||
scope.launch {
|
||||
runCatching { walletRepository.getCurrencyList() }
|
||||
.onSuccess {
|
||||
val currenciesList = it.currencies
|
||||
.onSuccess { response ->
|
||||
val currenciesList = response.currencies
|
||||
.map { with(it) { DataSourceCurrency(id, code, name, rateBTC, unit, type) } }
|
||||
|
||||
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
store.dispatchDialogShow(
|
||||
|
|
@ -64,7 +67,9 @@ class AppCurrencyMiddleware(
|
|||
|
||||
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
|
||||
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency)))
|
||||
fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency)
|
||||
fiatCurrenciesPrefStorage.saveAppCurrency(
|
||||
with(action.fiatCurrency) { DataSourceFiatCurrency(code, name, symbol) },
|
||||
)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
|
|
@ -77,7 +82,7 @@ class AppCurrencyMiddleware(
|
|||
}
|
||||
}
|
||||
|
||||
private fun List<CurrenciesResponse.Currency>.mapToUiModel(): List<FiatCurrency> {
|
||||
private fun List<DataSourceCurrency>.mapToUiModel(): List<FiatCurrency> {
|
||||
return this.map {
|
||||
FiatCurrency(
|
||||
code = it.code,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class WarningsMiddleware {
|
|||
is WalletAction.Warnings.CheckIfNeeded -> {
|
||||
showCardWarningsIfNeeded(globalState)
|
||||
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
|
||||
if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
|
||||
if (readyToShow) addWarningMessage(warning = WarningMessagesManager.appRatingWarning, autoUpdate = true)
|
||||
}
|
||||
is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline()
|
||||
is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
|
||||
|
|
@ -69,7 +69,7 @@ class WarningsMiddleware {
|
|||
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
|
||||
}
|
||||
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
|
||||
addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
|
||||
addWarningMessage(WarningMessagesManager.appRatingWarning, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,21 +78,21 @@ class WarningsMiddleware {
|
|||
val card = scanResponse.card
|
||||
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.isTestCard) {
|
||||
addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
|
||||
addWarningMessage(WarningMessagesManager.testCardWarning, autoUpdate = true)
|
||||
return@let
|
||||
}
|
||||
|
||||
showWarningLowRemainingSignaturesIfNeeded(card)
|
||||
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
|
||||
addWarningMessage(WarningMessagesManager.devCardWarning())
|
||||
addWarningMessage(WarningMessagesManager.devCardWarning)
|
||||
} else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) {
|
||||
checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) }
|
||||
}
|
||||
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) {
|
||||
addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
|
||||
addWarningMessage(WarningMessagesManager.onlineVerificationFailed)
|
||||
}
|
||||
if (scanResponse.isDemoCard()) {
|
||||
addWarningMessage(WarningMessagesManager.demoCardWarning())
|
||||
addWarningMessage(WarningMessagesManager.demoCardWarning)
|
||||
}
|
||||
setWarningMessages()
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ class WarningsMiddleware {
|
|||
if (scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) }
|
||||
return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) {
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning()
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
|
|
@ -123,7 +123,7 @@ class WarningsMiddleware {
|
|||
val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator
|
||||
return if (validator == null) {
|
||||
if (scanResponse.card.hasSignedHashes()) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning()
|
||||
WarningMessagesManager.alreadySignedHashesWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
|
|
@ -161,9 +161,9 @@ class WarningsMiddleware {
|
|||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning, true)
|
||||
} else if (signedHashes > 0) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning, true)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,14 +105,12 @@ internal class WalletViewModel @Inject constructor(
|
|||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||
.map { it.walletId }
|
||||
.flatMapLatest { selectedUserWalletId ->
|
||||
walletStoresManager.get(selectedUserWalletId)
|
||||
}
|
||||
.flatMapLatest(walletStoresManager::get)
|
||||
.debounce { walletStores ->
|
||||
if (walletStores.isNotEmpty()) WALLET_STORES_DEBOUNCE_TIMEOUT else 0
|
||||
}
|
||||
.onEach { walletStores ->
|
||||
store.dispatch(WalletAction.WalletStoresChanged(walletStores))
|
||||
store.dispatchOnMain(WalletAction.WalletStoresChanged(walletStores))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ object SignedHashesWarningDialog {
|
|||
setPositiveButton(R.string.common_understand) { _, _ ->
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
store.dispatch(
|
||||
GlobalAction.HideWarningMessage(
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning(),
|
||||
),
|
||||
GlobalAction.HideWarningMessage(WarningMessagesManager.signedHashesMultiWalletWarning),
|
||||
)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
|
|
|
|||
|
|
@ -57,4 +57,6 @@ internal sealed interface WalletSelectorAction : Action {
|
|||
) : WalletSelectorAction
|
||||
|
||||
object CloseError : WalletSelectorAction
|
||||
|
||||
object ClearUserWallets : WalletSelectorAction
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.map
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MyWallets
|
||||
|
|
@ -81,6 +81,12 @@ internal class WalletSelectorMiddleware {
|
|||
is WalletSelectorAction.ChangeAppCurrency -> {
|
||||
refreshUserWalletsAmounts()
|
||||
}
|
||||
is WalletSelectorAction.ClearUserWallets -> scope.launch {
|
||||
clearUserWallets()
|
||||
.doOnFailure { e ->
|
||||
Timber.e(e, "Unable to clear user wallets")
|
||||
}
|
||||
}
|
||||
is WalletSelectorAction.AddWallet.Success,
|
||||
is WalletSelectorAction.AddWallet.Error,
|
||||
is WalletSelectorAction.SelectedWalletChanged,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ internal object WalletSelectorReducer {
|
|||
is WalletSelectorAction.SelectWallet,
|
||||
is WalletSelectorAction.RemoveWallets,
|
||||
is WalletSelectorAction.RenameWallet,
|
||||
is WalletSelectorAction.ClearUserWallets,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||
|
|
@ -46,11 +45,8 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<WalletSelectorScreenState> {
|
||||
return viewModel.state.collectAsState()
|
||||
}
|
||||
override fun provideState(): State<WalletSelectorScreenState> = viewModel.state.collectAsState()
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
override fun ScreenContent(state: WalletSelectorScreenState, modifier: Modifier) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
|
|
|||
|
|
@ -200,13 +200,17 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
onDismiss = this::dismissWarningDialog,
|
||||
)
|
||||
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
|
||||
onConfirm = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onConfirm = this::clearUserWallets,
|
||||
onDismiss = this::dismissWarningDialog,
|
||||
)
|
||||
else -> currentDialog
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearUserWallets() {
|
||||
store.dispatch(WalletSelectorAction.ClearUserWallets)
|
||||
}
|
||||
|
||||
private fun dismissWarningDialog() {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ internal sealed interface WelcomeAction : Action {
|
|||
data class HandleIntentIfNeeded(val intent: Intent?) : WelcomeAction
|
||||
|
||||
object CloseError : WelcomeAction
|
||||
|
||||
object ClearUserWallets : WelcomeAction
|
||||
}
|
||||
|
|
@ -3,11 +3,14 @@ package com.tangem.tap.features.welcome.redux
|
|||
import android.content.Intent
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -51,6 +54,9 @@ internal class WelcomeMiddleware {
|
|||
is WelcomeAction.HandleIntentIfNeeded -> {
|
||||
handleInitialIntent(action.intent)
|
||||
}
|
||||
is WelcomeAction.ClearUserWallets -> {
|
||||
clearUserWallets()
|
||||
}
|
||||
is WelcomeAction.ProceedWithBiometrics.Error,
|
||||
is WelcomeAction.ProceedWithCard.Error,
|
||||
is WelcomeAction.ProceedWithBiometrics.Success,
|
||||
|
|
@ -60,22 +66,31 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun proceedWithBiometrics(state: WelcomeState) {
|
||||
scope.launch {
|
||||
userWalletsListManager.unlockIfLockable()
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets with biometrics")
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Biometric))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
private fun clearUserWallets() = scope.launch {
|
||||
userWalletsListManager.clear()
|
||||
.flatMap { tangemSdkManager.clearSavedUserCodes() }
|
||||
.doOnFailure { e ->
|
||||
Timber.e(e, "Unable to clear user wallets")
|
||||
}
|
||||
.doOnResult {
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
}
|
||||
}
|
||||
private fun proceedWithBiometrics(state: WelcomeState) = scope.launch {
|
||||
userWalletsListManager.unlockIfLockable()
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets with biometrics")
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Biometric))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithCard(state: WelcomeState) = scope.launch {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
|
||||
onConfirm = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onConfirm = this::clearUserWallets,
|
||||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
else -> null
|
||||
|
|
@ -88,6 +88,10 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
closeError()
|
||||
}
|
||||
|
||||
private fun clearUserWallets() {
|
||||
store.dispatch(WelcomeAction.ClearUserWallets)
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState.skip { old, new -> old.welcomeState == new.welcomeState }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue