Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-02 17:57:18 +05:00
parent 1c3dd8cf3f
commit 65ead13295
7 changed files with 279 additions and 6 deletions

View file

@ -33,7 +33,7 @@ import com.tangem.wallet.R
import kotlin.math.max
@Composable
fun StoriesScreen(
internal fun StoriesScreen(
homeState: MutableState<HomeState>,
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit,

View file

@ -10,6 +10,7 @@ import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.managetokens.ManageTokensToggles
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
@ -48,6 +49,7 @@ internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory,
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
@ -122,7 +124,11 @@ internal class ChildFactory @Inject constructor(
}
is AppRoute.ManageTokens -> {
if (manageTokensToggles.isFeatureEnabled) {
TODO("Not implemented yet") // [REDACTED_JIRA]
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = ManageTokensComponent.Params(),
componentFactory = manageTokensComponentFactory,
)
} else {
route.asFragmentChild(Provider { TokensListFragment() })
}

View file

@ -2,13 +2,10 @@ 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,
)
class Params
interface Factory : ComponentFactory<Params, ManageTokensComponent>
}

View file

@ -0,0 +1,40 @@
package com.tangem.features.managetokens.component.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.model.ManageTokensModel
import com.tangem.features.managetokens.ui.ManageTokensScreen
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultManageTokensComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted params: ManageTokensComponent.Params,
) : ManageTokensComponent, AppComponentContext by context {
private val model: ManageTokensModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
ManageTokensScreen(
modifier = modifier,
state = state,
)
}
@AssistedFactory
interface Factory : ManageTokensComponent.Factory {
override fun create(
context: AppComponentContext,
params: ManageTokensComponent.Params,
): DefaultManageTokensComponent
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.managetokens.di
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.component.impl.DefaultManageTokensComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindManageTokensComponentFactory(factory: DefaultManageTokensComponent.Factory): ManageTokensComponent.Factory
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.managetokens.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.managetokens.model.ManageTokensModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(ManageTokensModel::class)
fun provideManageTokensModel(model: ManageTokensModel): Model
}

View file

@ -0,0 +1,192 @@
package com.tangem.features.managetokens.model
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.navigation.Router
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.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.utils.coroutines.CoroutineDispatcherProvider
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
import javax.inject.Inject
@ComponentScoped
internal class ManageTokensModel @Inject constructor(
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val changedItemsIds: MutableSet<String> = mutableSetOf()
private var items = initItems()
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(
value = ManageTokensUM(
popBack = router::pop,
items = items,
search = SearchBarUM(
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
query = "",
onQueryChange = ::searchCurrencies,
isActive = false,
onActiveChange = ::toggleSearchBar,
),
hasChanges = false,
onSaveClick = {},
onAddCustomToken = {},
),
)
@Suppress("UnusedPrivateMember")
private fun searchCurrencies(query: String) {
// TODO: [REDACTED_JIRA]
}
private fun toggleSearchBar(isActive: Boolean) {
state.update { state ->
state.copy(
search = state.search.copy(isActive = isActive),
)
}
}
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
}
state.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)
}
state.update { state ->
items = items.mutate {
it[currencyIndex] = updatedItem
}
state.copy(
items = items,
hasChanges = changedItemsIds.isNotEmpty(),
)
}
}
}