Updated on 2026-08-14
This commit is contained in:
parent
edc5a0cf13
commit
aa6ddb9428
13 changed files with 461 additions and 0 deletions
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.onramp.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Swap select tokens component
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SwapSelectTokensComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Params, SwapSelectTokensComponent>
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
*/
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.features.onramp.swap
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.onramp.entity.OnrampOperation
|
||||
import com.tangem.features.onramp.swap.model.SwapSelectTokensModel
|
||||
import com.tangem.features.onramp.swap.ui.SwapSelectTokens
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Stable
|
||||
internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
|
||||
tokenListComponentFactory: OnrampTokenListComponent.Factory,
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: SwapSelectTokensComponent.Params,
|
||||
) : AppComponentContext by appComponentContext, SwapSelectTokensComponent {
|
||||
|
||||
private val model: SwapSelectTokensModel = getOrCreateModel()
|
||||
|
||||
private val selectFromTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create(
|
||||
context = child(key = "select_from_token_list"),
|
||||
params = OnrampTokenListComponent.Params(
|
||||
filterOperation = OnrampOperation.SWAP,
|
||||
hasSearchBar = true,
|
||||
userWalletId = params.userWalletId,
|
||||
onTokenClick = model::selectFromToken,
|
||||
),
|
||||
)
|
||||
|
||||
private val selectToTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create(
|
||||
context = child(key = "select_to_token_list"),
|
||||
params = OnrampTokenListComponent.Params(
|
||||
filterOperation = OnrampOperation.SWAP,
|
||||
hasSearchBar = true,
|
||||
userWalletId = params.userWalletId,
|
||||
onTokenClick = model::selectToToken,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state = model.state.collectAsStateWithLifecycle()
|
||||
|
||||
SwapSelectTokens(
|
||||
state = state.value,
|
||||
selectFromTokenListComponent = selectFromTokenListComponent,
|
||||
selectToTokenListComponent = selectToTokenListComponent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SwapSelectTokensComponent.Factory {
|
||||
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SwapSelectTokensComponent.Params,
|
||||
): DefaultSwapSelectTokensComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.swap.di
|
||||
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.onramp.swap.DefaultSwapSelectTokensComponent
|
||||
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 SwapSelectTokensComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSwapSelectTokensComponentFactory(
|
||||
factory: DefaultSwapSelectTokensComponent.Factory,
|
||||
): SwapSelectTokensComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.swap.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.swap.model.SwapSelectTokensModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface SwapSelectTokensModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SwapSelectTokensModel::class)
|
||||
fun bindOnrampTokenListModel(model: SwapSelectTokensModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.onramp.swap.entity
|
||||
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* [SwapSelectTokensUM] controller
|
||||
*
|
||||
* @property router router
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SwapSelectTokensController @Inject constructor(
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
val state: StateFlow<SwapSelectTokensUM> get() = _state
|
||||
|
||||
private val _state: MutableStateFlow<SwapSelectTokensUM> = MutableStateFlow(
|
||||
value = SwapSelectTokensUM(
|
||||
onBackClick = router::pop,
|
||||
exchangeFrom = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)),
|
||||
exchangeTo = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_to_title)),
|
||||
),
|
||||
)
|
||||
|
||||
fun update(transform: (SwapSelectTokensUM) -> SwapSelectTokensUM) {
|
||||
Timber.d("Applying non-name transformation")
|
||||
_state.update(transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SwapSelectTokensUMTransformer) {
|
||||
Timber.d("Applying ${transformer::class.simpleName}")
|
||||
_state.update(transformer::transform)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.onramp.swap.entity
|
||||
|
||||
/**
|
||||
* Swap select tokens UI model
|
||||
*
|
||||
* @property onBackClick callback is called when back button is clicked
|
||||
* @property exchangeFrom exchange "from" card UI model
|
||||
* @property exchangeTo exchange "to" card UI model
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal data class SwapSelectTokensUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val exchangeFrom: ExchangeCardUM,
|
||||
val exchangeTo: ExchangeCardUM,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.onramp.swap.entity
|
||||
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
/**
|
||||
* Base [SwapSelectTokensUM] transformer
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface SwapSelectTokensUMTransformer : Transformer<SwapSelectTokensUM>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.swap.entity.transformer
|
||||
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer
|
||||
|
||||
/**
|
||||
* Transformer for removing selected "from" token
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object RemoveSelectedFromTokenTransformer : SwapSelectTokensUMTransformer {
|
||||
|
||||
override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM {
|
||||
return prevState.copy(
|
||||
exchangeFrom = ExchangeCardUM.Empty(titleReference = prevState.exchangeFrom.titleReference),
|
||||
exchangeTo = ExchangeCardUM.Empty(titleReference = prevState.exchangeTo.titleReference),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.onramp.swap.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer
|
||||
import com.tangem.features.onramp.swap.entity.utils.toFilled
|
||||
|
||||
/**
|
||||
* Transformer for selecting "from" token
|
||||
*
|
||||
* @property selectedTokenItemState token item state
|
||||
* @property onRemoveClick callback is called when remove button is clicked
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SelectFromTokenTransformer(
|
||||
private val selectedTokenItemState: TokenItemState,
|
||||
private val onRemoveClick: () -> Unit,
|
||||
) : SwapSelectTokensUMTransformer {
|
||||
|
||||
override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM {
|
||||
return prevState.copy(
|
||||
exchangeFrom = prevState.exchangeFrom.toFilled(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
removeButtonUM = ExchangeCardUM.RemoveButtonUM(onClick = onRemoveClick),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.onramp.swap.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer
|
||||
import com.tangem.features.onramp.swap.entity.utils.toFilled
|
||||
|
||||
/**
|
||||
* Transformer for selecting "to" token
|
||||
*
|
||||
* @property selectedTokenItemState token item state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SelectToTokenTransformer(
|
||||
private val selectedTokenItemState: TokenItemState,
|
||||
) : SwapSelectTokensUMTransformer {
|
||||
|
||||
override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM {
|
||||
return prevState.copy(
|
||||
exchangeFrom = prevState.exchangeFrom.hideRemoveButton(),
|
||||
exchangeTo = prevState.exchangeTo.toFilled(selectedTokenItemState = selectedTokenItemState),
|
||||
)
|
||||
}
|
||||
|
||||
private fun ExchangeCardUM.hideRemoveButton(): ExchangeCardUM {
|
||||
return (this as? ExchangeCardUM.Filled)?.copy(removeButtonUM = null) ?: this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.features.onramp.swap.entity.utils
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
|
||||
/**
|
||||
* Convert from [ExchangeCardUM] to [ExchangeCardUM.Filled]
|
||||
*
|
||||
* @param selectedTokenItemState token item state
|
||||
* @param removeButtonUM remove button UI model
|
||||
*/
|
||||
internal fun ExchangeCardUM.toFilled(
|
||||
selectedTokenItemState: TokenItemState,
|
||||
removeButtonUM: ExchangeCardUM.RemoveButtonUM? = null,
|
||||
): ExchangeCardUM.Filled {
|
||||
return ExchangeCardUM.Filled(
|
||||
titleReference = titleReference,
|
||||
tokenItemState = selectedTokenItemState,
|
||||
removeButtonUM = removeButtonUM,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.features.onramp.swap.model
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensController
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromTokenTransformer
|
||||
import com.tangem.features.onramp.swap.entity.transformer.SelectFromTokenTransformer
|
||||
import com.tangem.features.onramp.swap.entity.transformer.SelectToTokenTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SwapSelectTokensModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val controller: SwapSelectTokensController,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<SwapSelectTokensUM> = controller.state
|
||||
|
||||
private var fromCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var toCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
/**
|
||||
* Select "from" token
|
||||
*
|
||||
* @param selectedTokenItemState selected token item state
|
||||
* @param status crypto currency status
|
||||
*/
|
||||
fun selectFromToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) {
|
||||
fromCurrencyStatus = status
|
||||
|
||||
controller.update(
|
||||
transformer = SelectFromTokenTransformer(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
onRemoveClick = ::onRemoveClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Select "to" token
|
||||
*
|
||||
* @param selectedTokenItemState selected token item state
|
||||
* @param status crypto currency status
|
||||
*/
|
||||
fun selectToToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) {
|
||||
toCurrencyStatus = status
|
||||
|
||||
controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState))
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
private fun onRemoveClick() {
|
||||
fromCurrencyStatus = null
|
||||
|
||||
controller.update(transformer = RemoveSelectedFromTokenTransformer)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.features.onramp.swap.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
|
||||
/**
|
||||
* Swap select tokens
|
||||
*
|
||||
* @param state state
|
||||
* @param selectFromTokenListComponent select "from" token list component
|
||||
* @param selectToTokenListComponent select "to" token list component
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun SwapSelectTokens(
|
||||
state: SwapSelectTokensUM,
|
||||
selectFromTokenListComponent: OnrampTokenListComponent,
|
||||
selectToTokenListComponent: OnrampTokenListComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = state.onBackClick)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
contentPadding = PaddingValues(bottom = 8.dp),
|
||||
) {
|
||||
stickyHeader(key = "header") {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = state.onBackClick,
|
||||
text = stringResource(id = R.string.common_swap),
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "exchange_from", contentType = "exchange_from") {
|
||||
ExchangeCard(
|
||||
state = state.exchangeFrom,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp, bottom = 12.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
|
||||
if (state.exchangeFrom is ExchangeCardUM.Empty) {
|
||||
item(key = "select_from", contentType = "select_from") {
|
||||
selectFromTokenListComponent.Content(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.exchangeFrom is ExchangeCardUM.Filled) {
|
||||
item(key = "exchange_to", contentType = "exchange_to") {
|
||||
ExchangeCard(
|
||||
state = state.exchangeTo,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 12.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
|
||||
if (state.exchangeTo is ExchangeCardUM.Empty) {
|
||||
item(key = "select_to", contentType = "select_to") {
|
||||
selectToTokenListComponent.Content(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue