Updated on 2026-08-14
This commit is contained in:
parent
36516c900f
commit
c069df9302
19 changed files with 786 additions and 438 deletions
|
|
@ -76,6 +76,7 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.material)
|
||||
implementation(deps.googlePlay.core)
|
||||
implementation(deps.googlePlay.core.ktx)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
|
|
@ -49,6 +50,8 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.homeState == newState.homeState
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ import android.view.LayoutInflater
|
|||
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
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.tokens.presentation.ui.AddTokensScreen
|
||||
import com.tangem.tap.features.tokens.presentation.viewmodels.AddTokensViewModel
|
||||
import com.tangem.tap.features.tokens.presentation.viewmodels.TokensListViewModel
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
|
|
@ -35,9 +36,8 @@ internal class TokensListFragment : Fragment() {
|
|||
setContent {
|
||||
isTransitionGroup = true
|
||||
|
||||
val viewModel = hiltViewModel<AddTokensViewModel>().apply {
|
||||
// TODO("[REDACTED_TASK_KEY] subscribe on Redux events")
|
||||
// subscribeOnReduxEvents(lifecycle = LocalLifecycleOwner.current.lifecycle)
|
||||
val viewModel = hiltViewModel<TokensListViewModel>().apply {
|
||||
subscribeOnReduxEvents(lifecycle = LocalLifecycleOwner.current.lifecycle)
|
||||
}
|
||||
|
||||
TangemTheme {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
|
||||
/**
|
||||
* Network item state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface AddTokensNetworkItemState {
|
||||
sealed interface NetworkItemState {
|
||||
|
||||
/** Network name */
|
||||
val name: String
|
||||
|
|
@ -23,6 +25,7 @@ sealed interface AddTokensNetworkItemState {
|
|||
* Network item state that is available for read
|
||||
*
|
||||
* @property name network name
|
||||
* @property protocolName network protocol name
|
||||
* @property iconResId network icon id from resources
|
||||
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||
*/
|
||||
|
|
@ -31,25 +34,30 @@ sealed interface AddTokensNetworkItemState {
|
|||
override val protocolName: String,
|
||||
override val iconResId: Int,
|
||||
override val isMainNetwork: Boolean,
|
||||
) : AddTokensNetworkItemState
|
||||
) : NetworkItemState
|
||||
|
||||
/**
|
||||
* Network item state that is available for read and edit
|
||||
*
|
||||
* @property name network name
|
||||
* @property iconResId network icon id from resources
|
||||
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||
* @property isAdded flag that determines if the user has saved the token
|
||||
* @property networkId network id
|
||||
* @property onToggleClick lambda be invoked when switch is been toggled
|
||||
* @property name network name
|
||||
* @property protocolName network protocol name
|
||||
* @property iconResId network icon id from resources
|
||||
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||
* @property isAdded flag that determines if the user has saved the token
|
||||
* @property networkId network id
|
||||
* @property contractAddress contract address
|
||||
* @property onToggleClick lambda be invoked when switch is been toggled
|
||||
* @property onNetworkClick lambda be invoked when network item is been clicked
|
||||
*/
|
||||
data class EditAccess(
|
||||
data class ManageAccess(
|
||||
override val name: String,
|
||||
override val protocolName: String,
|
||||
override val iconResId: Int,
|
||||
override val isMainNetwork: Boolean,
|
||||
val isAdded: Boolean,
|
||||
val networkId: String,
|
||||
val onToggleClick: (String) -> Unit,
|
||||
) : AddTokensNetworkItemState
|
||||
val contractAddress: ContractAddress?,
|
||||
val onToggleClick: (String, String) -> Unit,
|
||||
val onNetworkClick: () -> Unit,
|
||||
) : NetworkItemState
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
/**
|
||||
* Token model
|
||||
*
|
||||
* @property name token name
|
||||
* @property iconUrl token icon url
|
||||
* @property networks list of networks
|
||||
*/
|
||||
data class TokenItemModel(
|
||||
val name: String,
|
||||
val iconUrl: String,
|
||||
val networks: List<AddTokensNetworkItemState>,
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Network item state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface TokenItemState {
|
||||
|
||||
/** Token name */
|
||||
val name: String
|
||||
|
||||
/** Token icon url */
|
||||
val iconUrl: String
|
||||
|
||||
/** List of networks */
|
||||
val networks: ImmutableList<NetworkItemState>
|
||||
|
||||
/**
|
||||
* Token item state that is available for read
|
||||
*
|
||||
* @property name token name
|
||||
* @property iconUrl token icon url
|
||||
* @property networks list of networks that is available for read
|
||||
*/
|
||||
data class ReadAccess(
|
||||
override val name: String,
|
||||
override val iconUrl: String,
|
||||
override val networks: ImmutableList<NetworkItemState.ReadAccess>,
|
||||
) : TokenItemState
|
||||
|
||||
/**
|
||||
* Token item state that is available for read and edit
|
||||
*
|
||||
* @property name token name
|
||||
* @property iconUrl token icon url
|
||||
* @property networks list of networks is available for read and edit
|
||||
* @property id token id
|
||||
*/
|
||||
data class ManageAccess(
|
||||
override val name: String,
|
||||
override val iconUrl: String,
|
||||
override val networks: ImmutableList<NetworkItemState.ManageAccess>,
|
||||
val id: String,
|
||||
) : TokenItemState
|
||||
}
|
||||
|
|
@ -1,21 +1,23 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* State holder for adding token screen
|
||||
* State holder for screen with list of tokens
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal sealed interface AddTokensStateHolder {
|
||||
internal sealed interface TokensListStateHolder {
|
||||
|
||||
/** Toolbar state */
|
||||
val toolbarState: AddTokensToolbarState
|
||||
val toolbarState: TokensListToolbarState
|
||||
|
||||
/**
|
||||
* Util function that allow to make a copy
|
||||
*
|
||||
* @param toolbarState toolbar state
|
||||
*/
|
||||
fun copySealed(toolbarState: AddTokensToolbarState): AddTokensStateHolder {
|
||||
fun copySealed(toolbarState: TokensListToolbarState): TokensListStateHolder {
|
||||
return when (this) {
|
||||
is ManageAccess -> copy(toolbarState = toolbarState)
|
||||
is Loading -> copy(toolbarState = toolbarState)
|
||||
|
|
@ -28,7 +30,7 @@ internal sealed interface AddTokensStateHolder {
|
|||
*
|
||||
* @property toolbarState toolbar state
|
||||
*/
|
||||
data class Loading(override val toolbarState: AddTokensToolbarState) : AddTokensStateHolder
|
||||
data class Loading(override val toolbarState: TokensListToolbarState) : TokensListStateHolder
|
||||
|
||||
/**
|
||||
* State screen that is available only for read
|
||||
|
|
@ -37,9 +39,9 @@ internal sealed interface AddTokensStateHolder {
|
|||
* @property tokens tokens list
|
||||
*/
|
||||
data class ReadAccess(
|
||||
override val toolbarState: AddTokensToolbarState,
|
||||
override val tokens: List<TokenItemModel>,
|
||||
) : AddTokensStateHolder, TokensListVisibility
|
||||
override val toolbarState: TokensListToolbarState,
|
||||
override val tokens: ImmutableList<TokenItemState.ReadAccess>,
|
||||
) : TokensListStateHolder, TokensListVisibility
|
||||
|
||||
/**
|
||||
* State screen that is available for read and edit
|
||||
|
|
@ -49,8 +51,8 @@ internal sealed interface AddTokensStateHolder {
|
|||
* @property onSaveButtonClick callback to be invoked when SaveButton is being clicked
|
||||
*/
|
||||
data class ManageAccess(
|
||||
override val toolbarState: AddTokensToolbarState,
|
||||
override val tokens: List<TokenItemModel>,
|
||||
override val toolbarState: TokensListToolbarState,
|
||||
override val tokens: ImmutableList<TokenItemState.ManageAccess>,
|
||||
val onSaveButtonClick: () -> Unit,
|
||||
) : AddTokensStateHolder, TokensListVisibility
|
||||
) : TokensListStateHolder, TokensListVisibility
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
/** Toolbar state */
|
||||
sealed interface AddTokensToolbarState {
|
||||
sealed interface TokensListToolbarState {
|
||||
|
||||
/** Callback to be invoked when BackButton is being clicked */
|
||||
val onBackButtonClick: () -> Unit
|
||||
|
|
@ -10,7 +10,7 @@ sealed interface AddTokensToolbarState {
|
|||
val onSearchButtonClick: () -> Unit
|
||||
|
||||
/** Toolbar state as title */
|
||||
sealed interface Title : AddTokensToolbarState {
|
||||
sealed interface Title : TokensListToolbarState {
|
||||
|
||||
/** Toolbar title id from resources */
|
||||
val titleResId: Int
|
||||
|
|
@ -36,7 +36,7 @@ sealed interface AddTokensToolbarState {
|
|||
* @property onSearchButtonClick callback to be invoked when SearchButton is being clicked
|
||||
* @property onAddCustomTokenClick callback to be invoked when AddCustomTokenButton is being clicked
|
||||
*/
|
||||
data class EditAccess(
|
||||
data class ManageAccess(
|
||||
override val titleResId: Int,
|
||||
override val onBackButtonClick: () -> Unit,
|
||||
override val onSearchButtonClick: () -> Unit,
|
||||
|
|
@ -59,5 +59,5 @@ sealed interface AddTokensToolbarState {
|
|||
val value: String,
|
||||
val onValueChange: (String) -> Unit,
|
||||
val onCleanButtonClick: () -> Unit,
|
||||
) : AddTokensToolbarState
|
||||
) : TokensListToolbarState
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.tap.features.tokens.presentation.states
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/** Marker interface for states that have a list of tokens */
|
||||
sealed interface TokensListVisibility {
|
||||
|
||||
/** Tokens list */
|
||||
val tokens: List<TokenItemModel>
|
||||
val tokens: ImmutableList<TokenItemState>
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap.features.tokens.presentation.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
|
@ -12,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -22,33 +26,51 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.extensions.OnBottomReached
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensStateHolder
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemModel
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListStateHolder
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListVisibility
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddTokensScreen(stateHolder: AddTokensStateHolder) {
|
||||
internal fun AddTokensScreen(stateHolder: TokensListStateHolder) {
|
||||
BackHandler(onBack = stateHolder.toolbarState.onBackButtonClick)
|
||||
|
||||
Scaffold(
|
||||
topBar = { AddTokensToolbar(state = stateHolder.toolbarState) },
|
||||
floatingActionButton = {
|
||||
if (stateHolder is AddTokensStateHolder.ManageAccess) {
|
||||
if (stateHolder is TokensListStateHolder.ManageAccess) {
|
||||
SaveChangesButton(onClick = stateHolder.onSaveButtonClick)
|
||||
}
|
||||
},
|
||||
) {
|
||||
when (stateHolder) {
|
||||
is AddTokensStateHolder.Loading -> LoadingContent()
|
||||
is TokensListVisibility -> TokensListContent(stateHolder = stateHolder, scaffoldPadding = it)
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) { scaffoldPadding ->
|
||||
// This is a hack because AnimatedContent trigger recomposition if stateHolder content is changed
|
||||
AnimatedVisibility(
|
||||
visible = stateHolder is TokensListVisibility,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
if (stateHolder is TokensListVisibility) {
|
||||
TokensListContent(
|
||||
stateHolder = stateHolder,
|
||||
scaffoldPadding = scaffoldPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = stateHolder is TokensListStateHolder.Loading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
LoadingContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,9 +89,10 @@ private fun LoadingContent() {
|
|||
|
||||
@Composable
|
||||
private fun TokensListContent(stateHolder: TokensListVisibility, scaffoldPadding: PaddingValues) {
|
||||
val state = rememberLazyListState()
|
||||
state.OnBottomReached(loadMoreThreshold = 40) {
|
||||
store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse))
|
||||
val state = rememberLazyListState().apply {
|
||||
OnBottomReached(loadMoreThreshold = 40) {
|
||||
store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse))
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
|
|
@ -79,7 +102,7 @@ private fun TokensListContent(stateHolder: TokensListVisibility, scaffoldPadding
|
|||
) {
|
||||
items(
|
||||
items = stateHolder.tokens,
|
||||
key = TokenItemModel::name,
|
||||
key = TokenItemState::name,
|
||||
itemContent = { TokenItem(model = it) },
|
||||
)
|
||||
}
|
||||
|
|
@ -102,8 +125,8 @@ private fun SaveChangesButton(onClick: () -> Unit) {
|
|||
private fun Preview_AddTokensScreen_Loading() {
|
||||
TangemTheme {
|
||||
AddTokensScreen(
|
||||
stateHolder = AddTokensStateHolder.Loading(
|
||||
toolbarState = AddTokensToolbarState.Title.EditAccess(
|
||||
stateHolder = TokensListStateHolder.Loading(
|
||||
toolbarState = TokensListToolbarState.Title.ManageAccess(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
|
|
@ -119,53 +142,16 @@ private fun Preview_AddTokensScreen_Loading() {
|
|||
private fun Preview_AddTokensScreen_ManageAccess() {
|
||||
TangemTheme {
|
||||
AddTokensScreen(
|
||||
stateHolder = AddTokensStateHolder.ManageAccess(
|
||||
toolbarState = AddTokensToolbarState.Title.EditAccess(
|
||||
stateHolder = TokensListStateHolder.ManageAccess(
|
||||
toolbarState = TokensListToolbarState.Title.ManageAccess(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
onAddCustomTokenClick = {},
|
||||
),
|
||||
tokens = listOf(
|
||||
TokenItemModel(
|
||||
name = "ETHEREUM (ETH)",
|
||||
iconUrl = "",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.img_btc_22,
|
||||
isMainNetwork = true,
|
||||
isAdded = true,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
TokenItemModel(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.img_eth_22,
|
||||
isMainNetwork = false,
|
||||
isAdded = true,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
isAdded = false,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
tokens = persistentListOf(
|
||||
TokenListPreviewData.createManageToken(),
|
||||
TokenListPreviewData.createManageToken(),
|
||||
),
|
||||
onSaveButtonClick = {},
|
||||
),
|
||||
|
|
@ -178,44 +164,15 @@ private fun Preview_AddTokensScreen_ManageAccess() {
|
|||
private fun Preview_AddTokensScreen_ReadAccess() {
|
||||
TangemTheme {
|
||||
AddTokensScreen(
|
||||
stateHolder = AddTokensStateHolder.ReadAccess(
|
||||
toolbarState = AddTokensToolbarState.Title.EditAccess(
|
||||
stateHolder = TokensListStateHolder.ReadAccess(
|
||||
toolbarState = TokensListToolbarState.Title.ReadAccess(
|
||||
titleResId = R.string.search_tokens_title,
|
||||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
onAddCustomTokenClick = {},
|
||||
),
|
||||
tokens = listOf(
|
||||
TokenItemModel(
|
||||
name = "ETHEREUM (ETH)",
|
||||
iconUrl = "",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.img_btc_22,
|
||||
isMainNetwork = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
TokenItemModel(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.img_eth_22,
|
||||
isMainNetwork = false,
|
||||
),
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
tokens = persistentListOf(
|
||||
TokenListPreviewData.createReadToken(),
|
||||
TokenListPreviewData.createReadToken(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
package com.tangem.tap.features.tokens.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
|
|
@ -16,13 +12,18 @@ import androidx.compose.material.IconButton
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -30,17 +31,19 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
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.tokens.presentation.states.AddTokensToolbarState
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState.SearchInputField
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState.Title
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState.SearchInputField
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState.Title
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddTokensToolbar(state: AddTokensToolbarState) {
|
||||
internal fun AddTokensToolbar(state: TokensListToolbarState) {
|
||||
TopAppBar(backgroundColor = TangemTheme.colors.background.secondary) {
|
||||
IconButton(onClick = state.onBackButtonClick) {
|
||||
Icon(
|
||||
|
|
@ -50,14 +53,9 @@ internal fun AddTokensToolbar(state: AddTokensToolbarState) {
|
|||
)
|
||||
}
|
||||
|
||||
val contentModifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(bottom = TangemTheme.dimens.spacing4)
|
||||
|
||||
when (state) {
|
||||
is Title -> TitleContent(state = state, modifier = contentModifier)
|
||||
is SearchInputField -> InputContent(state = state, modifier = contentModifier)
|
||||
is Title -> TitleContent(state = state, modifier = Modifier.weight(1f))
|
||||
is SearchInputField -> InputContent(state = state, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +64,7 @@ internal fun AddTokensToolbar(state: AddTokensToolbarState) {
|
|||
private fun TitleContent(state: Title, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = stringResource(id = state.titleResId),
|
||||
modifier = modifier,
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing26),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.h3,
|
||||
|
|
@ -80,7 +78,7 @@ private fun TitleContent(state: Title, modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
if (state is Title.EditAccess) {
|
||||
if (state is Title.ManageAccess) {
|
||||
IconButton(onClick = state.onAddCustomTokenClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_plus_24),
|
||||
|
|
@ -91,12 +89,15 @@ private fun TitleContent(state: Title, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun InputContent(state: SearchInputField, modifier: Modifier = Modifier) {
|
||||
private fun InputContent(state: SearchInputField, modifier: Modifier = Modifier) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
BasicTextField(
|
||||
value = state.value,
|
||||
onValueChange = state.onValueChange,
|
||||
modifier = modifier,
|
||||
modifier = modifier.focusRequester(focusRequester),
|
||||
textStyle = TangemTheme.typography.subtitle1.copy(fontWeight = FontWeight.Normal),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions(onSearch = { state.onSearchButtonClick() }),
|
||||
|
|
@ -104,48 +105,52 @@ fun InputContent(state: SearchInputField, modifier: Modifier = Modifier) {
|
|||
maxLines = 1,
|
||||
cursorBrush = SolidColor(value = TangemTheme.colors.stroke.secondary),
|
||||
) { innerTextField ->
|
||||
Column(modifier = Modifier.padding(top = TangemTheme.dimens.spacing6)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing6),
|
||||
) {
|
||||
Hint(value = state.value)
|
||||
innerTextField()
|
||||
}
|
||||
|
||||
IconButton(onClick = state.onCleanButtonClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_cross_rounded_24),
|
||||
contentDescription = "Clean entered value",
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(height = TangemTheme.dimens.size1)
|
||||
.background(color = TangemTheme.colors.stroke.primary),
|
||||
)
|
||||
.weight(1f)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing28,
|
||||
top = TangemTheme.dimens.spacing2,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
Hint(value = state.value)
|
||||
innerTextField()
|
||||
}
|
||||
|
||||
IconButton(onClick = state.onCleanButtonClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_cross_rounded_24),
|
||||
contentDescription = "Clean entered value",
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a hack because Compose can't show the keyboard if the input field is not found.
|
||||
// So first I request focus and make a delay and then show the keyboard.
|
||||
val keyboardManager = LocalSoftwareKeyboardController.current
|
||||
LaunchedEffect(keyboardManager) {
|
||||
focusRequester.requestFocus()
|
||||
delay(timeMillis = 100)
|
||||
keyboardManager?.show()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Hint(value: String) {
|
||||
if (value.isBlank()) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4),
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_search_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
tint = TangemColorPalette.Dark1,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_search),
|
||||
|
|
@ -164,7 +169,7 @@ private fun Hint(value: String) {
|
|||
private fun Preview_AddTokensToolbar_EditAccess() {
|
||||
TangemTheme {
|
||||
AddTokensToolbar(
|
||||
state = Title.EditAccess(
|
||||
state = Title.ManageAccess(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.tap.features.tokens.presentation.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -20,7 +18,8 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.NetworkItemState
|
||||
import kotlinx.collections.immutable.ImmutableCollection
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -28,21 +27,19 @@ import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemSt
|
|||
@Composable
|
||||
internal fun BriefNetworksList(
|
||||
isCollapsed: Boolean,
|
||||
networks: List<AddTokensNetworkItemState>,
|
||||
networks: ImmutableCollection<NetworkItemState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isCollapsed,
|
||||
modifier = modifier,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = shrinkVertically() + fadeOut(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
if (isCollapsed) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
|
||||
networks.forEach {
|
||||
key(it.name) {
|
||||
BriefNetworkItem(model = it)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
|
||||
networks.forEach {
|
||||
key(NetworkItemState::name) {
|
||||
BriefNetworkItem(model = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +50,7 @@ internal fun BriefNetworksList(
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun BriefNetworkItem(model: AddTokensNetworkItemState) {
|
||||
internal fun BriefNetworkItem(model: NetworkItemState) {
|
||||
Box(modifier = Modifier.size(size = TangemTheme.dimens.size20)) {
|
||||
Image(
|
||||
painter = painterResource(id = model.iconResId),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import androidx.compose.animation.expandVertically
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
|
|
@ -18,8 +21,11 @@ import androidx.compose.material.SwitchDefaults
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -28,8 +34,8 @@ 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.tokens.presentation.states.AddTokensNetworkItemState
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.tap.features.tokens.presentation.states.NetworkItemState
|
||||
import kotlinx.collections.immutable.ImmutableCollection
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -37,7 +43,8 @@ import com.tangem.wallet.R
|
|||
@Composable
|
||||
internal fun DetailedNetworksList(
|
||||
isExpanded: Boolean,
|
||||
networks: List<AddTokensNetworkItemState>,
|
||||
tokenId: String?,
|
||||
networks: ImmutableCollection<NetworkItemState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
|
|
@ -46,38 +53,51 @@ internal fun DetailedNetworksList(
|
|||
enter = fadeIn() + expandVertically(),
|
||||
exit = shrinkVertically() + fadeOut(),
|
||||
) {
|
||||
if (isExpanded) {
|
||||
Column {
|
||||
networks.forEachIndexed { index, network ->
|
||||
key(network.name) {
|
||||
DetailedNetworkItem(model = network, isLastItem = networks.lastIndex == index)
|
||||
}
|
||||
Column {
|
||||
networks.forEachIndexed { index, network ->
|
||||
key(network.name) {
|
||||
DetailedNetworkItem(model = network, tokenId = tokenId, isLastItem = networks.size - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun DetailedNetworkItem(model: AddTokensNetworkItemState, isLastItem: Boolean) {
|
||||
private fun DetailedNetworkItem(model: NetworkItemState, tokenId: String?, isLastItem: Boolean) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val itemHeight = TangemTheme.dimens.size50
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
enabled = model is NetworkItemState.ManageAccess,
|
||||
onLongClick = {
|
||||
if (model is NetworkItemState.ManageAccess && model.contractAddress != null) {
|
||||
clipboardManager.setText(AnnotatedString(text = model.contractAddress))
|
||||
model.onNetworkClick()
|
||||
}
|
||||
},
|
||||
onClick = {},
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = itemHeight)
|
||||
.padding(start = TangemTheme.dimens.spacing38),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NetworkItemArrow(itemHeight = itemHeight, isLastItem = isLastItem)
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing14))
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing16))
|
||||
BriefNetworkItem(model = model)
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6))
|
||||
NetworkTitle(model = model)
|
||||
|
||||
if (model is AddTokensNetworkItemState.EditAccess) {
|
||||
if (model is NetworkItemState.ManageAccess) {
|
||||
Switch(
|
||||
checked = model.isAdded,
|
||||
onCheckedChange = { model.onToggleClick(model.networkId) },
|
||||
onCheckedChange = { model.onToggleClick(requireNotNull(tokenId), model.networkId) },
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing8),
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = TangemColorPalette.Meadow,
|
||||
|
|
@ -88,7 +108,7 @@ private fun DetailedNetworkItem(model: AddTokensNetworkItemState, isLastItem: Bo
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.NetworkTitle(model: AddTokensNetworkItemState) {
|
||||
private fun RowScope.NetworkTitle(model: NetworkItemState) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = buildAnnotatedString {
|
||||
|
|
@ -105,7 +125,7 @@ private fun RowScope.NetworkTitle(model: AddTokensNetworkItemState) {
|
|||
},
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 13.sp,
|
||||
color = if (model is AddTokensNetworkItemState.EditAccess && model.isAdded) {
|
||||
color = if (model is NetworkItemState.ManageAccess && model.isAdded) {
|
||||
TangemColorPalette.Black
|
||||
} else {
|
||||
TangemColorPalette.Dark2
|
||||
|
|
@ -115,30 +135,12 @@ private fun RowScope.NetworkTitle(model: AddTokensNetworkItemState) {
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_DetailedNetworksList_EditAccess() {
|
||||
private fun Preview_DetailedNetworksList_ManageAccess() {
|
||||
TangemTheme {
|
||||
DetailedNetworksList(
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
isAdded = true,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
isAdded = false,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
),
|
||||
isExpanded = false,
|
||||
isExpanded = true,
|
||||
tokenId = null,
|
||||
networks = TokenListPreviewData.createManageNetworksList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -148,21 +150,9 @@ private fun Preview_DetailedNetworksList_EditAccess() {
|
|||
private fun Preview_DetailedNetworksList_ReadAccess() {
|
||||
TangemTheme {
|
||||
DetailedNetworksList(
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "ETHEREUM",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
),
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
),
|
||||
),
|
||||
isExpanded = false,
|
||||
isExpanded = true,
|
||||
tokenId = null,
|
||||
networks = TokenListPreviewData.createReadNetworksList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -38,16 +38,15 @@ import coil.compose.SubcomposeAsyncImage
|
|||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.extensions.toPx
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemModel
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemState
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun TokenItem(model: TokenItemModel) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
internal fun TokenItem(model: TokenItemState) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
|
|
@ -102,6 +101,7 @@ internal fun TokenItem(model: TokenItemModel) {
|
|||
|
||||
DetailedNetworksList(
|
||||
isExpanded = isExpanded,
|
||||
tokenId = (model as? TokenItemState.ManageAccess)?.id,
|
||||
networks = model.networks,
|
||||
modifier = Modifier.constrainAs(detailedNetworksList) {
|
||||
top.linkTo(anchor = availableNetworksText.bottom, margin = spacing16)
|
||||
|
|
@ -115,7 +115,7 @@ internal fun TokenItem(model: TokenItemModel) {
|
|||
onClick = { isExpanded = !isExpanded },
|
||||
modifier = Modifier.constrainAs(changeNetworksViewButton) {
|
||||
top.linkTo(parent.top)
|
||||
end.linkTo(anchor = parent.end, margin = spacing16)
|
||||
end.linkTo(anchor = parent.end, margin = spacing6)
|
||||
if (!isExpanded) bottom.linkTo(parent.bottom)
|
||||
},
|
||||
)
|
||||
|
|
@ -205,34 +205,9 @@ private fun ChangeNetworksViewButton(isExpanded: Boolean, onClick: () -> Unit, m
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokenItem_EditAccess() {
|
||||
private fun Preview_TokenItem_ManageAccess() {
|
||||
TangemTheme {
|
||||
TokenItem(
|
||||
TokenItemModel(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "Ethereum",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
isAdded = true,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
AddTokensNetworkItemState.EditAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
isAdded = false,
|
||||
networkId = "",
|
||||
onToggleClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
TokenItem(model = TokenListPreviewData.createManageToken())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,49 +215,6 @@ private fun Preview_TokenItem_EditAccess() {
|
|||
@Composable
|
||||
private fun Preview_TokenItem_ReadAccess() {
|
||||
TangemTheme {
|
||||
TokenItem(
|
||||
TokenItemModel(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = listOf(
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "Ethereum",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
),
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokenItem_EditAccess_LargeNetworkList() {
|
||||
TangemTheme {
|
||||
TokenItem(
|
||||
TokenItemModel(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = List(
|
||||
size = 15,
|
||||
init = {
|
||||
AddTokensNetworkItemState.ReadAccess(
|
||||
name = if (it % 2 != 0) "Ethereum" else "BNB SMART CHAIN",
|
||||
protocolName = if (it % 2 != 0) "MAIN" else "BEP20",
|
||||
iconResId = if (it % 2 != 0) R.drawable.ic_eth_no_color else R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = it % 2 != 0,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
TokenItem(model = TokenListPreviewData.createReadToken())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.tap.features.tokens.presentation.ui
|
||||
|
||||
import com.tangem.tap.features.tokens.presentation.states.NetworkItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemState
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object TokenListPreviewData {
|
||||
|
||||
fun createManageToken(): TokenItemState.ManageAccess {
|
||||
return TokenItemState.ManageAccess(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = createManageNetworksList(),
|
||||
id = "",
|
||||
)
|
||||
}
|
||||
|
||||
fun createReadToken(): TokenItemState.ReadAccess {
|
||||
return TokenItemState.ReadAccess(
|
||||
name = "Tether (USDT)",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
networks = createReadNetworksList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun createManageNetworksList(): ImmutableList<NetworkItemState.ManageAccess> {
|
||||
return persistentListOf(
|
||||
NetworkItemState.ManageAccess(
|
||||
name = "Ethereum",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
isAdded = true,
|
||||
networkId = "",
|
||||
contractAddress = null,
|
||||
onToggleClick = { _, _ -> },
|
||||
onNetworkClick = {},
|
||||
),
|
||||
NetworkItemState.ManageAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
isAdded = false,
|
||||
networkId = "",
|
||||
contractAddress = null,
|
||||
onToggleClick = { _, _ -> },
|
||||
onNetworkClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createReadNetworksList(): ImmutableList<NetworkItemState.ReadAccess> {
|
||||
return persistentListOf(
|
||||
NetworkItemState.ReadAccess(
|
||||
name = "Ethereum",
|
||||
protocolName = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_no_color,
|
||||
isMainNetwork = true,
|
||||
),
|
||||
NetworkItemState.ReadAccess(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_no_color,
|
||||
isMainNetwork = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.presentation.viewmodels
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.tokens.presentation.router.AddTokensRouter
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensStateHolder
|
||||
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* ViewModel for add tokens screen
|
||||
*
|
||||
* @property router feature router
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class AddTokensViewModel @Inject constructor(
|
||||
private val router: AddTokensRouter,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StoreSubscriber<TokensState> {
|
||||
|
||||
var uiState by mutableStateOf(getInitialUiState())
|
||||
private set
|
||||
|
||||
init {
|
||||
loadContent()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
store.unsubscribe(subscriber = this)
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
uiState = uiState.copySealed(
|
||||
toolbarState = getToolbarState(hasEditAccess = state.allowToAdd),
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadContent() {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { old, new -> old.tokensState == new.tokensState }
|
||||
.select(AppState::tokensState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialUiState(): AddTokensStateHolder {
|
||||
return AddTokensStateHolder.Loading(toolbarState = getToolbarState(store.state.tokensState.allowToAdd))
|
||||
}
|
||||
|
||||
private fun getToolbarState(hasEditAccess: Boolean): AddTokensToolbarState {
|
||||
return if (hasEditAccess) {
|
||||
AddTokensToolbarState.Title.EditAccess(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = router::popBackStack,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
onAddCustomTokenClick = router::openAddCustomTokenScreen,
|
||||
)
|
||||
} else {
|
||||
AddTokensToolbarState.Title.ReadAccess(
|
||||
titleResId = R.string.search_tokens_title,
|
||||
onBackButtonClick = router::popBackStack,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchButtonClick() {
|
||||
uiState = when (val state = uiState.toolbarState) {
|
||||
is AddTokensToolbarState.Title -> {
|
||||
uiState.copySealed(
|
||||
toolbarState = AddTokensToolbarState.SearchInputField(
|
||||
onBackButtonClick = uiState.toolbarState.onBackButtonClick,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
value = "",
|
||||
onValueChange = ::onValueChange,
|
||||
onCleanButtonClick = { onValueChange(newValue = "") },
|
||||
),
|
||||
)
|
||||
}
|
||||
is AddTokensToolbarState.SearchInputField -> {
|
||||
store.dispatch(TokensAction.SetSearchInput(state.value))
|
||||
uiState.copySealed(
|
||||
getToolbarState(store.state.tokensState.allowToAdd),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onValueChange(newValue: String) {
|
||||
val state = requireNotNull(
|
||||
value = uiState.toolbarState as? AddTokensToolbarState.SearchInputField,
|
||||
lazyMessage = { "Impossible to change value in another state" },
|
||||
)
|
||||
|
||||
uiState = uiState.copySealed(toolbarState = state.copy(value = newValue))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,453 @@
|
|||
package com.tangem.tap.features.tokens.presentation.viewmodels
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.presentation.router.AddTokensRouter
|
||||
import com.tangem.tap.features.tokens.presentation.states.NetworkItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokenItemState
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListStateHolder
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.ui.compose.fullName
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* ViewModel for add tokens screen
|
||||
*
|
||||
* @property router feature router
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class TokensListViewModel @Inject constructor(
|
||||
private val router: AddTokensRouter,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
) : ViewModel() {
|
||||
|
||||
private val uiStateBuilder = UiStateBuilder()
|
||||
|
||||
var uiState by mutableStateOf(uiStateBuilder.getInitialUiState())
|
||||
private set
|
||||
|
||||
private val reduxSubscriber = ReduxSubscriber()
|
||||
private var debounceJob: Job? = null
|
||||
|
||||
private var originalAddedTokenList: List<TokenWithBlockchain>? = null
|
||||
private var originalAddedBlockchainList: List<Blockchain>? = null
|
||||
private var addedTokenList: List<TokenWithBlockchain>? = null
|
||||
private var addedBlockchainList: List<Blockchain>? = null
|
||||
|
||||
fun subscribeOnReduxEvents(lifecycle: Lifecycle) {
|
||||
lifecycle.addObserver(reduxSubscriber)
|
||||
}
|
||||
|
||||
private object AnalyticsSender {
|
||||
fun sendWhenTokenAdded(token: Token) {
|
||||
Analytics.send(
|
||||
event = ManageTokens.TokenSwitcherChanged(
|
||||
type = AnalyticsParam.CurrencyType.Token(token),
|
||||
state = AnalyticsParam.OnOffState.On,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendWhenBlockchainAdded(blockchain: Blockchain) {
|
||||
Analytics.send(
|
||||
event = ManageTokens.TokenSwitcherChanged(
|
||||
type = AnalyticsParam.CurrencyType.Blockchain(blockchain),
|
||||
state = AnalyticsParam.OnOffState.On,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendWhenTokenRemoved(token: Token) {
|
||||
Analytics.send(
|
||||
event = ManageTokens.TokenSwitcherChanged(
|
||||
type = AnalyticsParam.CurrencyType.Token(token),
|
||||
state = AnalyticsParam.OnOffState.Off,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendWhenBlockchainRemoved(blockchain: Blockchain) {
|
||||
Analytics.send(
|
||||
event = ManageTokens.TokenSwitcherChanged(
|
||||
type = AnalyticsParam.CurrencyType.Blockchain(blockchain),
|
||||
state = AnalyticsParam.OnOffState.Off,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendWhenSaveButtonClicked() {
|
||||
Analytics.send(ManageTokens.ButtonSaveChanges())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ReduxSubscriber : DefaultLifecycleObserver, StoreSubscriber<TokensState> {
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
store.subscribe(
|
||||
subscriber = this,
|
||||
transform = { state ->
|
||||
state
|
||||
.skipRepeats { old, new -> old.tokensState == new.tokensState }
|
||||
.select(AppState::tokensState)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
store.unsubscribe(subscriber = this)
|
||||
}
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
val isNotOriginalListsInitialized = originalAddedTokenList == null || originalAddedBlockchainList == null
|
||||
val isNotCurrentListsInitialized = addedTokenList == null || addedBlockchainList == null
|
||||
|
||||
if (isNotOriginalListsInitialized || isNotCurrentListsInitialized) {
|
||||
originalAddedTokenList = state.addedTokens
|
||||
originalAddedBlockchainList = state.addedBlockchains
|
||||
addedTokenList = state.addedTokens
|
||||
addedBlockchainList = state.addedBlockchains
|
||||
}
|
||||
|
||||
uiStateBuilder.updateStateByReduxState(state)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class UiStateBuilder {
|
||||
|
||||
fun getInitialUiState(): TokensListStateHolder {
|
||||
return TokensListStateHolder.Loading(toolbarState = getToolbarState())
|
||||
}
|
||||
|
||||
fun updateStateByReduxState(state: TokensState) {
|
||||
uiState = if (state.currencies.isEmpty()) {
|
||||
TokensListStateHolder.Loading(toolbarState = getToolbarState(reduxState = state))
|
||||
} else if (state.allowToAdd) {
|
||||
TokensListStateHolder.ManageAccess(
|
||||
toolbarState = getToolbarState(reduxState = state),
|
||||
tokens = state.currencies.map(::toManageTokenItemState).toImmutableList(),
|
||||
onSaveButtonClick = {
|
||||
val notNullAddedTokens = addedTokenList ?: return@ManageAccess
|
||||
val notNullAddedBlockchains = addedBlockchainList ?: return@ManageAccess
|
||||
|
||||
AnalyticsSender.sendWhenSaveButtonClicked()
|
||||
store.dispatch(TokensAction.SaveChanges(notNullAddedTokens, notNullAddedBlockchains))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TokensListStateHolder.ReadAccess(
|
||||
toolbarState = getToolbarState(state),
|
||||
tokens = state.currencies.map(::toReadTokenItemState).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToolbarState(reduxState: TokensState = store.state.tokensState): TokensListToolbarState {
|
||||
val toolbarState = uiState.toolbarState
|
||||
|
||||
return if (reduxState.searchInput != null && toolbarState is TokensListToolbarState.SearchInputField) {
|
||||
TokensListToolbarState.SearchInputField(
|
||||
onBackButtonClick = router::popBackStack,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
value = toolbarState.value,
|
||||
onValueChange = ::onSearchValueChange,
|
||||
onCleanButtonClick = ::onCleanButtonClick,
|
||||
)
|
||||
} else if (reduxState.allowToAdd) {
|
||||
TokensListToolbarState.Title.ManageAccess(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = router::popBackStack,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
onAddCustomTokenClick = router::openAddCustomTokenScreen,
|
||||
)
|
||||
} else {
|
||||
TokensListToolbarState.Title.ReadAccess(
|
||||
titleResId = R.string.search_tokens_title,
|
||||
onBackButtonClick = router::popBackStack,
|
||||
onSearchButtonClick = ::onSearchButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchButtonClick() {
|
||||
uiState = when (val state = uiState.toolbarState) {
|
||||
is TokensListToolbarState.Title -> {
|
||||
uiState.copySealed(
|
||||
toolbarState = TokensListToolbarState.SearchInputField(
|
||||
onBackButtonClick = uiState.toolbarState.onBackButtonClick,
|
||||
onSearchButtonClick = uiState.toolbarState.onSearchButtonClick,
|
||||
value = "",
|
||||
onValueChange = ::onSearchValueChange,
|
||||
onCleanButtonClick = ::onCleanButtonClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
is TokensListToolbarState.SearchInputField -> {
|
||||
store.dispatch(TokensAction.SetSearchInput(searchInput = state.value))
|
||||
uiState.copySealed(toolbarState = getToolbarState())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchValueChange(newValue: String) {
|
||||
(uiState.toolbarState as? TokensListToolbarState.SearchInputField)?.let { state ->
|
||||
uiState = uiState.copySealed(toolbarState = state.copy(value = newValue))
|
||||
}
|
||||
|
||||
debounceJob?.cancel()
|
||||
debounceJob = viewModelScope.launch(dispatchers.io) {
|
||||
delay(timeMillis = 800L)
|
||||
store.dispatch(TokensAction.SetSearchInput(searchInput = newValue))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCleanButtonClick() {
|
||||
(uiState.toolbarState as? TokensListToolbarState.SearchInputField)?.let { state ->
|
||||
if (state.value.isEmpty()) {
|
||||
uiState = uiState.copySealed(toolbarState = getToolbarState())
|
||||
} else {
|
||||
onSearchValueChange(newValue = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onToggleClick(tokenId: String, networkId: String) {
|
||||
(uiState as? TokensListStateHolder.ManageAccess)?.let { state ->
|
||||
val switchedToken = state.tokens.firstOrNull { it.id == tokenId }
|
||||
val contractAddress = switchedToken?.networks
|
||||
?.firstOrNull { it.networkId == networkId }
|
||||
?.contractAddress
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(
|
||||
networkId = if (contractAddress == null) networkId else tokenId,
|
||||
)
|
||||
|
||||
if (blockchain != null && contractAddress == null) {
|
||||
onBlockchainToggleClick(blockchain)
|
||||
} else if (contractAddress != null) {
|
||||
onTokenToggleClick(tokenId, networkId, switchedToken, contractAddress)
|
||||
}
|
||||
|
||||
updateTokensList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBlockchainToggleClick(blockchain: Blockchain) {
|
||||
val isRemoveAction = addedBlockchainList?.contains(blockchain)
|
||||
|
||||
if (isRemoveAction == true) {
|
||||
val isTokenWithSameBlockchainFound = addedTokenList?.any { it.blockchain == blockchain }
|
||||
val isAddedOnMainScreen = originalAddedBlockchainList?.contains(blockchain)
|
||||
|
||||
if (isTokenWithSameBlockchainFound == true) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = blockchain.name,
|
||||
currencySymbol = blockchain.currency,
|
||||
),
|
||||
)
|
||||
} else if (isAddedOnMainScreen == true) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = blockchain.name,
|
||||
onOk = {
|
||||
AnalyticsSender.sendWhenBlockchainAdded(blockchain)
|
||||
addedBlockchainList = addedBlockchainList?.minus(blockchain)
|
||||
updateTokensList()
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
AnalyticsSender.sendWhenBlockchainRemoved(blockchain)
|
||||
addedBlockchainList = addedBlockchainList?.minus(blockchain)
|
||||
}
|
||||
} else {
|
||||
AnalyticsSender.sendWhenBlockchainAdded(blockchain)
|
||||
addedBlockchainList = addedBlockchainList?.plus(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTokenToggleClick(
|
||||
tokenId: String,
|
||||
networkId: String,
|
||||
switchedToken: TokenItemState.ManageAccess,
|
||||
contractAddress: String,
|
||||
) {
|
||||
val currency = store.state.tokensState.currencies.firstOrNull { it.id == tokenId } ?: return
|
||||
val contract = currency.contracts.firstOrNull { it.networkId == networkId } ?: return
|
||||
val token = TokenWithBlockchain(
|
||||
token = Token(
|
||||
id = switchedToken.id,
|
||||
name = switchedToken.name,
|
||||
symbol = currency.symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = requireNotNull(contract.decimalCount),
|
||||
),
|
||||
blockchain = contract.blockchain,
|
||||
)
|
||||
val isUnsupportedToken = !store.state.tokensState.canHandleToken(token)
|
||||
val isRemoveAction = addedTokenList?.contains(token)
|
||||
val isAddedOnMainScreen = originalAddedTokenList?.contains(token)
|
||||
if (isRemoveAction == true) {
|
||||
if (isAddedOnMainScreen == true) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = token.token.name,
|
||||
onOk = {
|
||||
AnalyticsSender.sendWhenTokenRemoved(token.token)
|
||||
addedTokenList = addedTokenList?.minus(token)
|
||||
updateTokensList()
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
AnalyticsSender.sendWhenTokenRemoved(token.token)
|
||||
addedTokenList = addedTokenList?.minus(token)
|
||||
}
|
||||
} else {
|
||||
if (isUnsupportedToken) {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.alert_manage_tokens_unsupported_message,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
AnalyticsSender.sendWhenTokenAdded(token.token)
|
||||
addedTokenList = addedTokenList?.plus(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTokensList() {
|
||||
(uiState as? TokensListStateHolder.ManageAccess)?.let { state ->
|
||||
val tokens = store.state.tokensState.currencies.mapNotNull { currency ->
|
||||
state.tokens.firstOrNull { it.id == currency.id }?.copy(
|
||||
networks = currency.contracts.mapNotNull { contract ->
|
||||
state.tokens.firstOrNull { it.id == currency.id }
|
||||
?.networks?.firstOrNull { it.networkId == contract.networkId }
|
||||
?.copy(
|
||||
isAdded = contract.isAdded(),
|
||||
iconResId = if (contract.isAdded()) {
|
||||
getActiveIconRes(contract.blockchain.id)
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
},
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
uiState = state.copy(tokens = tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toManageTokenItemState(currency: Currency): TokenItemState.ManageAccess {
|
||||
return TokenItemState.ManageAccess(
|
||||
name = currency.fullName,
|
||||
iconUrl = currency.iconUrl,
|
||||
networks = currency.contracts.map(::toManageNetworkItemState).toImmutableList(),
|
||||
id = currency.id,
|
||||
)
|
||||
}
|
||||
|
||||
private fun toManageNetworkItemState(contract: Contract): NetworkItemState.ManageAccess {
|
||||
val isAdded = contract.isAdded()
|
||||
return NetworkItemState.ManageAccess(
|
||||
name = contract.blockchain.fullNameWithoutTestnet.uppercase(),
|
||||
protocolName = if (contract.address == null) {
|
||||
MAIN_NETWORK_LABEL
|
||||
} else {
|
||||
contract.blockchain.getNetworkName().uppercase()
|
||||
},
|
||||
iconResId = if (isAdded) {
|
||||
getActiveIconRes(contract.blockchain.id)
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
},
|
||||
isMainNetwork = contract.address == null,
|
||||
isAdded = isAdded,
|
||||
networkId = contract.networkId,
|
||||
contractAddress = contract.address,
|
||||
onToggleClick = ::onToggleClick,
|
||||
onNetworkClick = { store.dispatchNotification(R.string.contract_address_copied_message) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun toReadTokenItemState(currency: Currency): TokenItemState.ReadAccess {
|
||||
return TokenItemState.ReadAccess(
|
||||
name = currency.fullName,
|
||||
iconUrl = currency.iconUrl,
|
||||
networks = currency.contracts.map(::toReadNetworkItemState).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun toReadNetworkItemState(contract: Contract): NetworkItemState.ReadAccess {
|
||||
return NetworkItemState.ReadAccess(
|
||||
name = contract.blockchain.fullNameWithoutTestnet.uppercase(),
|
||||
protocolName = if (contract.address == null) {
|
||||
MAIN_NETWORK_LABEL
|
||||
} else {
|
||||
contract.blockchain.getNetworkName().uppercase()
|
||||
},
|
||||
iconResId = if (contract.isAdded()) {
|
||||
getActiveIconRes(contract.blockchain.id)
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
},
|
||||
isMainNetwork = contract.address == null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Contract.isAdded(): Boolean {
|
||||
return if (address != null) {
|
||||
addedTokenList?.any { addedToken ->
|
||||
address == addedToken.token.contractAddress && blockchain == addedToken.blockchain
|
||||
}
|
||||
} else {
|
||||
addedBlockchainList?.contains(blockchain)
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAIN_NETWORK_LABEL = "MAIN"
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ data class TangemDimens internal constructor(
|
|||
val spacing18: Dp = 18.dp,
|
||||
val spacing20: Dp = 20.dp,
|
||||
val spacing24: Dp = 24.dp,
|
||||
val spacing26: Dp = 26.dp,
|
||||
val spacing28: Dp = 28.dp,
|
||||
val spacing32: Dp = 32.dp,
|
||||
val spacing34: Dp = 34.dp,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ hilt = "2.44"
|
|||
hilt-navigation = "1.0.0"
|
||||
jodatime = "2.12.1"
|
||||
krateSharedPref = "2.0.0"
|
||||
kotlin-immutable-collections = "0.3.5"
|
||||
kotsonGsonExt = "2.5.0"
|
||||
lottie = "3.4.0"
|
||||
moshi = "1.12.0"
|
||||
|
|
@ -168,6 +169,7 @@ appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer"
|
|||
armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" }
|
||||
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
|
||||
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" }
|
||||
kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" }
|
||||
desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" }
|
||||
googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" }
|
||||
googlePlay-core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "googlePlayCoreKtx" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue