Updated on 2026-08-14
This commit is contained in:
commit
fc9db8288d
14 changed files with 651 additions and 84 deletions
|
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -23,6 +24,8 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
@Composable
|
||||
fun TangemSwitch(
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
checkedColor: Color = TangemTheme.colors.control.checked,
|
||||
uncheckedColor: Color = TangemTheme.colors.icon.informative,
|
||||
size: Dp = 48.dp,
|
||||
|
|
@ -39,23 +42,20 @@ fun TangemSwitch(
|
|||
(if (isChecked) checkedColor else uncheckedColor)
|
||||
.copy(alpha = if (enabled) 1f else .4f)
|
||||
}
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
enabled = enabled,
|
||||
) {
|
||||
onCheckedChange(!checked)
|
||||
}
|
||||
.indication(
|
||||
interactionSource = interactionSource,
|
||||
indication = rememberRipple(
|
||||
bounded = false,
|
||||
color = Color.Transparent,
|
||||
),
|
||||
enabled = enabled,
|
||||
role = Role.Switch,
|
||||
onClick = {
|
||||
onCheckedChange(!checked)
|
||||
},
|
||||
),
|
||||
) {
|
||||
BoxWithConstraints(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
package com.tangem.core.ui.components.fields
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusManager
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
|
|
@ -18,6 +22,7 @@ import androidx.compose.ui.platform.SoftwareKeyboardController
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -33,8 +38,9 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
fun SearchBar(state: SearchBarUM, modifier: Modifier = Modifier, colors: TextFieldColors = TangemSearchBarColors) {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
TextField(
|
||||
BasicTextField(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size48)
|
||||
|
|
@ -47,6 +53,7 @@ fun SearchBar(state: SearchBarUM, modifier: Modifier = Modifier, colors: TextFie
|
|||
},
|
||||
value = state.query,
|
||||
onValueChange = state.onQueryChange,
|
||||
interactionSource = interactionSource,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Search,
|
||||
|
|
@ -59,7 +66,43 @@ fun SearchBar(state: SearchBarUM, modifier: Modifier = Modifier, colors: TextFie
|
|||
),
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
textStyle = TangemTheme.typography.body2,
|
||||
textStyle = TangemTheme.typography.body2.copy(
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
decorationBox = @Composable { innerTextField ->
|
||||
DecorationBox(
|
||||
state = state,
|
||||
innerTextField = innerTextField,
|
||||
interactionSource = interactionSource,
|
||||
colors = colors,
|
||||
focusManager = focusManager,
|
||||
keyboardController = keyboardController,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun DecorationBox(
|
||||
state: SearchBarUM,
|
||||
innerTextField: @Composable () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
colors: TextFieldColors,
|
||||
focusManager: FocusManager,
|
||||
keyboardController: SoftwareKeyboardController?,
|
||||
) {
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = state.query,
|
||||
innerTextField = innerTextField,
|
||||
enabled = true,
|
||||
singleLine = true,
|
||||
visualTransformation = VisualTransformation.None,
|
||||
interactionSource = interactionSource,
|
||||
shape = TangemTheme.shapes.roundedCornersXLarge,
|
||||
colors = colors,
|
||||
contentPadding = PaddingValues(all = TangemTheme.dimens.spacing12),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
|
|
@ -84,8 +127,6 @@ fun SearchBar(state: SearchBarUM, modifier: Modifier = Modifier, colors: TextFie
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
shape = TangemTheme.shapes.roundedCornersXLarge,
|
||||
colors = colors,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Uni
|
|||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size52)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
horizontal = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
icon = {
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@ package com.tangem.core.ui.components.rows
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
|
|
@ -27,9 +28,10 @@ fun ChainRow(model: ChainRowUM, modifier: Modifier = Modifier, action: @Composab
|
|||
RowContentContainer(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
start = TangemTheme.dimens.spacing8,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
icon = {
|
||||
|
|
@ -65,17 +67,7 @@ private fun Preview_ChainRow(@PreviewParameter(ChainRowParameterProvider::class)
|
|||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
model = state,
|
||||
action = {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
painter = painterResource(id = R.drawable.ic_chevron_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
TangemSwitch(onCheckedChange = {}, checked = false)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ dependencies {
|
|||
implementation(deps.tangem.blockchain)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.compose.foundation)
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.decompose)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.managetokens.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface ManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ManageTokensComponent>
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
|
|
@ -11,61 +12,30 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.material)
|
||||
/* Project - API */
|
||||
implementation(projects.features.manageTokens.api)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.navigation)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.paging)
|
||||
implementation(deps.compose.reorderable)
|
||||
implementation(deps.compose.shimmer)
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.shimmer)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
/* DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Project - Data */
|
||||
implementation(projects.data.tokens)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(projects.features.manageTokens.api)
|
||||
/* Other */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.timber)
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package com.tangem.features.managetokens.component.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
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.impl.R
|
||||
import com.tangem.features.managetokens.ui.ManageTokensScreen
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
internal class PreviewManageTokensComponent : ManageTokensComponent {
|
||||
|
||||
private val changedItemsIds: MutableSet<String> = mutableSetOf()
|
||||
|
||||
private var items = initItems()
|
||||
|
||||
private val previewState = MutableStateFlow(
|
||||
value = ManageTokensUM(
|
||||
popBack = {},
|
||||
items = items,
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||
query = "",
|
||||
onQueryChange = ::searchCurrencies,
|
||||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
hasChanges = false,
|
||||
onSaveClick = {},
|
||||
onAddCustomToken = {},
|
||||
),
|
||||
)
|
||||
|
||||
private fun searchCurrencies(query: String) {
|
||||
previewState.update { state ->
|
||||
items = if (query.isBlank()) {
|
||||
initItems()
|
||||
} else {
|
||||
items.filter { currency ->
|
||||
currency.model.name.contains(query, ignoreCase = true)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
state.copy(
|
||||
search = state.search.copy(query = query),
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSearchBar(isActive: Boolean) {
|
||||
previewState.update { state ->
|
||||
state.copy(
|
||||
search = state.search.copy(isActive = isActive),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by previewState.collectAsState()
|
||||
|
||||
ManageTokensScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initItems() = List(size = 30) { index ->
|
||||
if (index < 2) {
|
||||
getCustomItem(index)
|
||||
} else {
|
||||
getBasicItem(index)
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
||||
private fun getCustomItem(index: Int) = CurrencyItemUM.Custom(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Custom token $index",
|
||||
type = "CT$index",
|
||||
icon = CurrencyIconState.CustomTokenIcon(
|
||||
tint = Color.White,
|
||||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
),
|
||||
showCustom = true,
|
||||
),
|
||||
onRemoveClick = {},
|
||||
)
|
||||
|
||||
private fun getBasicItem(index: Int) = CurrencyItemUM.Basic(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Currency $index",
|
||||
type = "C$index",
|
||||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
showCustom = false,
|
||||
),
|
||||
networks = if (index == 2) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
} else {
|
||||
CurrencyItemUM.Basic.NetworksUM.Collapsed
|
||||
},
|
||||
onExpandClick = { toggleCurrency(index) },
|
||||
)
|
||||
|
||||
private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex ->
|
||||
CurrencyNetworkUM(
|
||||
id = networkIndex.toString(),
|
||||
model = BlockchainRowUM(
|
||||
name = "NETWORK$networkIndex",
|
||||
type = "N$networkIndex",
|
||||
iconResId = R.drawable.ic_eth_16,
|
||||
isMainNetwork = networkIndex == 0,
|
||||
isSelected = false,
|
||||
),
|
||||
isSelected = false,
|
||||
onSelectedStateChange = { toggleNetwork(currencyIndex, networkIndex, isSelected = it) },
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
private fun toggleCurrency(index: Int) {
|
||||
val updatedItem = when (val item = items[index]) {
|
||||
is CurrencyItemUM.Basic -> item.copy(
|
||||
networks = if (item.networks is CurrencyItemUM.Basic.NetworksUM.Collapsed) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
} else {
|
||||
CurrencyItemUM.Basic.NetworksUM.Collapsed
|
||||
},
|
||||
)
|
||||
is CurrencyItemUM.Custom -> return
|
||||
}
|
||||
|
||||
previewState.update { state ->
|
||||
items = items.mutate {
|
||||
it[index] = updatedItem
|
||||
}
|
||||
state.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleNetwork(currencyIndex: Int, networkIndex: Int, isSelected: Boolean) {
|
||||
val updatedItem = when (val item = items[currencyIndex]) {
|
||||
is CurrencyItemUM.Basic -> {
|
||||
val updatedNetworks = (item.networks as? CurrencyItemUM.Basic.NetworksUM.Expanded)
|
||||
?.copy(
|
||||
networks = item.networks.networks.toPersistentList().mutate {
|
||||
it.fastForEachIndexed { index, network ->
|
||||
if (index == networkIndex) {
|
||||
it[index] = network.copy(
|
||||
model = network.model.copy(
|
||||
iconResId = if (isSelected) {
|
||||
R.drawable.img_eth_22
|
||||
} else {
|
||||
R.drawable.ic_eth_16
|
||||
},
|
||||
isSelected = isSelected,
|
||||
),
|
||||
isSelected = isSelected,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
?: return
|
||||
|
||||
item.copy(networks = updatedNetworks)
|
||||
}
|
||||
is CurrencyItemUM.Custom -> return
|
||||
}
|
||||
|
||||
val id = "${currencyIndex}_$networkIndex"
|
||||
if (changedItemsIds.contains(id)) {
|
||||
changedItemsIds.remove(id)
|
||||
} else {
|
||||
changedItemsIds.add(id)
|
||||
}
|
||||
|
||||
previewState.update { state ->
|
||||
items = items.mutate {
|
||||
it[currencyIndex] = updatedItem
|
||||
}
|
||||
state.copy(
|
||||
items = items,
|
||||
hasChanges = changedItemsIds.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.managetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class CurrencyItemUM {
|
||||
|
||||
abstract val id: String
|
||||
abstract val model: ChainRowUM
|
||||
|
||||
data class Basic(
|
||||
override val id: String,
|
||||
override val model: ChainRowUM,
|
||||
val networks: NetworksUM,
|
||||
val onExpandClick: () -> Unit,
|
||||
) : CurrencyItemUM() {
|
||||
|
||||
@Immutable
|
||||
sealed class NetworksUM {
|
||||
|
||||
data object Collapsed : NetworksUM()
|
||||
|
||||
data class Expanded(
|
||||
val networks: ImmutableList<CurrencyNetworkUM>,
|
||||
) : NetworksUM()
|
||||
}
|
||||
}
|
||||
|
||||
data class Custom(
|
||||
override val id: String,
|
||||
override val model: ChainRowUM,
|
||||
val onRemoveClick: () -> Unit,
|
||||
) : CurrencyItemUM()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.managetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
|
||||
@Immutable
|
||||
internal data class CurrencyNetworkUM(
|
||||
val id: String,
|
||||
val model: BlockchainRowUM,
|
||||
val isSelected: Boolean,
|
||||
val onSelectedStateChange: (Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.managetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
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,
|
||||
)
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
package com.tangem.features.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
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.FabPosition
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
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.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.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val CHEVRON_ROTATION_EXPANDED = 180f
|
||||
private const val CHEVRON_ROTATION_COLLAPSED = 0f
|
||||
|
||||
@Composable
|
||||
internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = state.popBack)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
topBar = {
|
||||
TangemTopAppBar(
|
||||
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,
|
||||
),
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
floatingActionButton = {
|
||||
SaveChangesButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
isVisible = state.hasChanges,
|
||||
onClick = state.onSaveClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SaveChangesButton(isVisible: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = isVisible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
label = "save_button_visibility",
|
||||
) {
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(id = R.string.common_save),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Currencies(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
items = state.items,
|
||||
search = state.search,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
visible = state.hasChanges,
|
||||
label = "bottom_fade_visibility",
|
||||
) {
|
||||
BottomFade()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun Currencies(items: ImmutableList<CurrencyItemUM>, search: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
) {
|
||||
stickyHeader(key = "search") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
SearchBar(state = search)
|
||||
}
|
||||
}
|
||||
|
||||
items(
|
||||
items = items,
|
||||
key = CurrencyItemUM::id,
|
||||
) { item ->
|
||||
when (item) {
|
||||
is CurrencyItemUM.Basic -> {
|
||||
BasicCurrencyItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
item = item,
|
||||
)
|
||||
}
|
||||
is CurrencyItemUM.Custom -> {
|
||||
CustomCurrencyItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
item = item,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomCurrencyItem(item: CurrencyItemUM.Custom, modifier: Modifier = Modifier) {
|
||||
ChainRow(
|
||||
modifier = modifier,
|
||||
model = item.model,
|
||||
action = {
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = resourceReference(R.string.manage_tokens_remove),
|
||||
onClick = item.onRemoveClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, modifier: Modifier = Modifier) {
|
||||
val isExpanded = item.networks is NetworksUM.Expanded
|
||||
|
||||
Column(modifier = modifier) {
|
||||
ChainRow(
|
||||
modifier = Modifier.clickable(onClick = item.onExpandClick),
|
||||
model = item.model,
|
||||
action = {
|
||||
val rotation by animateFloatAsState(
|
||||
targetValue = if (isExpanded) {
|
||||
CHEVRON_ROTATION_EXPANDED
|
||||
} else {
|
||||
CHEVRON_ROTATION_COLLAPSED
|
||||
},
|
||||
label = "chevron_rotation",
|
||||
)
|
||||
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.rotate(rotation)
|
||||
.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
NetworksList(
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing10,
|
||||
end = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
networks = item.networks,
|
||||
currencyId = item.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = networks is NetworksUM.Expanded,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
label = "networks_visibility",
|
||||
) {
|
||||
Column {
|
||||
val items = (networks as? NetworksUM.Expanded)?.networks
|
||||
|
||||
// To keep items on collapse and avoid animation cancellation
|
||||
val rememberedItems = remember(key1 = currencyId) { items }
|
||||
val currentItems = items ?: rememberedItems
|
||||
|
||||
currentItems?.fastForEachIndexed { index, network ->
|
||||
ArrowRow(
|
||||
isLastItem = index == currentItems.lastIndex,
|
||||
content = {
|
||||
BlockchainRow(
|
||||
model = network.model,
|
||||
action = {
|
||||
TangemSwitch(
|
||||
checked = network.isSelected,
|
||||
onCheckedChange = network.onSelectedStateChange,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ManageTokens() {
|
||||
TangemThemePreview {
|
||||
PreviewManageTokensComponent().Content(Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -24,7 +24,6 @@ internal fun BaseExtension.configureCompose(project: Project) {
|
|||
contains(":features:onboarding") || // TODO: divide on api/impl after migrating all onboarding to module
|
||||
contains(Regex(pattern = ":presentation\$")) ||
|
||||
contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA]
|
||||
contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function
|
||||
contains(Regex(pattern = ":features:markets:api\$")) || // provides Composable function
|
||||
contains(Regex(pattern = ":impl\$"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue