Updated on 2026-08-14
This commit is contained in:
parent
6031a97ffe
commit
8d0f11d10b
67 changed files with 1527 additions and 41 deletions
|
|
@ -26,6 +26,10 @@ interface ExpressTransactionsComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val onRatingRequested: (
|
||||
(txExternalId: String, providerName: String, txExternalUrl: String, userWalletIdStringValue: String) -> Unit
|
||||
)? = null,
|
||||
val onRatingDismiss: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ExpressTransactionsComponent>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ dependencies {
|
|||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.features.rating.api)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.TransferBottomSheetComponent
|
||||
import com.tangem.features.rating.RatingComponent
|
||||
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
|
||||
import com.tangem.features.tokendetails.ExpressTransactionsComponent
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
|
|
@ -47,6 +48,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
|
||||
private val yieldSupplyWarningComponentFactory: YieldSupplyDepositedWarningComponent.Factory,
|
||||
yieldSupplyComponentFactory: YieldSupplyComponent.Factory,
|
||||
private val ratingComponentFactory: RatingComponent.Factory,
|
||||
) : TokenDetailsComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: TokenDetailsModel = getOrCreateModel(params)
|
||||
|
|
@ -64,6 +66,8 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
params = ExpressTransactionsComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.currency,
|
||||
onRatingRequested = model::activateRatingForExpressTx,
|
||||
onRatingDismiss = { model.ratingSlotNavigation.dismiss() },
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -74,6 +78,15 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
private val ratingSlot = childSlot(
|
||||
key = RATING_SLOT_KEY,
|
||||
source = model.ratingSlotNavigation,
|
||||
serializer = null,
|
||||
childFactory = { params, ctx ->
|
||||
ratingComponentFactory.create(childByContext(ctx), params)
|
||||
},
|
||||
)
|
||||
|
||||
private val tokenMarketBlockComponent = params.currency.toTokenMarketParam()?.let { tokenMarketParams ->
|
||||
tokenMarketBlockComponentFactory.create(
|
||||
appComponentContext = child("tokenMarketBlockComponent"),
|
||||
|
|
@ -94,11 +107,13 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
val ratingSlotState by ratingSlot.subscribeAsState()
|
||||
NavigationBar3ButtonsScrim()
|
||||
|
||||
if (LocalRedesignEnabled.current) {
|
||||
val tokenDetailsUM by model.redesignUiState.collectAsStateWithLifecycle()
|
||||
|
||||
// TODO [REDACTED_TASK_KEY]: wire ratingSlotState into TokenDetailsScreen when redesign is ready
|
||||
TokenDetailsScreen(
|
||||
tokenDetailsUM = tokenDetailsUM,
|
||||
tokenMarketBlockComponent = tokenMarketBlockComponent,
|
||||
|
|
@ -115,6 +130,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
txHistoryComponent = txHistoryComponent,
|
||||
yieldSupplyComponent = yieldSupplyComponent,
|
||||
expressTransactionsComponent = expressTransactionsComponent,
|
||||
ratingComponent = ratingSlotState.child?.instance,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -178,4 +194,8 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
params: TokenDetailsComponent.Params,
|
||||
): DefaultTokenDetailsComponent
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RATING_SLOT_KEY = "ratingSlot"
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
|||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.ExpressStateFactory
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory
|
||||
import com.tangem.features.tokendetails.ExpressTransactionsComponent
|
||||
import com.tangem.features.tokendetails.ExpressTransactionsEvent
|
||||
|
|
@ -111,6 +112,16 @@ internal class ExpressTransactionsModel @Inject constructor(
|
|||
val expressTxState = internalUiState.value.transactionsToDisplay.firstOrNull { it.info.txId == txId }
|
||||
?: return
|
||||
internalUiState.value = expressStatusFactory.getStateWithExpressStatusBottomSheet(expressTxState)
|
||||
if (expressTxState is ExchangeUM) {
|
||||
expressTxState.info.txExternalId?.let { txExternalId ->
|
||||
params.onRatingRequested?.invoke(
|
||||
txExternalId,
|
||||
expressTxState.provider.name,
|
||||
expressTxState.info.txExternalUrl.orEmpty(),
|
||||
expressTxState.fromUserWalletId.stringValue,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGoToProviderClick(url: String) {
|
||||
|
|
@ -159,6 +170,7 @@ internal class ExpressTransactionsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
params.onRatingDismiss?.invoke()
|
||||
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +182,7 @@ internal class ExpressTransactionsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
params.onRatingDismiss?.invoke()
|
||||
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,18 @@ import com.arkivanov.decompose.router.slot.dismiss
|
|||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.domain.dynamicaddresses.IsDynamicAddressesAvailableUseCase
|
||||
import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.bottomsheet.receive.AddressModel
|
||||
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
|
||||
import com.tangem.features.rating.RatingComponent
|
||||
import com.tangem.feature.swap.domain.SwapFeedbackUseCase
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
|
||||
|
|
@ -183,6 +190,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val redesignStateController: TokenDetailsStateController,
|
||||
private val swapFeedbackUseCase: SwapFeedbackUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) : Model(),
|
||||
TokenDetailsClickIntents,
|
||||
YieldSupplyDepositedWarningComponent.ModelCallback {
|
||||
|
|
@ -209,6 +218,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private var isBalanceLoadedEventSent = false
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TokenDetailsBottomSheetConfig> = SlotNavigation()
|
||||
val ratingSlotNavigation = SlotNavigation<RatingComponent.Params>()
|
||||
|
||||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState.value },
|
||||
|
|
@ -907,6 +917,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
state.copy(pullToRefreshConfig = state.pullToRefreshConfig.copy(isRefreshing = false))
|
||||
}
|
||||
}.saveIn(refreshStateJobHolder)
|
||||
ratingSlotNavigation.dismiss()
|
||||
}
|
||||
|
||||
override fun onCloseRentInfoNotification() {
|
||||
|
|
@ -1079,6 +1090,37 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
uiState.value = stateFactory.getStateWithUpdatedBalanceSegmentedButtonConfig(config)
|
||||
}
|
||||
|
||||
fun activateRatingForExpressTx(
|
||||
txExternalId: String,
|
||||
providerName: String,
|
||||
txExternalUrl: String,
|
||||
userWalletIdStringValue: String,
|
||||
) {
|
||||
if (!swapFeatureToggles.isSwapRateExperienceEnabled) return
|
||||
ratingSlotNavigation.activate(
|
||||
RatingComponent.Params(
|
||||
onLoadRating = {
|
||||
swapFeedbackUseCase.getExistingRating(txExternalId)
|
||||
.fold(ifLeft = { null }, ifRight = { it?.rating })
|
||||
},
|
||||
onSubmitRating = { rating, feedback ->
|
||||
swapFeedbackUseCase.submit(
|
||||
SwapFeedbackParams(
|
||||
userWalletIdHash = userWalletIdStringValue.hexToBytes()
|
||||
.calculateSha256()
|
||||
.toHexString(),
|
||||
providerName = providerName,
|
||||
txUrl = txExternalUrl,
|
||||
txExternalId = txExternalId,
|
||||
rating = rating,
|
||||
feedback = feedback,
|
||||
),
|
||||
).onLeft { TangemLogger.e("Failed to submit swap feedback: $it") }
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onYieldInfoClick() {
|
||||
analyticsEventsHandler.send(
|
||||
YieldSupplyAnalytics.EarnedFundsInfo(
|
||||
|
|
|
|||
|
|
@ -207,9 +207,12 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun TangemBottomSheetConfig.toBottomSheetSlot(): BottomSheetSlot {
|
||||
val contentLambda: @Composable () -> Unit = {
|
||||
val contentLambda: @Composable ((@Composable () -> Unit)?) -> Unit = { extraContent ->
|
||||
when (this.content) {
|
||||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = this)
|
||||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(
|
||||
config = this,
|
||||
extraContent = extraContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
return BottomSheetSlot(config = this, content = contentLambda)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ internal fun TokenDetailsScreen(
|
|||
)
|
||||
}
|
||||
|
||||
expressState.bottomSheetSlot?.content()
|
||||
expressState.bottomSheetSlot?.content(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
|
||||
import com.tangem.features.rating.RatingComponent
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
|
|
@ -43,7 +44,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
// TODO: Split to blocks [REDACTED_JIRA]
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod", "LongParameterList")
|
||||
@Composable
|
||||
internal fun TokenDetailsScreenLegacy(
|
||||
state: TokenDetailsState,
|
||||
|
|
@ -51,6 +52,7 @@ internal fun TokenDetailsScreenLegacy(
|
|||
txHistoryComponent: TxHistoryComponent,
|
||||
yieldSupplyComponent: YieldSupplyComponent,
|
||||
expressTransactionsComponent: ExpressTransactionsComponent,
|
||||
ratingComponent: RatingComponent?,
|
||||
) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
|
|
@ -164,7 +166,9 @@ internal fun TokenDetailsScreenLegacy(
|
|||
}
|
||||
}
|
||||
|
||||
expressState.bottomSheetSlot?.content()
|
||||
expressState.bottomSheetSlot?.content(
|
||||
ratingComponent?.let { comp -> { comp.Content(modifier = Modifier.fillMaxWidth()) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +202,7 @@ private fun TokenDetailsScreenPreview(
|
|||
}
|
||||
},
|
||||
expressTransactionsComponent = PreviewExpressTransactionsComponent,
|
||||
ratingComponent = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,17 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.E
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetContent
|
||||
|
||||
@Composable
|
||||
internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) {
|
||||
internal fun ExpressStatusBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: ExpressStatusBottomSheetConfig ->
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(state)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(state = state, extraContent = extraContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
|
||||
@Composable
|
||||
internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) {
|
||||
internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (@Composable () -> Unit)? = null) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
|
|
@ -70,6 +70,10 @@ internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) {
|
|||
imageUrl = state.provider.imageLarge,
|
||||
)
|
||||
SpacerH12()
|
||||
if (extraContent != null) {
|
||||
extraContent()
|
||||
SpacerH12()
|
||||
}
|
||||
ExchangeStatusBlock(
|
||||
statuses = state.statuses,
|
||||
showLink = state.showProviderLink,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue