Updated on 2026-08-14
This commit is contained in:
parent
3069b8d32d
commit
f5e14a9ab4
13 changed files with 179 additions and 166 deletions
|
|
@ -16,7 +16,7 @@ internal sealed class CommonSendRoute : Route {
|
|||
|
||||
@Serializable
|
||||
data object Confirm : CommonSendRoute() {
|
||||
override val isEditMode: Boolean = true
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,14 +1,23 @@
|
|||
package com.tangem.features.send.subcomponents.amount
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountComponent
|
||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.subcomponents.amount.model.SendAmountModel
|
||||
import com.tangem.features.send.subcomponents.amount.ui.SendAmountContent
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -24,6 +33,23 @@ internal class DefaultSendAmountComponent @AssistedInject constructor(
|
|||
|
||||
override fun updateState(amountUM: AmountState) = model.updateState(amountUM)
|
||||
|
||||
@Composable
|
||||
override fun Title() {
|
||||
AppBarWithBackButtonAndIcon(
|
||||
text = stringResourceSafe(R.string.send_amount_label),
|
||||
onBackClick = {
|
||||
params.callback.onBackClick(params.route)
|
||||
},
|
||||
backIconRes = if (params.route.isEditMode) {
|
||||
R.drawable.ic_back_24
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
modifier = Modifier.height(TangemTheme.dimens.size56),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -37,6 +63,30 @@ internal class DefaultSendAmountComponent @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Footer() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
PrimaryButton(
|
||||
text = if (params.route.isEditMode) {
|
||||
stringResourceSafe(R.string.common_continue)
|
||||
} else {
|
||||
stringResourceSafe(R.string.common_next)
|
||||
},
|
||||
enabled = state.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
model.onAmountNext()
|
||||
params.callback.onNextClick(params.route)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SendAmountComponent.Factory {
|
||||
override fun create(
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import com.tangem.common.ui.amountScreen.models.AmountParameters
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -29,14 +27,12 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
|||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.send.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.api.entity.isFromMainScreenQr
|
||||
import com.tangem.features.send.api.subcomponents.amount.AmountRoute
|
||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceListener
|
||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountUpdateListener
|
||||
import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
|
||||
import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents.SelectedCurrencyType
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.common.CommonSendRoute
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -84,7 +80,6 @@ internal class SendAmountModel @Inject constructor(
|
|||
private var maxAmountBoundary: EnterAmountBoundary by Delegates.notNull()
|
||||
|
||||
init {
|
||||
configAmountNavigation()
|
||||
initAppCurrency()
|
||||
subscribeOnCryptoCurrencyStatusFlow()
|
||||
subscribeOnAmountReduceByTriggerUpdates()
|
||||
|
|
@ -104,6 +99,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
},
|
||||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
setSendWithSwapAvailability()
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
|
|
@ -274,9 +270,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
override fun onConvertToAnotherToken() {
|
||||
val amountParams = params as? SendAmountComponentParams.AmountParams ?: return
|
||||
modelScope.launch {
|
||||
var isEditMode = false
|
||||
amountParams.currentRoute.collect { route -> isEditMode = route.isEditMode }
|
||||
if (isEditMode) {
|
||||
if (amountParams.route.isEditMode) {
|
||||
sendAmountAlertFactory.showResetSendingAlert {
|
||||
params.callback.resetSendNavigation()
|
||||
confirmConvertToToken()
|
||||
|
|
@ -363,44 +357,6 @@ internal class SendAmountModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun configAmountNavigation() {
|
||||
val params = params as? SendAmountComponentParams.AmountParams ?: return
|
||||
combine(
|
||||
flow = uiState,
|
||||
// Filter on the public AmountRoute interface (not the internal CommonSendRoute.Amount) so an
|
||||
// external host (e.g. staking) that supplies its own AmountRoute is not silently dropped here.
|
||||
flow2 = params.currentRoute.filterIsInstance<AmountRoute>(),
|
||||
transform = { state, route -> state to route },
|
||||
).onEach { (state, route) ->
|
||||
setSendWithSwapAvailability()
|
||||
params.callback.onNavigationResult(
|
||||
NavigationUM.Content(
|
||||
source = CommonSendRoute.Amount::class.java.simpleName,
|
||||
title = resourceReference(R.string.send_amount_label),
|
||||
subtitle = null,
|
||||
backIconRes = if (route.isEditMode) {
|
||||
R.drawable.ic_back_24
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backIconClick = params.callback::onBackClick,
|
||||
primaryButton = NavigationButton(
|
||||
textReference = if (route.isEditMode) {
|
||||
resourceReference(R.string.common_continue)
|
||||
} else {
|
||||
resourceReference(R.string.common_next)
|
||||
},
|
||||
isEnabled = state.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
onAmountNext()
|
||||
params.callback.onNextClick()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun setSendWithSwapAvailability() {
|
||||
// Allowed only in multicurrency wallets
|
||||
val isMultiCurrency = userWallet?.isMultiCurrency == true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
package com.tangem.features.send.subcomponents.destination
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
|
|
@ -14,9 +19,15 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
|
||||
import com.tangem.features.addressbook.AddressBookFeatureToggles
|
||||
import com.tangem.features.addressbook.AddressSelectorComponent
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.subcomponents.destination.model.SendDestinationModel
|
||||
import com.tangem.features.send.subcomponents.destination.ui.SendDestinationContent
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -68,6 +79,22 @@ internal class DefaultSendDestinationComponent @AssistedInject constructor(
|
|||
|
||||
override fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
|
||||
|
||||
@Composable
|
||||
override fun Title() {
|
||||
BackHandler(onBack = model::onBackClick)
|
||||
AppBarWithBackButtonAndIcon(
|
||||
text = params.title.resolveReference(),
|
||||
onBackClick = model::onBackClick,
|
||||
backIconRes = if (params.route.isEditMode) {
|
||||
R.drawable.ic_back_24
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
modifier = Modifier.height(TangemTheme.dimens.size56),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -83,6 +110,30 @@ internal class DefaultSendDestinationComponent @AssistedInject constructor(
|
|||
selector.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Footer() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
PrimaryButton(
|
||||
text = if (params.route.isEditMode) {
|
||||
stringResourceSafe(R.string.common_continue)
|
||||
} else {
|
||||
stringResourceSafe(R.string.common_next)
|
||||
},
|
||||
enabled = state.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
model.saveResult()
|
||||
params.callback.onNextClick(params.route)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SendDestinationComponent.Factory {
|
||||
override fun create(
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ import com.arkivanov.decompose.router.slot.activate
|
|||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.entity.AddressBookOpenMode
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetBackupProblematicWalletForAddressUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
|
|
@ -49,8 +46,6 @@ import com.tangem.features.send.api.entity.PredefinedValues
|
|||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.common.CommonSendRoute
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.subcomponents.destination.SendDestinationAlertFactory
|
||||
import com.tangem.features.send.subcomponents.destination.analytics.EnterAddressSource
|
||||
import com.tangem.features.send.subcomponents.destination.analytics.SendDestinationAnalyticEvents
|
||||
|
|
@ -142,7 +137,6 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private val backupProblematicWalletCache = AtomicReference<Pair<String, UserWalletId?>?>(null)
|
||||
|
||||
init {
|
||||
configDestinationNavigation()
|
||||
subscribeOnQRScannerResult()
|
||||
initialState()
|
||||
resetContactOnEdit()
|
||||
|
|
@ -153,15 +147,12 @@ internal class SendDestinationModel @Inject constructor(
|
|||
|
||||
private fun resetContactOnEdit() {
|
||||
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
|
||||
params.currentRoute
|
||||
.filter { it.isEditMode }
|
||||
.onEach {
|
||||
val content = uiState.value as? DestinationUM.Content ?: return@onEach
|
||||
if (content.addressTextField.contactName != null) {
|
||||
_uiState.update(SendDestinationContactTransformer(contact = null))
|
||||
}
|
||||
if (params.route.isEditMode) {
|
||||
val content = uiState.value as? DestinationUM.Content ?: return
|
||||
if (content.addressTextField.contactName != null) {
|
||||
_uiState.update(SendDestinationContactTransformer(contact = null))
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
}
|
||||
|
||||
fun onContactClick(contact: MatchedContact) {
|
||||
|
|
@ -259,7 +250,23 @@ internal class SendDestinationModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun saveResult() {
|
||||
fun onBackClick() {
|
||||
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
|
||||
if (!params.route.isEditMode) {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
source = SendScreenSource.Address,
|
||||
isFromSummary = false,
|
||||
isValid = uiState.value.isPrimaryButtonEnabled,
|
||||
),
|
||||
)
|
||||
saveResult()
|
||||
}
|
||||
params.callback.onBackClick(params.route)
|
||||
}
|
||||
|
||||
fun saveResult() {
|
||||
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
|
||||
params.callback.onDestinationResult(uiState.value)
|
||||
}
|
||||
|
|
@ -490,60 +497,10 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private fun autoNextFromRecipient(type: EnterAddressSource, isValidAddress: Boolean, isValidMemo: Boolean) {
|
||||
if (type.isAutoNext && isValidAddress && isValidMemo) {
|
||||
saveResult()
|
||||
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick()
|
||||
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick(params.route)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun configDestinationNavigation() {
|
||||
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
|
||||
combine(
|
||||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).onEach { (state, route) ->
|
||||
params.callback.onNavigationResult(
|
||||
NavigationUM.Content(
|
||||
source = CommonSendRoute.Destination::class.java.simpleName,
|
||||
title = params.title,
|
||||
subtitle = null,
|
||||
backIconRes = if (route.isEditMode) {
|
||||
R.drawable.ic_back_24
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backIconClick = {
|
||||
if (!route.isEditMode) {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
source = SendScreenSource.Address,
|
||||
isFromSummary = false,
|
||||
isValid = state.isPrimaryButtonEnabled,
|
||||
),
|
||||
)
|
||||
saveResult()
|
||||
}
|
||||
params.callback.onBackClick()
|
||||
},
|
||||
primaryButton = NavigationButton(
|
||||
textReference = if (route.isEditMode) {
|
||||
resourceReference(R.string.common_continue)
|
||||
} else {
|
||||
resourceReference(R.string.common_next)
|
||||
},
|
||||
isEnabled = state.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
saveResult()
|
||||
params.callback.onNextClick()
|
||||
},
|
||||
),
|
||||
secondaryPairButtonsUM = null,
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RECENT_TX_SIZE = 100
|
||||
const val RECENT_LOAD_DELAY = 500L
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue