Updated on 2026-08-14
This commit is contained in:
parent
0ebbd12b95
commit
4681ebb29d
19 changed files with 108 additions and 14 deletions
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.features.customtoken.api.featuretoggles
|
||||
|
||||
/**
|
||||
* Add custom token feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CustomTokenFeatureToggles {
|
||||
|
||||
/** Availability of redesigned screen (internal feature) */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.customtoken.impl.featuretoggles.DefaultCustomTokenFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CustomTokenFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesCustomTokenFeatureToggles(featureTogglesManager: FeatureTogglesManager): CustomTokenFeatureToggles {
|
||||
return DefaultCustomTokenFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.features.customtoken.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
|
||||
/**
|
||||
* Default implementation of CustomToken feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultCustomTokenFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : CustomTokenFeatureToggles {
|
||||
|
||||
override val isRedesignedScreenEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* Add custom token screen
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
internal class AddCustomTokenFragment : Fragment()
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.tap.features.customtoken.legacy
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.AddCustomTokenScreen
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment), StoreSubscriber<AddCustomTokenState> {
|
||||
|
||||
private var state: MutableState<AddCustomTokenState> = mutableStateOf(domainStore.state.addCustomTokensState)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(ManageTokens.CustomToken.ScreenOpened())
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
domainStore.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.addCustomTokensState == newState.addCustomTokensState
|
||||
}.select { it.addCustomTokensState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: AddCustomTokenState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
this.state.value = state
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
view.findViewById<Toolbar>(R.id.toolbar)?.setTitle(R.string.add_custom_token_title)
|
||||
|
||||
val closePopupTrigger = initClosingPopupTriggerEvent()
|
||||
view.findViewById<ComposeView>(R.id.view_compose)?.setContent {
|
||||
TangemTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
AddCustomTokenScreen(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initClosingPopupTriggerEvent(): ClosePopupTrigger = ClosePopupTrigger().apply {
|
||||
onCloseComplete = ::handleOnBackPressed
|
||||
addBackPressHandler(
|
||||
object : FragmentOnBackPressedHandler {
|
||||
override fun handleOnBackPressed() = close()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.BottomSheetScaffold
|
||||
import androidx.compose.material.BottomSheetScaffoldState
|
||||
import androidx.compose.material.BottomSheetState
|
||||
import androidx.compose.material.BottomSheetValue
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconLeft
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.features.addCustomToken.redux.ScreenState
|
||||
import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.AddCustomTokenWarning
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.test.TestCase
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.test.TestCasesList
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>, closePopupTrigger: ClosePopupTrigger) {
|
||||
val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) }
|
||||
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed),
|
||||
)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } }
|
||||
|
||||
BottomSheetScaffold(
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
sheetContent = {
|
||||
Surface(color = colorResource(id = R.color.lightGray5)) {
|
||||
selectedTestCase.value.content(toggleBottomSheet)
|
||||
}
|
||||
},
|
||||
sheetPeekHeight = 0.dp,
|
||||
) {
|
||||
Column {
|
||||
TestCasesList(
|
||||
onItemClick = {
|
||||
selectedTestCase.value = it
|
||||
toggleBottomSheet()
|
||||
},
|
||||
)
|
||||
ScreenContent(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
|
||||
ComposeDialogManager()
|
||||
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
|
||||
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
private suspend fun BottomSheetScaffoldState.toggle() {
|
||||
if (bottomSheetState.isCollapsed) {
|
||||
bottomSheetState.expand()
|
||||
} else {
|
||||
bottomSheetState.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(state: MutableState<AddCustomTokenState>, closePopupTrigger: ClosePopupTrigger) {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
backgroundColor = colorResource(id = R.color.backgroundLightGray),
|
||||
floatingActionButton = {
|
||||
HangingOverKeyboardView(keyboardState = keyboardAsState()) {
|
||||
AddButton(state)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) { paddings ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(paddings)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp),
|
||||
) {
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
) {
|
||||
FormFields(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Warnings(state.value.warnings.toList()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FormFields(state: MutableState<AddCustomTokenState>, closePopupTrigger: ClosePopupTrigger) {
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { ModuleMessageConverter(context) }
|
||||
|
||||
val stateValue = state.value
|
||||
stateValue.form.fieldList.forEach { field ->
|
||||
val data = ScreenFieldData.fromState(field, stateValue, errorConverter)
|
||||
when (field.id) {
|
||||
ContractAddress -> TokenContractAddressView(data)
|
||||
Network -> TokenNetworkView(data, stateValue, closePopupTrigger)
|
||||
Name -> TokenNameView(data)
|
||||
Symbol -> TokenSymbolView(data)
|
||||
Decimals -> TokenDecimalsView(data)
|
||||
DerivationPath -> TokenDerivationPathView(data, stateValue, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
|
||||
if (warnings.isEmpty()) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val warningConverter = remember { ModuleMessageConverter(context) }
|
||||
|
||||
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)
|
||||
}
|
||||
AddCustomTokenWarning(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
warning = item,
|
||||
converter = warningConverter,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(state: MutableState<AddCustomTokenState>) {
|
||||
PrimaryButtonIconLeft(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.common_add),
|
||||
icon = painterResource(id = R.drawable.ic_plus_24),
|
||||
enabled = state.value.screenState.addButton.isEnabled,
|
||||
onClick = { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) },
|
||||
)
|
||||
}
|
||||
|
||||
data class ScreenFieldData(
|
||||
val field: DataField<*>,
|
||||
val error: AddCustomTokenError?,
|
||||
val errorConverter: ModuleMessageConverter,
|
||||
val viewState: ViewStates.TokenField,
|
||||
) {
|
||||
companion object {
|
||||
fun fromState(
|
||||
field: DataField<*>,
|
||||
state: AddCustomTokenState,
|
||||
errorConverter: ModuleMessageConverter,
|
||||
): ScreenFieldData {
|
||||
return ScreenFieldData(
|
||||
field = field,
|
||||
error = state.getError(field.id),
|
||||
errorConverter = errorConverter,
|
||||
viewState = selectField(field.id, state.screenState),
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectField(id: FieldId, screenState: ScreenState): ViewStates.TokenField {
|
||||
return when (id) {
|
||||
ContractAddress -> screenState.contractAddressField
|
||||
Network -> screenState.network
|
||||
Name -> screenState.name
|
||||
Symbol -> screenState.symbol
|
||||
Decimals -> screenState.decimals
|
||||
DerivationPath -> screenState.derivationPath
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
|
||||
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
|
||||
import com.tangem.domain.features.addCustomToken.TokenField
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.BlockchainSpinner
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_contract_address_input_title,
|
||||
placeholder = "0x0000000000000000000000000000000000000000",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
isLoading = screenFieldData.viewState.isLoading,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
// trailingIcon = { PasteClearButton(showFirst = tokenField.data.value.isEmpty()) }
|
||||
) {
|
||||
domainStore.dispatch(OnTokenContractAddressChanged(Field.Data(it, true)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNameView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_name_input_title,
|
||||
placeholderId = R.string.custom_token_name_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) {
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(it, true)))
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNetworkView(
|
||||
screenFieldData: ScreenFieldData,
|
||||
state: AddCustomTokenState,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
val networkField = screenFieldData.field as TokenBlockchainField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_network_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.blockchainToName(it) ?: notSelected },
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
) { domainStore.dispatch(OnTokenNetworkChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenSymbolView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_token_symbol_input_title,
|
||||
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
) { domainStore.dispatch(OnTokenSymbolChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val tokenField = screenFieldData.field as TokenField
|
||||
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = tokenField.data,
|
||||
labelId = R.string.custom_token_decimals_input_title,
|
||||
placeholder = "8",
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
error = screenFieldData.error,
|
||||
errorConverter = screenFieldData.errorConverter,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
) { domainStore.dispatch(OnTokenDecimalsChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDerivationPathView(
|
||||
screenFieldData: ScreenFieldData,
|
||||
state: AddCustomTokenState,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
val networkField = screenFieldData.field as TokenDerivationPathField
|
||||
|
||||
BlockchainSpinner(
|
||||
title = R.string.custom_token_derivation_path_input_title,
|
||||
itemList = networkField.itemList,
|
||||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.blockchainToName(it) ?: notSelected },
|
||||
dropdownItemView = { blockchain ->
|
||||
val derivationPathName = state.blockchainToName(blockchain, true) ?: notSelected
|
||||
val blockchainName = state.blockchainToName(blockchain) ?: notSelected
|
||||
TitleSubtitle(derivationPathName, blockchainName)
|
||||
},
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun HangingOverKeyboardView(
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardState: State<Keyboard>,
|
||||
spaceBetweenKeyboard: Dp = 0.dp,
|
||||
content: @Composable (BoxScope.() -> Unit),
|
||||
) {
|
||||
val padding = remember(keyboardState) {
|
||||
when (val state = keyboardState.value) {
|
||||
is Keyboard.Closed -> 0.dp
|
||||
is Keyboard.Opened -> state.height + spaceBetweenKeyboard
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier.padding(bottom = padding)) { content() }
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.tap.common.compose.SimpleDialog
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun SelectTokenNetworkDialog(dialog: DomainDialog.SelectTokenDialog, onDismissRequest: () -> Unit) {
|
||||
SimpleDialog(
|
||||
title = stringResource(id = R.string.custom_token_network_input_title),
|
||||
items = dialog.items,
|
||||
onSelect = dialog.onSelect,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) { network -> TitleSubtitle(dialog.networkIdConverter(network.networkId), network.contractAddress ?: "") }
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun ContractAddressTests(onItemClick: VoidCallback) {
|
||||
val casesInfo = listOf(
|
||||
"USDC on ETH" to "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
"BUSD on ETH" to "0x4fabb145d64652a948d72533023f6e7a623c7c53",
|
||||
"ETH on AVALANCHE" to "0xf20d962a6c8f70c731bd838a3a388d7d48fa6e15",
|
||||
"USDC on ETH (invalid - cut address)" to "0xa0b86991c6218b36c1d1",
|
||||
"Custom EVM" to "0x1111111111111111112111111111111111111113",
|
||||
"Supported by several networks" to "0xa1faa113cbe53436df28ff0aee54275c13b40975",
|
||||
"Invalid" to "!@#_ _-%%^&&*((){P P2iOWsdfFQLA",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SolanaAddressTests(onItemClick: VoidCallback) {
|
||||
val casesInfo = listOf(
|
||||
"USDT (full)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"USDT (valid - 2/3 of address)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8Ben",
|
||||
"USDT (invalid - 1/3 of address)" to "Es9vMFrzaCERmJ",
|
||||
"ETH (full)" to "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CasesListContent(casesList: List<Pair<String, String>>, onItemClick: VoidCallback) {
|
||||
LazyColumn(
|
||||
content = {
|
||||
item {
|
||||
Row {
|
||||
ResetContractAddressButton(onItemClick)
|
||||
Text("", modifier = Modifier.weight(1f))
|
||||
ResetAllFieldsButton(onItemClick)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
items(casesList.size) {
|
||||
val (info, address) = casesList[it]
|
||||
ContractAddressButton(info, address, onItemClick)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetAllFieldsButton(onItemClick: VoidCallback) {
|
||||
ActionButton(
|
||||
name = "Reset",
|
||||
onClick = {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(
|
||||
AddCustomTokenAction.OnTokenDerivationPathChanged(
|
||||
Field.Data(
|
||||
Blockchain.Unknown,
|
||||
false,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetContractAddressButton(onItemClick: VoidCallback) {
|
||||
ActionButton(
|
||||
name = "Set empty address",
|
||||
onClick = {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContractAddressButton(name: String, address: String, onItemClick: VoidCallback) {
|
||||
ActionButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
name = name,
|
||||
onClick = {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false)))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionButton(name: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Button(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
onClick = onClick,
|
||||
) { Text(name, fontSize = 12.sp) }
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tap.features.customtoken.legacy.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TestCasesList(onItemClick: (TestCase) -> Unit) {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Surface(color = colorResource(id = R.color.lightGray5)) {
|
||||
Column(Modifier.padding(horizontal = 16.dp)) {
|
||||
listOf(TestCase.ContractAddress, TestCase.SolanaTokens)
|
||||
.map { case -> TestCaseListItem(testCase = case, onItemClick = { onItemClick(case) }) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TestCaseListItem(testCase: TestCase, onItemClick: () -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = testCase.description,
|
||||
)
|
||||
Button(
|
||||
onClick = onItemClick,
|
||||
) { Text("Start") }
|
||||
}
|
||||
}
|
||||
|
||||
enum class TestCase(val description: String, val content: @Composable (VoidCallback) -> Unit) {
|
||||
ContractAddress("Test contract address field", { ContractAddressTests(it) }),
|
||||
Auto("Test contract address field", { ContractAddressTests(it) }),
|
||||
SolanaTokens("Test Solana contract addresses", { SolanaAddressTests(it) }),
|
||||
;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue