Updated on 2026-08-14
This commit is contained in:
commit
1cecd561dc
16 changed files with 741 additions and 15 deletions
|
|
@ -62,7 +62,7 @@ fun MessageBottomSheetV2(state: MessageBottomSheetUMV2, onDismissRequest: () ->
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
|
||||
fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
state.elements.fastForEach {
|
||||
when (it) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ android {
|
|||
dependencies {
|
||||
/* Project - API */
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.swapV2.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
|
|||
|
|
@ -7,20 +7,27 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||
import com.tangem.features.managetokens.choosetoken.ui.ChooseManagedTokenContent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkTrigger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: ChooseManagedTokensComponent.Params,
|
||||
private val swapChooseTokenNetworkFactory: SwapChooseTokenNetworkComponent.Factory,
|
||||
private val swapChooseTokenNetworkTrigger: SwapChooseTokenNetworkTrigger,
|
||||
) : ChooseManagedTokensComponent, AppComponentContext by context {
|
||||
|
||||
private val model: ChooseManagedTokensModel = getOrCreateModel(params)
|
||||
|
|
@ -48,18 +55,21 @@ internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
|||
config: ChooseManageTokensBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
else -> getStubComponent()
|
||||
}
|
||||
|
||||
private fun getStubComponent() = StubComponent()
|
||||
|
||||
class StubComponent : ComposableBottomSheetComponent {
|
||||
override fun dismiss() {}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
/* no-op */
|
||||
}
|
||||
is ChooseManageTokensBottomSheetConfig.SwapTokensBottomSheetConfig -> swapChooseTokenNetworkFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SwapChooseTokenNetworkComponent.Params(
|
||||
userWalletId = config.userWalletId,
|
||||
initialCurrency = config.initialCurrency,
|
||||
token = config.token,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
onResult = {
|
||||
componentScope.launch {
|
||||
swapChooseTokenNetworkTrigger.trigger(it)
|
||||
}
|
||||
router.pop()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.pagination.BatchFetchResult
|
|||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -142,7 +143,11 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(items = items),
|
||||
readContent = state.readContent.copy(
|
||||
items = items.filterNot {
|
||||
it.id.value == params.initialCurrency.id.rawCurrencyId?.value
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.swap.v2.api.choosetoken
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface SwapChooseTokenNetworkComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val initialCurrency: CryptoCurrency,
|
||||
val token: ManagedCryptoCurrency.Token,
|
||||
val onDismiss: () -> Unit,
|
||||
val onResult: (CryptoCurrency) -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SwapChooseTokenNetworkComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.swap.v2.api.choosetoken
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Triggers swap token selection
|
||||
*/
|
||||
interface SwapChooseTokenNetworkTrigger {
|
||||
|
||||
suspend fun trigger(cryptoCurrency: CryptoCurrency)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkTrigger
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.DefaultSwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.DefaultSwapChooseTokenNetworkTrigger
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.SwapChooseTokenNetworkListener
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenNetworkModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(ModelComponent::class)
|
||||
@Module
|
||||
internal interface SwapChooseTokenModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SwapChooseTokenNetworkModel::class)
|
||||
fun provideSwapChooseTokenNetworkModel(impl: SwapChooseTokenNetworkModel): Model
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface SwapChooseTokenModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideSwapChooseTokenNetworksBottomSheetComponent(
|
||||
impl: DefaultSwapChooseTokenNetworkComponent.Factory,
|
||||
): SwapChooseTokenNetworkComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSwapChooseTokenNetworkTrigger(impl: DefaultSwapChooseTokenNetworkTrigger): SwapChooseTokenNetworkTrigger
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSwapChooseTokenNetworkListener(impl: DefaultSwapChooseTokenNetworkTrigger): SwapChooseTokenNetworkListener
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenNetworkModel
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.ui.SwapChooseTokenNetworkBottomSheet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultSwapChooseTokenNetworkComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: SwapChooseTokenNetworkComponent.Params,
|
||||
) : SwapChooseTokenNetworkComponent, AppComponentContext by context {
|
||||
|
||||
private val model: SwapChooseTokenNetworkModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
SwapChooseTokenNetworkBottomSheet(
|
||||
config = state.bottomSheetConfig,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SwapChooseTokenNetworkComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SwapChooseTokenNetworkComponent.Params,
|
||||
): DefaultSwapChooseTokenNetworkComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkTrigger
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Listens to swap token selection
|
||||
*/
|
||||
interface SwapChooseTokenNetworkListener {
|
||||
val swapChooseTokenNetworkResultFlow: SharedFlow<CryptoCurrency>
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class DefaultSwapChooseTokenNetworkTrigger @Inject constructor() :
|
||||
SwapChooseTokenNetworkTrigger,
|
||||
SwapChooseTokenNetworkListener {
|
||||
|
||||
override val swapChooseTokenNetworkResultFlow: SharedFlow<CryptoCurrency>
|
||||
field = MutableSharedFlow<CryptoCurrency>()
|
||||
|
||||
override suspend fun trigger(result: CryptoCurrency) {
|
||||
swapChooseTokenNetworkResultFlow.emit(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class SwapChooseTokenNetworkUM(
|
||||
val bottomSheetConfig: TangemBottomSheetConfig,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed class SwapChooseTokenNetworkContentUM : TangemBottomSheetConfigContent {
|
||||
|
||||
abstract val messageContent: MessageBottomSheetUMV2
|
||||
|
||||
data class Loading(
|
||||
override val messageContent: MessageBottomSheetUMV2,
|
||||
) : SwapChooseTokenNetworkContentUM()
|
||||
|
||||
data class Error(
|
||||
override val messageContent: MessageBottomSheetUMV2,
|
||||
) : SwapChooseTokenNetworkContentUM()
|
||||
|
||||
data class Content(
|
||||
override val messageContent: MessageBottomSheetUMV2,
|
||||
val swapNetworks: ImmutableList<SwapChooseNetworkUM>,
|
||||
) : SwapChooseTokenNetworkContentUM()
|
||||
}
|
||||
|
||||
internal data class SwapChooseNetworkUM(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
@DrawableRes val iconResId: Int,
|
||||
val hasFixedRate: Boolean,
|
||||
val onNetworkClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
|
||||
internal object SwapChooseTokenFactory {
|
||||
|
||||
fun getErrorMessage(tokenName: String, onDismiss: () -> Unit): MessageBottomSheetUMV2 {
|
||||
return messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = resourceReference(R.string.express_swap_not_supported_title, wrappedList(tokenName))
|
||||
body = resourceReference(R.string.express_swap_not_supported_text)
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { onDismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase
|
||||
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory.getErrorMessage
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.transformers.SwapChooseContentStateTransformer
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.transformers.SwapChooseErrorStateTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class SwapChooseTokenNetworkModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val getSwapSupportedPairsUseCase: GetSwapSupportedPairsUseCase,
|
||||
private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
private val params: SwapChooseTokenNetworkComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<SwapChooseTokenNetworkUM>
|
||||
field: MutableStateFlow<SwapChooseTokenNetworkUM> = MutableStateFlow(
|
||||
SwapChooseTokenNetworkUM(
|
||||
TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = params.onDismiss,
|
||||
content = SwapChooseTokenNetworkContentUM.Loading(
|
||||
messageContent = getErrorMessage(
|
||||
tokenName = params.token.name,
|
||||
onDismiss = params.onDismiss,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
initContent()
|
||||
}
|
||||
|
||||
private fun initContent() {
|
||||
val userWallet = getUserWalletUseCase(params.userWalletId).getOrElse {
|
||||
Timber.e("Failed to get user wallet: $it")
|
||||
getGenericErrorState()
|
||||
return
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
val cryptoCurrencyList = createCryptoCurrencyUseCase(
|
||||
token = params.token,
|
||||
userWalletId = params.userWalletId,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to get crypto currency")
|
||||
getGenericErrorState()
|
||||
return@launch
|
||||
}
|
||||
val pairs = getSwapSupportedPairsUseCase.invoke(
|
||||
userWallet = userWallet,
|
||||
initialCurrency = params.initialCurrency,
|
||||
cryptoCurrencyList = cryptoCurrencyList + params.initialCurrency,
|
||||
).getOrElse {
|
||||
Timber.e(it.toString())
|
||||
uiState.update(
|
||||
SwapChooseErrorStateTransformer(
|
||||
tokenName = params.token.name,
|
||||
onDismiss = params.onDismiss,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
delay(MINIMUM_LOADING_TIME)
|
||||
uiState.update(
|
||||
SwapChooseContentStateTransformer(
|
||||
pairs = pairs,
|
||||
onNetworkClick = params.onResult,
|
||||
tokenName = params.token.name,
|
||||
onDismiss = params.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGenericErrorState() {
|
||||
uiMessageSender.send(
|
||||
DialogMessage(
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
message = resourceReference(id = R.string.common_unknown_error),
|
||||
onDismissRequest = params.onDismiss,
|
||||
firstActionBuilder = { okAction() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MINIMUM_LOADING_TIME = 100L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.iconResId
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseNetworkUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory.getErrorMessage
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SwapChooseContentStateTransformer(
|
||||
private val pairs: SwapCurrencies,
|
||||
private val tokenName: String,
|
||||
private val onNetworkClick: (CryptoCurrency) -> Unit,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<SwapChooseTokenNetworkUM> {
|
||||
override fun transform(prevState: SwapChooseTokenNetworkUM): SwapChooseTokenNetworkUM {
|
||||
val swapNetworks = pairs.fromGroup.available.map {
|
||||
val network = it.currencyStatus.currency.network
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference(network.name),
|
||||
subtitle = stringReference(network.standardType.name),
|
||||
iconResId = network.iconResId,
|
||||
hasFixedRate = it.providers.any { it.rateTypes.any { it == ExpressRateType.Fixed } },
|
||||
onNetworkClick = { onNetworkClick(it.currencyStatus.currency) },
|
||||
)
|
||||
}.toPersistentList()
|
||||
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig.copy(
|
||||
content = if (swapNetworks.isNotEmpty()) {
|
||||
SwapChooseTokenNetworkContentUM.Content(
|
||||
swapNetworks = swapNetworks,
|
||||
messageContent = getErrorMessage(
|
||||
tokenName = tokenName,
|
||||
onDismiss = onDismiss,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
SwapChooseTokenNetworkContentUM.Error(
|
||||
messageContent = getErrorMessage(
|
||||
tokenName = tokenName,
|
||||
onDismiss = onDismiss,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.transformers
|
||||
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model.SwapChooseTokenFactory.getErrorMessage
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SwapChooseErrorStateTransformer(
|
||||
private val tokenName: String,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<SwapChooseTokenNetworkUM> {
|
||||
override fun transform(prevState: SwapChooseTokenNetworkUM): SwapChooseTokenNetworkUM {
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig.copy(
|
||||
content = SwapChooseTokenNetworkContentUM.Error(
|
||||
messageContent = getErrorMessage(
|
||||
tokenName = tokenName,
|
||||
onDismiss = onDismiss,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
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.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseNetworkUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
internal fun SwapChooseTokenNetworkBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemModalBottomSheet<SwapChooseTokenNetworkContentUM>(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
title = {
|
||||
AnimatedContent(
|
||||
targetState = config.content is SwapChooseTokenNetworkContentUM.Content,
|
||||
) { isContent ->
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_choose_network).takeIf { isContent },
|
||||
endIconRes = R.drawable.ic_close_24,
|
||||
onEndClick = config.onDismissRequest,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { contentConfig ->
|
||||
SwapChooseTokenNetworkContent(contentConfig)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SwapChooseTokenNetworkContent(state: SwapChooseTokenNetworkContentUM) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
|
||||
) { targetState ->
|
||||
when (targetState) {
|
||||
is SwapChooseTokenNetworkContentUM.Content -> SwapChooseTokenNetworkContentList(targetState.swapNetworks)
|
||||
else -> {
|
||||
Box {
|
||||
MessageBottomSheetV2Content(
|
||||
state = targetState.messageContent,
|
||||
modifier = Modifier
|
||||
.conditional(targetState is SwapChooseTokenNetworkContentUM.Loading) {
|
||||
alpha(0f)
|
||||
},
|
||||
)
|
||||
if (targetState is SwapChooseTokenNetworkContentUM.Loading) {
|
||||
CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.inactive,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapChooseTokenNetworkContentList(swapNetworks: ImmutableList<SwapChooseNetworkUM>) {
|
||||
Column {
|
||||
swapNetworks.fastForEach { network ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
onClick = { network.onNetworkClick() },
|
||||
)
|
||||
.padding(
|
||||
vertical = 12.dp,
|
||||
horizontal = 14.dp,
|
||||
),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(36.dp),
|
||||
painter = painterResource(id = network.iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = network.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = network.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
if (network.hasFixedRate) {
|
||||
SpacerWMax()
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.swap_fixed_rate),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
)
|
||||
.padding(
|
||||
vertical = 2.dp,
|
||||
horizontal = 4.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun SwapChooseTokenNetworkContent_Preview(
|
||||
@PreviewParameter(PreviewProvider::class) params: SwapChooseTokenNetworkContentUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
SwapChooseTokenNetworkContent(state = params)
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewProvider : PreviewParameterProvider<SwapChooseTokenNetworkContentUM> {
|
||||
override val values: Sequence<SwapChooseTokenNetworkContentUM>
|
||||
get() = sequenceOf(
|
||||
SwapChooseTokenNetworkContentUM.Loading(
|
||||
messageContent = messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.ic_alert_triangle_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Unspecified
|
||||
}
|
||||
title = stringReference("Shiba Inu is not suppoerted")
|
||||
body = stringReference("This token is not supported. Please choose a different token to swap.")
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
},
|
||||
),
|
||||
SwapChooseTokenNetworkContentUM.Error(
|
||||
messageContent = messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.ic_alert_triangle_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = stringReference("Shiba Inu is not supported")
|
||||
body = stringReference("This token is not supported. Please choose a different token to swap.")
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
},
|
||||
),
|
||||
SwapChooseTokenNetworkContentUM.Content(
|
||||
messageContent = messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = stringReference("Shiba Inu is not suppoerted")
|
||||
body = stringReference("This token is not supported. Please choose a different token to swap.")
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
},
|
||||
swapNetworks = persistentListOf(
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("Cardano"),
|
||||
subtitle = stringReference("MAIN"),
|
||||
iconResId = R.drawable.img_cardano_22,
|
||||
hasFixedRate = false,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
SwapChooseTokenNetworkContentUM.Content(
|
||||
messageContent = messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = stringReference("Shiba Inu is not suppoerted")
|
||||
body = stringReference("This token is not supported. Please choose a different token to swap.")
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
},
|
||||
swapNetworks = persistentListOf(
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("Cardano"),
|
||||
subtitle = stringReference("MAIN"),
|
||||
iconResId = R.drawable.img_cardano_22,
|
||||
hasFixedRate = false,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("BNB Smart Chain"),
|
||||
subtitle = stringReference("BEP20"),
|
||||
iconResId = R.drawable.img_bsc_22,
|
||||
hasFixedRate = false,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("BNB Smart Chain"),
|
||||
subtitle = stringReference("BEP20"),
|
||||
iconResId = R.drawable.img_bsc_22,
|
||||
hasFixedRate = true,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("BNB Smart Chain"),
|
||||
subtitle = stringReference("BEP20"),
|
||||
iconResId = R.drawable.img_bsc_22,
|
||||
hasFixedRate = false,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
SwapChooseTokenNetworkContentUM.Content(
|
||||
messageContent = messageBottomSheetUM {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Attention
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = stringReference("Shiba Inu is not suppoerted")
|
||||
body = stringReference("This token is not supported. Please choose a different token to swap.")
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.warning_button_ok)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
},
|
||||
swapNetworks = buildList {
|
||||
repeat(10) {
|
||||
add(
|
||||
SwapChooseNetworkUM(
|
||||
title = stringReference("Cardano"),
|
||||
subtitle = stringReference("MAIN"),
|
||||
iconResId = R.drawable.img_cardano_22,
|
||||
hasFixedRate = false,
|
||||
onNetworkClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -60,7 +60,7 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
|
|||
},
|
||||
topBar = {
|
||||
ExpandableSearchView(
|
||||
title = stringResourceSafe(R.string.swapping_token_list_title),
|
||||
title = stringResourceSafe(R.string.common_choose_token),
|
||||
onBackClick = onBack,
|
||||
placeholderSearchText = stringResourceSafe(id = R.string.common_search_tokens),
|
||||
onSearchChange = state.onSearchEntered,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue