Updated on 2026-08-14
This commit is contained in:
parent
0f44f21d6f
commit
4f237f898b
11 changed files with 218 additions and 52 deletions
|
|
@ -98,7 +98,7 @@ internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
viewModel.onSearchClick()
|
||||
} else {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal class HomeViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,13 @@ internal class ChildFactory @Inject constructor(
|
|||
if (manageTokensToggles.isFeatureEnabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = ManageTokensComponent.Params(),
|
||||
params = ManageTokensComponent.Params(
|
||||
mode = if (route.readOnlyContent) {
|
||||
ManageTokensComponent.Mode.READ_ONLY
|
||||
} else {
|
||||
ManageTokensComponent.Mode.MANAGE
|
||||
},
|
||||
),
|
||||
componentFactory = manageTokensComponentFactory,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -189,7 +189,11 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data object ManageTokens : AppRoute(path = "/manage_tokens")
|
||||
data class ManageTokens(
|
||||
val readOnlyContent: Boolean,
|
||||
) : AppRoute(path = "/manage_tokens/$readOnlyContent"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object AddCustomToken : AppRoute(path = "/add_custom_token")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
|
||||
interface ManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
class Params
|
||||
data class Params(val mode: Mode)
|
||||
|
||||
enum class Mode { READ_ONLY, MANAGE, }
|
||||
interface Factory : ComponentFactory<Params, ManageTokensComponent>
|
||||
}
|
||||
|
|
@ -6,15 +6,14 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensUM
|
||||
import com.tangem.features.managetokens.entity.*
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.ManageTokensScreen
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
|
@ -28,11 +27,18 @@ internal class PreviewManageTokensComponent : ManageTokensComponent {
|
|||
private val changedItemsIds: MutableSet<String> = mutableSetOf()
|
||||
|
||||
private var items = initItems()
|
||||
|
||||
private val previewState = MutableStateFlow(
|
||||
value = ManageTokensUM(
|
||||
value = ManageTokensUM.ManageContent(
|
||||
popBack = {},
|
||||
items = items,
|
||||
topBar = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = {},
|
||||
endButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onIconClicked = {},
|
||||
),
|
||||
),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||
query = "",
|
||||
|
|
@ -41,8 +47,8 @@ internal class PreviewManageTokensComponent : ManageTokensComponent {
|
|||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
hasChanges = false,
|
||||
isLoading = false,
|
||||
onSaveClick = {},
|
||||
onAddCustomToken = {},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.managetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
internal sealed class ManageTokensTopBarUM {
|
||||
|
||||
abstract val title: TextReference
|
||||
abstract val onBackButtonClick: () -> Unit
|
||||
|
||||
data class ReadContent(
|
||||
override val title: TextReference,
|
||||
override val onBackButtonClick: () -> Unit,
|
||||
) : ManageTokensTopBarUM()
|
||||
|
||||
data class ManageContent(
|
||||
override val title: TextReference,
|
||||
override val onBackButtonClick: () -> Unit,
|
||||
val endButton: TopAppBarButtonUM,
|
||||
) : ManageTokensTopBarUM()
|
||||
}
|
||||
|
|
@ -5,11 +5,40 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal data class ManageTokensUM(
|
||||
val popBack: () -> Unit,
|
||||
val items: ImmutableList<CurrencyItemUM>,
|
||||
val search: SearchBarUM,
|
||||
val hasChanges: Boolean,
|
||||
val onAddCustomToken: () -> Unit,
|
||||
val onSaveClick: () -> Unit,
|
||||
)
|
||||
internal sealed class ManageTokensUM {
|
||||
|
||||
abstract val popBack: () -> Unit
|
||||
abstract val isLoading: Boolean
|
||||
abstract val items: ImmutableList<CurrencyItemUM>
|
||||
abstract val topBar: ManageTokensTopBarUM
|
||||
abstract val search: SearchBarUM
|
||||
|
||||
data class ReadContent(
|
||||
override val popBack: () -> Unit,
|
||||
override val isLoading: Boolean,
|
||||
override val items: ImmutableList<CurrencyItemUM>,
|
||||
override val topBar: ManageTokensTopBarUM,
|
||||
override val search: SearchBarUM,
|
||||
) : ManageTokensUM()
|
||||
|
||||
data class ManageContent(
|
||||
override val popBack: () -> Unit,
|
||||
override val isLoading: Boolean,
|
||||
override val items: ImmutableList<CurrencyItemUM>,
|
||||
override val topBar: ManageTokensTopBarUM,
|
||||
override val search: SearchBarUM,
|
||||
val onSaveClick: () -> Unit,
|
||||
val hasChanges: Boolean,
|
||||
) : ManageTokensUM()
|
||||
|
||||
fun copySealed(
|
||||
search: SearchBarUM = this.search,
|
||||
items: ImmutableList<CurrencyItemUM> = this.items,
|
||||
hasChanges: Boolean = this is ManageContent && this.hasChanges,
|
||||
): ManageTokensUM {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(search = search, items = items, hasChanges = hasChanges)
|
||||
is ReadContent -> copy(search = search, items = items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,16 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensUM
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.*
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
|
@ -24,17 +25,33 @@ import javax.inject.Inject
|
|||
|
||||
@ComponentScoped
|
||||
internal class ManageTokensModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: ManageTokensComponent.Params = paramsContainer.require()
|
||||
private val changedItemsIds: MutableSet<String> = mutableSetOf()
|
||||
private var items = initItems()
|
||||
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(
|
||||
value = ManageTokensUM(
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(value = getInitialState(mode = params.mode))
|
||||
|
||||
private fun getInitialState(mode: ManageTokensComponent.Mode): ManageTokensUM {
|
||||
return when (mode) {
|
||||
ManageTokensComponent.Mode.READ_ONLY -> createReadContentModel()
|
||||
ManageTokensComponent.Mode.MANAGE -> createManageContentModel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReadContentModel(): ManageTokensUM.ReadContent {
|
||||
return ManageTokensUM.ReadContent(
|
||||
popBack = router::pop,
|
||||
items = items,
|
||||
isLoading = false,
|
||||
items = initItems(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||
query = "",
|
||||
|
|
@ -42,20 +59,60 @@ internal class ManageTokensModel @Inject constructor(
|
|||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createManageContentModel(): ManageTokensUM.ManageContent {
|
||||
return ManageTokensUM.ManageContent(
|
||||
popBack = router::pop,
|
||||
isLoading = false,
|
||||
items = initItems(),
|
||||
topBar = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
endButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onIconClicked = ::onAddCustomToken,
|
||||
),
|
||||
),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||
query = "",
|
||||
onQueryChange = ::searchCurrencies,
|
||||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
onSaveClick = ::onSaveClick,
|
||||
hasChanges = false,
|
||||
onSaveClick = {},
|
||||
onAddCustomToken = {},
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun onAddCustomToken() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
private fun onSaveClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun searchCurrencies(query: String) {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
val newItems = if (query.isBlank()) {
|
||||
initItems()
|
||||
} else {
|
||||
state.value.items.filter { currency ->
|
||||
currency.model.name.contains(query, ignoreCase = true)
|
||||
}.toPersistentList()
|
||||
}
|
||||
state.update { state ->
|
||||
state.copySealed(search = state.search.copy(query = query), items = newItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSearchBar(isActive: Boolean) {
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
state.copySealed(
|
||||
search = state.search.copy(isActive = isActive),
|
||||
)
|
||||
}
|
||||
|
|
@ -135,7 +192,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
items = items.mutate {
|
||||
it[index] = updatedItem
|
||||
}
|
||||
state.copy(items = items)
|
||||
state.copySealed(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +234,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
items = items.mutate {
|
||||
it[currencyIndex] = updatedItem
|
||||
}
|
||||
state.copy(
|
||||
state.copySealed(
|
||||
items = items,
|
||||
hasChanges = changedItemsIds.isNotEmpty(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
|
|
@ -36,12 +37,14 @@ import com.tangem.core.ui.components.rows.ArrowRow
|
|||
import com.tangem.core.ui.components.rows.BlockchainRow
|
||||
import com.tangem.core.ui.components.rows.ChainRow
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -57,14 +60,9 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
modifier = modifier,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
topBar = {
|
||||
TangemTopAppBar(
|
||||
ManageTokensTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
title = stringResource(id = R.string.main_manage_tokens),
|
||||
startButton = TopAppBarButtonUM.Back(state.popBack),
|
||||
endButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onIconClicked = state.onAddCustomToken,
|
||||
),
|
||||
topBar = state.topBar,
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
|
|
@ -72,18 +70,36 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
state = state,
|
||||
search = state.search,
|
||||
items = state.items,
|
||||
isLoading = state.isLoading,
|
||||
hasChanges = state is ManageTokensUM.ManageContent && state.hasChanges,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
floatingActionButton = {
|
||||
SaveChangesButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
isVisible = state.hasChanges,
|
||||
onClick = state.onSaveClick,
|
||||
)
|
||||
if (state is ManageTokensUM.ManageContent) {
|
||||
SaveChangesButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
isVisible = state.hasChanges,
|
||||
onClick = state.onSaveClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM, modifier: Modifier = Modifier) {
|
||||
TangemTopAppBar(
|
||||
modifier = modifier,
|
||||
title = topBar.title.resolveReference(),
|
||||
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
|
||||
endButton = when (topBar) {
|
||||
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
|
||||
is ManageTokensTopBarUM.ReadContent -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -106,24 +122,48 @@ private fun SaveChangesButton(isVisible: Boolean, onClick: () -> Unit, modifier:
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
private fun LoadingContent() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(color = TangemTheme.colors.icon.accent)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
search: SearchBarUM,
|
||||
items: ImmutableList<CurrencyItemUM>,
|
||||
isLoading: Boolean,
|
||||
hasChanges: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
Currencies(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
items = state.items,
|
||||
search = state.search,
|
||||
items = items,
|
||||
search = search,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
visible = state.hasChanges,
|
||||
visible = hasChanges,
|
||||
label = "bottom_fade_visibility",
|
||||
) {
|
||||
BottomFade()
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(targetState = isLoading, label = "ManageTokensLoadingContent") {
|
||||
if (it) {
|
||||
LoadingContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ internal class DefaultWalletRouter(
|
|||
}
|
||||
|
||||
override fun openManageTokensScreen() {
|
||||
router.push(AppRoute.ManageTokens)
|
||||
router.push(AppRoute.ManageTokens(readOnlyContent = false))
|
||||
}
|
||||
|
||||
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue