Updated on 2026-08-14
This commit is contained in:
commit
d16c443ee2
55 changed files with 1581 additions and 67 deletions
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -48,9 +50,50 @@ internal object OnrampDomainModule {
|
|||
return CheckOnrampAvailabilityUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampStatusUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
): GetOnrampStatusUseCase {
|
||||
return GetOnrampStatusUseCase(onrampRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase {
|
||||
return GetOnrampCurrencyUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampTransactionsUseCase(
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
): GetOnrampTransactionsUseCase {
|
||||
return GetOnrampTransactionsUseCase(onrampTransactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampTransactionUseCase(
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
): GetOnrampTransactionUseCase {
|
||||
return GetOnrampTransactionUseCase(onrampTransactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampRemoveTransactionUseCase(
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
): OnrampRemoveTransactionUseCase {
|
||||
return OnrampRemoveTransactionUseCase(onrampTransactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampSaveTransactionUseCase(
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
): OnrampSaveTransactionUseCase {
|
||||
return OnrampSaveTransactionUseCase(onrampTransactionRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,7 @@ import com.tangem.features.managetokens.component.ManageTokensComponent
|
|||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
||||
import com.tangem.features.onramp.component.BuyCryptoComponent
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.component.SellCryptoComponent
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.onramp.component.*
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
|
@ -52,6 +49,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||
private val onrampComponentFactory: OnrampComponent.Factory,
|
||||
private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory,
|
||||
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
|
||||
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
||||
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||
|
|
@ -203,6 +201,13 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = onrampComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.OnrampSuccess -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = OnrampSuccessComponent.Params(route.txId),
|
||||
componentFactory = onrampSuccessComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.BuyCrypto -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
|
|
|
|||
|
|
@ -299,6 +299,13 @@ sealed class AppRoute(val path: String) : Route {
|
|||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class OnrampSuccess(
|
||||
val txId: String,
|
||||
) : AppRoute(path = "/onramp/success/$txId"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BuyCrypto(
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -31,10 +31,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -118,7 +115,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
|
|||
@Composable
|
||||
private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) {
|
||||
FooterContainer(
|
||||
footer = stringResource(id = R.string.give_permission_policy_type_footer),
|
||||
footer = resourceReference(R.string.give_permission_policy_type_footer),
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
AmountItem(
|
||||
|
|
@ -130,7 +127,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest)
|
|||
}
|
||||
SpacerH16()
|
||||
FooterContainer(
|
||||
footer = data.footerText.resolveReference(),
|
||||
footer = data.footerText,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
FeeItem(fee = data.fee)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
package com.tangem.common.ui.expressStatus
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
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.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Block with express statuses
|
||||
*
|
||||
* @param state ui holder
|
||||
* @param modifier modifier
|
||||
* @see [Figma](https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=18459-26521&t=4jox7bfqUiXnm2h1-4)
|
||||
*/
|
||||
@Composable
|
||||
fun ExpressStatusBlock(state: ExpressStatusUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
Text(
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
SpacerWMax()
|
||||
AnimatedVisibility(visible = state.link is ExpressLinkUM.Content) {
|
||||
val link = remember(this) { state.link as ExpressLinkUM.Content }
|
||||
Row(
|
||||
modifier = Modifier.clickable { link.onClick() },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = link.icon),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.spacing16)
|
||||
.padding(end = TangemTheme.dimens.spacing2),
|
||||
)
|
||||
Text(
|
||||
text = link.text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
state.statuses.forEachIndexed { index, item ->
|
||||
ExpressStatusStep(item, index == state.statuses.lastIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressStatusStep(status: ExpressStatusItemUM, isLast: Boolean) {
|
||||
AnimatedContent(
|
||||
targetState = status,
|
||||
label = "Exchange Step Change Success",
|
||||
transitionSpec = {
|
||||
fadeIn(tween(durationMillis = 220)) togetherWith
|
||||
fadeOut(tween(durationMillis = 220))
|
||||
},
|
||||
) { content ->
|
||||
Row {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
when (content.state) {
|
||||
ExpressStatusItemState.Active -> StepInProgress()
|
||||
ExpressStatusItemState.Default -> StepDefault()
|
||||
ExpressStatusItemState.Done -> Step(
|
||||
iconRes = R.drawable.ic_check_24,
|
||||
iconColor = TangemTheme.colors.icon.primary1,
|
||||
borderColor = TangemTheme.colors.field.focused,
|
||||
)
|
||||
ExpressStatusItemState.Error -> Step(
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
iconColor = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
ExpressStatusItemState.Warning -> Step(
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
iconColor = TangemTheme.colors.icon.attention,
|
||||
)
|
||||
}
|
||||
if (!isLast) {
|
||||
StepSeparator()
|
||||
}
|
||||
}
|
||||
val textColor = when (status.state) {
|
||||
ExpressStatusItemState.Active -> TangemTheme.colors.text.primary1
|
||||
ExpressStatusItemState.Default -> TangemTheme.colors.text.disabled
|
||||
ExpressStatusItemState.Done -> TangemTheme.colors.text.primary1
|
||||
ExpressStatusItemState.Error -> TangemTheme.colors.text.warning
|
||||
ExpressStatusItemState.Warning -> TangemTheme.colors.text.attention
|
||||
}
|
||||
Text(
|
||||
text = content.text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StepDefault() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.border(
|
||||
width = TangemTheme.dimens.size1_5,
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing2),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Step(iconColor: Color, @DrawableRes iconRes: Int, borderColor: Color = iconColor) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.border(
|
||||
width = TangemTheme.dimens.size1_5,
|
||||
color = borderColor,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing2),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StepInProgress() {
|
||||
CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing2)
|
||||
.size(TangemTheme.dimens.size14),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StepSeparator() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing2)
|
||||
.size(
|
||||
width = TangemTheme.dimens.size1_5,
|
||||
height = TangemTheme.dimens.size10,
|
||||
)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ExchangeStatusBlock() {
|
||||
val state = ExpressStatusUM(
|
||||
title = resourceReference(R.string.express_exchange_status_title),
|
||||
link = ExpressLinkUM.Content(
|
||||
icon = R.drawable.ic_alert_24,
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = {},
|
||||
),
|
||||
statuses = persistentListOf(
|
||||
ExpressStatusItemUM(text = stringReference("Done"), state = ExpressStatusItemState.Done),
|
||||
ExpressStatusItemUM(text = stringReference("Active"), state = ExpressStatusItemState.Active),
|
||||
ExpressStatusItemUM(text = stringReference("Warning"), state = ExpressStatusItemState.Warning),
|
||||
ExpressStatusItemUM(text = stringReference("Error"), state = ExpressStatusItemState.Error),
|
||||
ExpressStatusItemUM(text = stringReference("Default"), state = ExpressStatusItemState.Default),
|
||||
),
|
||||
)
|
||||
|
||||
TangemThemePreview {
|
||||
ExpressStatusBlock(state = state)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.common.ui.expressStatus
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.ExpressNotificationsUM
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun ExpressStatusNotificationBlock(state: NotificationUM?) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
label = "Express Status Notification Change",
|
||||
) { notification ->
|
||||
if (notification?.config != null) {
|
||||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (state) {
|
||||
is ExpressNotificationsUM.NeedVerification -> TangemTheme.colors.icon.attention
|
||||
is ExpressNotificationsUM.FailedByProvider -> TangemTheme.colors.icon.warning
|
||||
else -> null
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.common.ui.expressStatus.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* UI data holder for express status block
|
||||
*
|
||||
* @property title block title
|
||||
* @property link provider web link
|
||||
* @property statuses list of possible and active statuses
|
||||
*/
|
||||
data class ExpressStatusUM(
|
||||
val title: TextReference,
|
||||
val link: ExpressLinkUM,
|
||||
val statuses: ImmutableList<ExpressStatusItemUM>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Provider web link for express status block.
|
||||
* [Empty] if no link needed
|
||||
* [Content] if link is provided and displayed
|
||||
*/
|
||||
@Stable
|
||||
sealed class ExpressLinkUM {
|
||||
data object Empty : ExpressLinkUM()
|
||||
data class Content(
|
||||
@DrawableRes val icon: Int,
|
||||
val text: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
) : ExpressLinkUM()
|
||||
}
|
||||
|
||||
/**
|
||||
* Single status item in express status block
|
||||
*/
|
||||
data class ExpressStatusItemUM(
|
||||
val text: TextReference,
|
||||
val state: ExpressStatusItemState,
|
||||
)
|
||||
|
||||
/**
|
||||
* Available status states for express status block
|
||||
*/
|
||||
enum class ExpressStatusItemState {
|
||||
Active,
|
||||
Default,
|
||||
Done,
|
||||
Warning,
|
||||
Error,
|
||||
;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
||||
object ExpressNotificationsUM {
|
||||
|
||||
data class NeedVerification(val onGoToProviderClick: () -> Unit) : NotificationUM.Warning(
|
||||
title = resourceReference(R.string.express_exchange_notification_verification_title),
|
||||
subtitle = resourceReference(R.string.express_exchange_notification_verification_text),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = onGoToProviderClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class FailedByProvider(val onGoToProviderClick: () -> Unit) : NotificationUM.Error(
|
||||
title = resourceReference(R.string.express_exchange_notification_failed_title),
|
||||
subtitle = resourceReference(R.string.express_exchange_notification_failed_text),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = onGoToProviderClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ data class OnrampStatusResponse(
|
|||
@Json(name = "payoutAddress")
|
||||
val payoutAddress: String,
|
||||
|
||||
// @Json(name = "status")
|
||||
// val status: ???
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
|
||||
@Json(name = "failReason")
|
||||
val failReason: String?,
|
||||
|
|
@ -48,14 +48,46 @@ data class OnrampStatusResponse(
|
|||
val toDecimals: String,
|
||||
|
||||
@Json(name = "toAmount")
|
||||
val toAmount: String,
|
||||
val toAmount: String?,
|
||||
|
||||
@Json(name = "toActualAmount")
|
||||
val toActualAmount: String,
|
||||
val toActualAmount: String?,
|
||||
|
||||
@Json(name = "paymentMethod")
|
||||
val paymentMethod: String,
|
||||
|
||||
@Json(name = "countryCode")
|
||||
val countryCode: String,
|
||||
)
|
||||
)
|
||||
|
||||
enum class Status {
|
||||
@Json(name = "created")
|
||||
Created,
|
||||
|
||||
@Json(name = "expired")
|
||||
Expired,
|
||||
|
||||
@Json(name = "waiting-for-payment")
|
||||
WaitingForPayment,
|
||||
|
||||
@Json(name = "payment-processing")
|
||||
PaymentProcessing,
|
||||
|
||||
@Json(name = "verifying")
|
||||
Verifying,
|
||||
|
||||
@Json(name = "failed")
|
||||
Failed,
|
||||
|
||||
@Json(name = "paid")
|
||||
Paid,
|
||||
|
||||
@Json(name = "sending")
|
||||
Sending,
|
||||
|
||||
@Json(name = "finished")
|
||||
Finished,
|
||||
|
||||
@Json(name = "paused")
|
||||
Paused,
|
||||
}
|
||||
|
|
@ -107,6 +107,8 @@ object PreferencesKeys {
|
|||
|
||||
val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") }
|
||||
|
||||
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
|
|
|
|||
|
|
@ -150,4 +150,12 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSetSync(key: Prefere
|
|||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
/** Get flow of set of [T] by string [key], or empty if data is not found */
|
||||
inline fun <reified T> AppPreferencesStore.getObjectSet(key: Preferences.Key<String>): Flow<Set<T>> {
|
||||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
return data.map {
|
||||
it[key]?.let(adapter::fromJson) ?: emptySet()
|
||||
}
|
||||
}
|
||||
|
|
@ -568,7 +568,7 @@
|
|||
<string name="onramp_no_available_providers">Keine verfügbaren Anbieter für diese Währung</string>
|
||||
<string name="onramp_pay_with">Bezahlen mit</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Du kannst Deine Transaktion beim Drittanbieter %s abschließen.</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s …</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s…</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Unsere Dienstleistungen sind in diesem Land nicht verfügbar</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Änder oder bestätige bitte</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Dein Wohnsitz wurde identifiziert als</string>
|
||||
|
|
|
|||
|
|
@ -858,14 +858,14 @@
|
|||
<string name="toast_balances_shown">Saldos mostrados</string>
|
||||
<string name="toast_undo">Cancelar</string>
|
||||
<string name="token_button_unavailability_generic_description">Esta operación no está disponible actualmente. Por favor, inténtalo de nuevo más tarde.</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">Comprar %s no está disponible en este momento. Por favor revise sus actualizaciones.</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">Comprar %s no está disponible con los proveedores actuales pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">No tiene fondos para vender. Recargue su cuenta para poder vender fondos desde ella.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">No tienes fondos para enviar. Recarga tu cuenta para poder enviar fondos desde ella.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">El servicio de intercambio %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">El servicio de intercambio %s no está compatible con los proveedores actuales pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">La venta de fondos estará disponible una vez que la(s) transacción(es) pendiente(s) en la red %s se complete</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">El envío de fondos estará disponible una vez que se completen las transacciones pendientes en la red %s.</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">La venta de %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">El staking %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">Vender %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">El staking de %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_details_generate_xpub">Generar XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Ocultar</string>
|
||||
<string name="token_details_hide_alert_message">Está a punto de ocultar este token de la pantalla principal. Puede volver a agregarlo en cualquier momento a través de la página de gestión de tokens.</string>
|
||||
|
|
|
|||
|
|
@ -850,14 +850,14 @@
|
|||
<string name="toast_balances_shown">Soldes affichés</string>
|
||||
<string name="toast_undo">Annuler</string>
|
||||
<string name="token_button_unavailability_generic_description">Cette opération est actuellement indisponible. Veuillez réessayer plus tard.</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">L\'achat de %s n\'est pas disponible pour le moment. Veuillez vérifier vos mises à jour.</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter davantage d\'options.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">Vous n\'avez pas de fonds à vendre. Renflouez votre compte pour pouvoir vendre des fonds à partir de celui-ci.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">Vous n\'avez pas de fonds à envoyer. Renflouez votre compte pour pouvoir envoyer des fonds à partir de celui-ci.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">Le service d\'échange %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">Le service d\'échange %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">La vente de fonds sera disponible une fois que la ou les transactions en attente dans le réseau %s seront terminées</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées.</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">Le staking %s n’est pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">Le staking de %s n\'est pas pris en charge par les fournisseurs actuels, mais nous travaillons à ajouter plus d\'options.</string>
|
||||
<string name="token_details_generate_xpub">Générer XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Masquer</string>
|
||||
<string name="token_details_hide_alert_message">Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons.</string>
|
||||
|
|
|
|||
|
|
@ -830,7 +830,7 @@
|
|||
<string name="swapping_alert_cex_description_with_slippage">金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです</string>
|
||||
<string name="swapping_alert_dex_description">この金額には、サービスプロバイダーの手数料が含まれています。</string>
|
||||
<string name="swapping_alert_dex_description_with_slippage">金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です</string>
|
||||
<string name="swapping_alert_title">手数料</string>
|
||||
<string name="swapping_alert_title">情報</string>
|
||||
<string name="swapping_approve_information_text">すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。</string>
|
||||
<string name="swapping_approve_information_title">承認</string>
|
||||
<string name="swapping_fee_estimation_error_text">手数料見積りエラーです。サポートにフィードバックをお送りください。</string>
|
||||
|
|
@ -846,14 +846,14 @@
|
|||
<string name="toast_balances_shown">残高表示</string>
|
||||
<string name="toast_undo">元に戻す</string>
|
||||
<string name="token_button_unavailability_generic_description">この操作は現在利用できません。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">現在、 %sの買付はご利用いただけません。アップデート情報をご確認ください。</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">%sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">売却できる資金がありません。アカウントに入金して、売却できるようにしてください。</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">送金する資金がありません。アカウントに入金して、送金できるようにしてください。</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">現在、 %sの交換はご利用いただけません。アップデート情報を確認してください。</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">%sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">ネットワーク%s内の保留中の取引が完了すると、資金の売却が可能になります。</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください。</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">%s のステーキングは現在ご利用いただけません。最新情報をご確認ください。</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">%sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">%sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_details_generate_xpub">XPUBを生成する</string>
|
||||
<string name="token_details_hide_alert_hide">非表示</string>
|
||||
<string name="token_details_hide_alert_message">このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。</string>
|
||||
|
|
@ -946,7 +946,7 @@
|
|||
<string name="wallet_network_group_title">%sネットワーク</string>
|
||||
<string name="wallet_notification_address_copied">アドレスがクリップボードにコピーされました</string>
|
||||
<string name="wallet_notification_no_internet">インターネット接続がありません</string>
|
||||
<string name="wallet_promo_banner_button_title">今すぐ10 %オフで購入</string>
|
||||
<string name="wallet_promo_banner_button_title">今すぐ10 %%オフで購入</string>
|
||||
<string name="wallet_promo_banner_description">1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。</string>
|
||||
<string name="wallet_promo_banner_title">Tangemウォレットを見る</string>
|
||||
<string name="wallet_settings_title">ウォレット設定</string>
|
||||
|
|
@ -1019,7 +1019,7 @@
|
|||
<string name="warning_solana_fee_message">Solanaネットワークが混雑しています。2分以内に取引が完了しない場合は、再度取引を繰り返してください。</string>
|
||||
<string name="warning_solana_fee_title">Solanaネットワークアラート</string>
|
||||
<string name="warning_solana_rent_fee_message">Solana ネットワークは 2 日ごとに%1$sのレンタル料を請求します。レンタル料を支払えないアカウントはネットワークから削除されます。アカウントに%2$s以上入金すると、無料で使用できます。</string>
|
||||
<string name="warning_some_networks_unreachable_message">現在、一部のネットワークにアクセスできません。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="warning_some_networks_unreachable_message">下にスワイプして更新するか、後でもう一度お試しください。</string>
|
||||
<string name="warning_some_networks_unreachable_title">一部のネットワークにアクセスできません</string>
|
||||
<string name="warning_testnet_card_message">これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。</string>
|
||||
<string name="warning_testnet_card_title">テスト目的のみ</string>
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@
|
|||
<string name="onramp_no_available_providers">Для данной валюты нет доступных провайдеров</string>
|
||||
<string name="onramp_pay_with">Оплата с</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Вы сможете завершить транзакцию через сервис стороннего провайдера, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Переход к %s ...</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Переход к %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Наши сервисы недоступны в данной стране</string>
|
||||
<string name="onramp_settings_title">Настройки</string>
|
||||
<string name="onramp_via">Через</string>
|
||||
|
|
@ -966,7 +966,7 @@
|
|||
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
|
||||
<string name="wallet_promo_banner_button_title">Получить с 10%% скидкой</string>
|
||||
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
|
||||
<string name="wallet_promo_banner_title">Откройте Tangem Wallet </string>
|
||||
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Настройки кошелька</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку</string>
|
||||
|
|
|
|||
|
|
@ -294,6 +294,9 @@
|
|||
<string name="express_provider_not_available">Unavailable for this pair</string>
|
||||
<string name="express_provider_permission_needed">Permission Required</string>
|
||||
<string name="express_provider_recommended">Recommended</string>
|
||||
<string name="express_status_bought">Bought %s</string>
|
||||
<string name="express_status_buying">Buying %s</string>
|
||||
<string name="express_status_buying_active">Buying %s...</string>
|
||||
<string name="express_token_list_empty_search">No tokens found. Please try another request</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">Transaction ID copied</string>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
|
|
@ -20,15 +23,16 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
@Composable
|
||||
fun FooterContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
footer: String? = null,
|
||||
footer: TextReference? = null,
|
||||
footerTopPadding: Dp = TangemTheme.dimens.spacing8,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
content()
|
||||
AnimatedVisibility(visible = footer != null) {
|
||||
val footerWrapped = remember(this) { requireNotNull(footer) }
|
||||
Text(
|
||||
text = footer.orEmpty(),
|
||||
text = footerWrapped.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -14,15 +14,11 @@ import com.tangem.blockchain.common.Token as SdkToken
|
|||
|
||||
class ResponseCryptoCurrenciesFactory {
|
||||
|
||||
fun createCurrency(
|
||||
currencyId: CryptoCurrency.ID,
|
||||
response: UserTokensResponse,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency {
|
||||
fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency {
|
||||
return response.tokens
|
||||
.asSequence()
|
||||
.mapNotNull { createCurrency(it, scanResponse) }
|
||||
.first { it.id == currencyId }
|
||||
.first { it.id.value == currencyId }
|
||||
}
|
||||
|
||||
fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
|||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
||||
internal class DefaultOnrampErrorResolver(
|
||||
private val onrampErrorConverter: OnrampErrorConverter,
|
||||
) : OnrampErrorResolver {
|
||||
internal class DefaultOnrampErrorResolver : OnrampErrorResolver {
|
||||
|
||||
private val onrampErrorConverter = OnrampErrorConverter()
|
||||
|
||||
override fun resolve(throwable: Throwable): OnrampError {
|
||||
return if (throwable is ApiResponseError.HttpException) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.onramp
|
|||
|
||||
import com.tangem.data.onramp.converters.CountryConverter
|
||||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
import com.tangem.data.onramp.converters.StatusConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
|||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -27,6 +29,7 @@ internal class DefaultOnrampRepository(
|
|||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
private val countryConverter = CountryConverter(currencyConverter)
|
||||
private val statusConverter = StatusConverter()
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
|
|
@ -46,6 +49,12 @@ internal class DefaultOnrampRepository(
|
|||
.let(countryConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) {
|
||||
onrampApi.getStatus(txId)
|
||||
.getOrThrow()
|
||||
.let(statusConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) {
|
||||
appPreferencesStore.storeObject<OnrampCurrencyDTO>(
|
||||
key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSet
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSetSync
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultOnrampTransactionRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : OnrampTransactionRepository {
|
||||
|
||||
override suspend fun storeTransaction(transaction: OnrampTransaction) {
|
||||
withContext(dispatchers.io) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)
|
||||
val updated = stored?.toMutableSet()
|
||||
?.addOrReplace(transaction) { it.txId == transaction.txId }
|
||||
?: mutableSetOf(transaction)
|
||||
|
||||
mutablePreferences.setObjectSet(
|
||||
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
value = updated,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) {
|
||||
val stored = appPreferencesStore.getObjectSetSync<OnrampTransaction>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)
|
||||
|
||||
stored.firstOrNull { it.txId == txId }
|
||||
}
|
||||
|
||||
override fun getTransactions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): Flow<List<OnrampTransaction>> = appPreferencesStore
|
||||
.getObjectSet<OnrampTransaction>(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY)
|
||||
.map { transactions ->
|
||||
transactions.filter {
|
||||
it.userWalletId == userWalletId && it.toCurrencyId == cryptoCurrencyId.value
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeTransaction(txId: String) {
|
||||
withContext(dispatchers.io) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
runCatching {
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)?.toMutableSet()
|
||||
|
||||
stored?.removeIf { it.txId == txId }
|
||||
|
||||
mutablePreferences.setObjectSet(
|
||||
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
value = stored ?: emptySet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.data.onramp.converters
|
||||
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class StatusConverter : Converter<OnrampStatusResponse, OnrampStatus> {
|
||||
override fun convert(value: OnrampStatusResponse): OnrampStatus {
|
||||
return OnrampStatus(
|
||||
txId = value.txId,
|
||||
providerId = value.providerId,
|
||||
payoutAddress = value.payoutAddress,
|
||||
status = OnrampStatus.Status.valueOf(value.status.name),
|
||||
failReason = value.failReason,
|
||||
externalTxId = value.externalTxId,
|
||||
externalTxUrl = value.externalTxUrl,
|
||||
payoutHash = value.payoutHash,
|
||||
createdAt = value.createdAt,
|
||||
fromCurrencyCode = value.fromCurrencyCode,
|
||||
fromAmount = value.fromAmount,
|
||||
toContractAddress = value.toContractAddress,
|
||||
toNetwork = value.toNetwork,
|
||||
toDecimals = value.toDecimals,
|
||||
toAmount = value.toAmount,
|
||||
toActualAmount = value.toActualAmount,
|
||||
paymentMethod = value.paymentMethod,
|
||||
countryCode = value.countryCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
package com.tangem.data.onramp.di
|
||||
|
||||
import com.tangem.data.onramp.DefaultOnrampErrorResolver
|
||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.data.onramp.DefaultOnrampTransactionRepository
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -28,4 +32,22 @@ internal object OnrampDataModule {
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampTransactionRepository(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): OnrampTransactionRepository {
|
||||
return DefaultOnrampTransactionRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampErrorResolver(): OnrampErrorResolver {
|
||||
return DefaultOnrampErrorResolver()
|
||||
}
|
||||
}
|
||||
|
|
@ -293,23 +293,28 @@ internal class DefaultCurrenciesRepository(
|
|||
userWalletId: UserWalletId,
|
||||
id: CryptoCurrency.ID,
|
||||
): CryptoCurrency = withContext(dispatchers.io) {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val response = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
responseCurrenciesFactory.createCurrency(
|
||||
currencyId = id,
|
||||
response = response,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
getMultiCurrencyWalletCurrency(userWalletId, id.value)
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency =
|
||||
withContext(dispatchers.io) {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val response = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
responseCurrenciesFactory.createCurrency(
|
||||
currencyId = id,
|
||||
response = response,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getNetworkCoin(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ plugins {
|
|||
|
||||
dependencies {
|
||||
api(projects.domain.onramp.models)
|
||||
api(projects.domain.tokens.models)
|
||||
api(projects.domain.wallets.models)
|
||||
|
||||
api(projects.domain.core)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ plugins {
|
|||
|
||||
dependencies {
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.jodatime)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampStatus(
|
||||
val txId: String,
|
||||
val providerId: String,
|
||||
val payoutAddress: String,
|
||||
val status: Status,
|
||||
val failReason: String?,
|
||||
val externalTxId: String,
|
||||
val externalTxUrl: String?,
|
||||
val payoutHash: String?,
|
||||
val createdAt: String,
|
||||
val fromCurrencyCode: String,
|
||||
val fromAmount: String,
|
||||
val toContractAddress: String,
|
||||
val toNetwork: String,
|
||||
val toDecimals: String,
|
||||
val toAmount: String?,
|
||||
val toActualAmount: String?,
|
||||
val paymentMethod: String,
|
||||
val countryCode: String,
|
||||
) {
|
||||
enum class Status(val order: Int) {
|
||||
Created(order = 1),
|
||||
Expired(order = 2),
|
||||
Paused(order = 3),
|
||||
WaitingForPayment(order = 4),
|
||||
PaymentProcessing(order = 5),
|
||||
Verifying(order = 6),
|
||||
Failed(order = 7),
|
||||
Paid(order = 8),
|
||||
Sending(order = 9),
|
||||
Finished(order = 10),
|
||||
;
|
||||
|
||||
fun isTerminal(): Boolean = when (this) {
|
||||
Expired,
|
||||
Failed,
|
||||
Paused,
|
||||
Finished,
|
||||
-> true
|
||||
Created,
|
||||
WaitingForPayment,
|
||||
PaymentProcessing,
|
||||
Verifying,
|
||||
Paid,
|
||||
Sending,
|
||||
-> false
|
||||
}
|
||||
}
|
||||
}
|
||||
18
domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt
vendored
Normal file
18
domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.onramp.model.cache
|
||||
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampTransaction(
|
||||
val txId: String,
|
||||
val userWalletId: UserWalletId,
|
||||
val fromAmount: SerializedBigDecimal,
|
||||
val fromCurrency: OnrampCurrency,
|
||||
val toAmount: SerializedBigDecimal,
|
||||
val toCurrencyId: String,
|
||||
val providerName: String,
|
||||
val providerImageUrl: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class GetOnrampStatusUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(txId: String): Either<OnrampError, OnrampStatus> {
|
||||
return Either.catch {
|
||||
onrampRepository.getStatus(txId)
|
||||
}.mapLeft {
|
||||
errorResolver.resolve(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
|
||||
class GetOnrampTransactionUseCase(
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(txId: String): Either<OnrampError, OnrampTransaction> {
|
||||
return Either.catch {
|
||||
requireNotNull(
|
||||
onrampTransactionRepository.getTransactionById(txId),
|
||||
)
|
||||
}.mapLeft {
|
||||
OnrampError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class GetOnrampTransactionsUseCase(
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): Either<OnrampError, Flow<List<OnrampTransaction>>> {
|
||||
return Either.catch {
|
||||
onrampTransactionRepository.getTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
)
|
||||
}.mapLeft {
|
||||
OnrampError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
|
||||
class OnrampRemoveTransactionUseCase(
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(txId: String): Either<OnrampError, Unit> {
|
||||
return Either.catch {
|
||||
onrampTransactionRepository.removeTransaction(txId)
|
||||
}.mapLeft {
|
||||
OnrampError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
|
||||
class OnrampSaveTransactionUseCase(
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(transaction: OnrampTransaction): Either<OnrampError, Unit> {
|
||||
return Either.catch { onrampTransactionRepository.storeTransaction(transaction) }
|
||||
.mapLeft { OnrampError.UnknownError }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,17 @@ package com.tangem.domain.onramp.repositories
|
|||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampRepository {
|
||||
// api
|
||||
suspend fun getCurrencies(): List<OnrampCurrency>
|
||||
suspend fun getCountries(): List<OnrampCountry>
|
||||
suspend fun getCountryByIp(): OnrampCountry
|
||||
suspend fun getStatus(txId: String): OnrampStatus
|
||||
|
||||
// cache
|
||||
suspend fun saveDefaultCurrency(currency: OnrampCurrency)
|
||||
suspend fun getDefaultCurrencySync(): OnrampCurrency?
|
||||
fun getDefaultCurrency(): Flow<OnrampCurrency?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.onramp.repositories
|
||||
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampTransactionRepository {
|
||||
|
||||
suspend fun storeTransaction(transaction: OnrampTransaction)
|
||||
|
||||
suspend fun getTransactionById(txId: String): OnrampTransaction?
|
||||
|
||||
fun getTransactions(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<OnrampTransaction>>
|
||||
|
||||
suspend fun removeTransaction(txId: String)
|
||||
}
|
||||
|
|
@ -27,6 +27,22 @@ class GetCryptoCurrencyUseCase(
|
|||
return either { getCurrency(userWalletId, id) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns specific cryptocurrency for a given user wallet.
|
||||
*
|
||||
* !!! Important Use only [CryptoCurrency.ID.value] as cryptoCurrencyId
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param cryptoCurrencyId String representation of the [CryptoCurrency.ID.value] of the cryptocurrency.
|
||||
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: String,
|
||||
): Either<CurrencyStatusError, CryptoCurrency> {
|
||||
return either { getCurrency(userWalletId, cryptoCurrencyId) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary cryptocurrency for a given user wallet.
|
||||
*
|
||||
|
|
@ -49,6 +65,18 @@ class GetCryptoCurrencyUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.getCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
id: String,
|
||||
): CryptoCurrency {
|
||||
return catch(
|
||||
block = {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id)
|
||||
},
|
||||
catch = { raise(CurrencyStatusError.DataError(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
|
||||
return catch(
|
||||
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
|
||||
|
|
|
|||
|
|
@ -167,6 +167,17 @@ interface CurrenciesRepository {
|
|||
*/
|
||||
suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency
|
||||
|
||||
/**
|
||||
* Retrieves the cryptocurrency for a specific multi-currency user wallet.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param id The unique identifier of the cryptocurrency to be retrieved.
|
||||
* @return The cryptocurrency associated with the user wallet and ID.
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency
|
||||
|
||||
/**
|
||||
* Get the coin for a specific network.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -100,6 +100,14 @@ internal class MockCurrenciesRepository(
|
|||
return token
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency {
|
||||
val token = token.getOrElse { e -> throw e }
|
||||
|
||||
require(token.id.value == id)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
override suspend fun getNetworkCoin(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface OnrampSuccessComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val txId: String)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampSuccessComponent>
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ dependencies {
|
|||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/** Project - Common */
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -42,7 +43,6 @@ dependencies {
|
|||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
implementation(project(":common:ui"))
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** AndroidX */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.onramp.success
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||
import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel
|
||||
import com.tangem.features.onramp.success.ui.OnrampSuccessComponentContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultOnrampSuccessComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: OnrampSuccessComponent.Params,
|
||||
) : OnrampSuccessComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnrampSuccessComponentModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsState()
|
||||
|
||||
BackHandler(onBack = router::pop)
|
||||
OnrampSuccessComponentContent(
|
||||
state = state,
|
||||
onBackClick = router::pop,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampSuccessComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnrampSuccessComponent.Params,
|
||||
): DefaultOnrampSuccessComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.success.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampSuccessComponentModelModule {
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampSuccessComponentModel::class)
|
||||
fun bindOnrampSuccessComponentModel(model: OnrampSuccessComponentModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.success.di
|
||||
|
||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||
import com.tangem.features.onramp.success.DefaultOnrampSuccessComponent
|
||||
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 OnrampSuccessComponentModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampSuccessComponentFactory(
|
||||
factory: DefaultOnrampSuccessComponent.Factory,
|
||||
): OnrampSuccessComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.onramp.success.entity
|
||||
|
||||
interface OnrampSuccessClickIntents {
|
||||
fun goToProviderClick(providerLink: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.onramp.success.entity
|
||||
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
sealed class OnrampSuccessComponentUM {
|
||||
|
||||
data object Loading : OnrampSuccessComponentUM()
|
||||
|
||||
data class Content(
|
||||
val txId: String,
|
||||
val timestamp: Long,
|
||||
val currencyImageUrl: String,
|
||||
val fromAmount: TextReference,
|
||||
val toAmount: TextReference,
|
||||
val statusBlock: ExpressStatusUM,
|
||||
val providerName: TextReference,
|
||||
val providerImageUrl: String,
|
||||
val notification: NotificationUM?,
|
||||
) : OnrampSuccessComponentUM()
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
package com.tangem.features.onramp.success.entity.conterter
|
||||
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.notifications.ExpressNotificationsUM
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.joda.time.DateTime
|
||||
|
||||
internal class SetOnrampSuccessContentConverter(
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val transaction: OnrampTransaction,
|
||||
private val goToProviderClick: (String) -> Unit,
|
||||
) : Converter<OnrampStatus, OnrampSuccessComponentUM> {
|
||||
override fun convert(value: OnrampStatus): OnrampSuccessComponentUM {
|
||||
return OnrampSuccessComponentUM.Content(
|
||||
txId = value.txId,
|
||||
timestamp = DateTime.parse(value.createdAt).millis,
|
||||
currencyImageUrl = transaction.fromCurrency.image,
|
||||
fromAmount = stringReference(
|
||||
transaction.fromAmount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = transaction.fromCurrency.name,
|
||||
fiatCurrencySymbol = transaction.fromCurrency.code,
|
||||
)
|
||||
},
|
||||
),
|
||||
toAmount = stringReference(
|
||||
transaction.toAmount.format {
|
||||
crypto(cryptoCurrency)
|
||||
},
|
||||
),
|
||||
providerName = stringReference(transaction.providerName),
|
||||
providerImageUrl = transaction.providerImageUrl,
|
||||
statusBlock = convertStatuses(value.status, value.externalTxUrl),
|
||||
notification = getNotification(value.status, value.externalTxUrl),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotification(status: OnrampStatus.Status, externalTxUrl: String?): NotificationUM? {
|
||||
if (externalTxUrl == null) return null
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying -> {
|
||||
ExpressNotificationsUM.NeedVerification {
|
||||
goToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
OnrampStatus.Status.Failed -> {
|
||||
ExpressNotificationsUM.FailedByProvider {
|
||||
goToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM {
|
||||
val statuses = with(status) {
|
||||
persistentListOf(
|
||||
getAwaitingDepositItem(),
|
||||
getPaymentProcessingItem(),
|
||||
getBuyingItem(),
|
||||
getSendingItem(),
|
||||
)
|
||||
}
|
||||
|
||||
return ExpressStatusUM(
|
||||
title = resourceReference(R.string.common_transaction_status),
|
||||
link = getStatusLink(status, externalTxUrl),
|
||||
statuses = statuses,
|
||||
)
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.WaitingForPayment.order -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving)
|
||||
}
|
||||
this == OnrampStatus.Status.WaitingForPayment -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving_active)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_exchange_status_received)
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.WaitingForPayment),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming)
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming_active)
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
resourceReference(R.string.express_exchange_status_verifying)
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
resourceReference(R.string.express_exchange_status_failed)
|
||||
}
|
||||
else -> resourceReference(R.string.express_exchange_status_confirmed)
|
||||
},
|
||||
state = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
ExpressStatusItemState.Default
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
ExpressStatusItemState.Active
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
ExpressStatusItemState.Warning
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
ExpressStatusItemState.Error
|
||||
}
|
||||
else -> {
|
||||
ExpressStatusItemState.Done
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Paid.order -> {
|
||||
resourceReference(R.string.express_status_buying, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
this == OnrampStatus.Status.Paid -> {
|
||||
resourceReference(R.string.express_status_buying_active, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_status_bought, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Paid),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Sending.order -> {
|
||||
resourceReference(R.string.express_exchange_status_sending, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
this == OnrampStatus.Status.Sending -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sending_active,
|
||||
wrappedList(cryptoCurrency.name),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_exchange_status_sent, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Sending),
|
||||
)
|
||||
|
||||
private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM {
|
||||
if (externalTxUrl == null) return ExpressLinkUM.Empty
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying,
|
||||
OnrampStatus.Status.Failed,
|
||||
-> {
|
||||
ExpressLinkUM.Content(
|
||||
icon = R.drawable.ic_arrow_top_right_24,
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = {
|
||||
goToProviderClick(externalTxUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> ExpressLinkUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when {
|
||||
order < targetState.order -> ExpressStatusItemState.Default
|
||||
this == targetState -> ExpressStatusItemState.Active
|
||||
else -> ExpressStatusItemState.Done
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.features.onramp.success.entity.previewdata
|
||||
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.joda.time.DateTime
|
||||
|
||||
internal data object OnrampSuccessComponentUMPreviewData {
|
||||
|
||||
val loadingState = OnrampSuccessComponentUM.Loading
|
||||
|
||||
val contentState = OnrampSuccessComponentUM.Content(
|
||||
txId = "b2894851-7b63-4f56-bd75-e408f1dcba31",
|
||||
timestamp = DateTime.parse("2023-09-20T13:45:10.868Z").millis,
|
||||
providerName = stringReference("1Inch"),
|
||||
providerImageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/1INCH1024.png",
|
||||
fromAmount = stringReference("100 USDT"),
|
||||
toAmount = stringReference("99.99 $"),
|
||||
currencyImageUrl = "",
|
||||
notification = null,
|
||||
statusBlock = ExpressStatusUM(
|
||||
title = resourceReference(R.string.express_exchange_status_title),
|
||||
link = ExpressLinkUM.Empty,
|
||||
statuses = persistentListOf(
|
||||
ExpressStatusItemUM(
|
||||
text = stringReference("Deposit received"),
|
||||
state = ExpressStatusItemState.Done,
|
||||
),
|
||||
ExpressStatusItemUM(
|
||||
text = stringReference("Confirmed"),
|
||||
state = ExpressStatusItemState.Done,
|
||||
),
|
||||
ExpressStatusItemUM(
|
||||
text = stringReference("Buying Bitcoin..."),
|
||||
state = ExpressStatusItemState.Active,
|
||||
),
|
||||
ExpressStatusItemUM(
|
||||
text = stringReference("Sending to you"),
|
||||
state = ExpressStatusItemState.Default,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.tangem.features.onramp.success.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.onramp.GetOnrampStatusUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
|
||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessClickIntents
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
|
||||
import com.tangem.features.onramp.success.entity.conterter.SetOnrampSuccessContentConverter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class OnrampSuccessComponentModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val getOnrampTransactionUseCase: GetOnrampTransactionUseCase,
|
||||
private val getOnrampStatusUseCase: GetOnrampStatusUseCase,
|
||||
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model(), OnrampSuccessClickIntents {
|
||||
|
||||
private val params: OnrampSuccessComponent.Params = paramsContainer.require()
|
||||
private val _state: MutableStateFlow<OnrampSuccessComponentUM> = MutableStateFlow(
|
||||
value = OnrampSuccessComponentUM.Loading,
|
||||
)
|
||||
|
||||
val state: StateFlow<OnrampSuccessComponentUM> get() = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
loadData()
|
||||
}
|
||||
|
||||
override fun goToProviderClick(providerLink: String) {
|
||||
urlOpener.openUrl(providerLink)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
modelScope.launch {
|
||||
getOnrampTransactionUseCase(txId = params.txId)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.toString())
|
||||
},
|
||||
ifRight = { transaction ->
|
||||
loadTransactionStatus(transaction)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadTransactionStatus(transaction: OnrampTransaction) {
|
||||
val cryptoCurrencies = getCryptoCurrencyUseCase(
|
||||
transaction.userWalletId,
|
||||
transaction.toCurrencyId,
|
||||
).getOrElse { error("Crypto currency not found") }
|
||||
|
||||
getOnrampStatusUseCase(txId = params.txId)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.toString())
|
||||
},
|
||||
ifRight = { status ->
|
||||
_state.update {
|
||||
SetOnrampSuccessContentConverter(
|
||||
cryptoCurrency = cryptoCurrencies,
|
||||
transaction = transaction,
|
||||
goToProviderClick = ::goToProviderClick,
|
||||
).convert(status)
|
||||
}
|
||||
removeTransactionIfTerminalStatus(status)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun removeTransactionIfTerminalStatus(status: OnrampStatus) {
|
||||
modelScope.launch {
|
||||
if (status.status.isTerminal()) {
|
||||
onrampRemoveTransactionUseCase(status.txId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
package com.tangem.features.onramp.success.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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 coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBlock
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
|
||||
import com.tangem.features.onramp.success.entity.previewdata.OnrampSuccessComponentUMPreviewData
|
||||
|
||||
@Composable
|
||||
internal fun OnrampSuccessComponentContent(
|
||||
state: OnrampSuccessComponentUM,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (state !is OnrampSuccessComponentUM.Content) {
|
||||
Box(modifier.background(TangemTheme.colors.background.secondary))
|
||||
} else {
|
||||
Content(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: OnrampSuccessComponentUM.Content, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.systemBarsPadding(),
|
||||
topBar = {
|
||||
TangemTopAppBar(
|
||||
startButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
onIconClicked = onBackClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
PrimaryButton(
|
||||
text = stringResource(R.string.common_close),
|
||||
onClick = onBackClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
) { scaffoldPaddings ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(scaffoldPaddings)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp),
|
||||
) {
|
||||
TransactionDoneTitle(
|
||||
title = resourceReference(R.string.common_in_progress),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
state.timestamp.toTimeFormat(DateTimeFormatters.dateFormatter),
|
||||
state.timestamp.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
)
|
||||
SpacerH24()
|
||||
AmountBlock(state)
|
||||
SpacerH12()
|
||||
FooterContainer(
|
||||
footer = resourceReference(R.string.onramp_transaction_status_footer_text),
|
||||
) {
|
||||
ExpressStatusBlock(state.statusBlock)
|
||||
}
|
||||
ExpressStatusNotificationBlock(
|
||||
state.notification,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountBlock(state: OnrampSuccessComponentUM.Content) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
val iconModifier = Modifier.size(size = 40.dp)
|
||||
SubcomposeAsyncImage(
|
||||
modifier = iconModifier,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.currencyImageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { CircleShimmer(modifier = iconModifier) },
|
||||
error = { },
|
||||
contentDescription = null,
|
||||
)
|
||||
ResizableText(
|
||||
text = state.fromAmount.resolveReference(),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
)
|
||||
Text(
|
||||
text = state.toAmount.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
)
|
||||
SpacerH12()
|
||||
AmountProviderBlock(
|
||||
state.providerName,
|
||||
state.providerImageUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountProviderBlock(providerName: TextReference, providerImageUrl: String, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.common_with),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.size(size = 16.dp),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(providerImageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { CircleShimmer() },
|
||||
error = { },
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = providerName.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun OnrampSuccessComponentContent_Preview(
|
||||
@PreviewParameter(OnrampSuccessComponentContentPreviewProvider::class)
|
||||
data: OnrampSuccessComponentUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
OnrampSuccessComponentContent(
|
||||
state = data,
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class OnrampSuccessComponentContentPreviewProvider :
|
||||
PreviewParameterProvider<OnrampSuccessComponentUM> {
|
||||
override val values: Sequence<OnrampSuccessComponentUM>
|
||||
get() = sequenceOf(
|
||||
OnrampSuccessComponentUMPreviewData.contentState,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnterAmount
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
|
|
@ -41,7 +40,7 @@ internal fun SendCustomFee(
|
|||
repeat(customValues.size) { index ->
|
||||
val value = customValues[index]
|
||||
FooterContainer(
|
||||
footer = value.footer.resolveReference(),
|
||||
footer = value.footer,
|
||||
) {
|
||||
if (value.label != null) {
|
||||
InputRowEnterInfoAmount(
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
|
|
@ -32,8 +36,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates
|
|||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ private fun LazyListScope.addressItem(
|
|||
) {
|
||||
item(key = ADDRESS_FIELD_KEY) {
|
||||
FooterContainer(
|
||||
footer = stringResource(R.string.send_recipient_address_footer, network),
|
||||
footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(network)),
|
||||
) {
|
||||
InputRowRecipient(
|
||||
value = address.value,
|
||||
|
|
@ -137,7 +139,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM
|
|||
value = memoField.value,
|
||||
label = memoField.label,
|
||||
placeholder = placeholder,
|
||||
footer = stringResource(R.string.send_recipient_memo_footer),
|
||||
footer = resourceReference(R.string.send_recipient_memo_footer),
|
||||
onValueChange = memoField.onValueChange,
|
||||
onPasteClick = onMemoChange,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing20),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal fun TextFieldWithPaste(
|
|||
onValueChange: (String) -> Unit,
|
||||
onPasteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
footer: String? = null,
|
||||
footer: TextReference? = null,
|
||||
labelStyle: TextStyle = TangemTheme.typography.body2,
|
||||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ internal sealed interface ExchangeStatusNotifications {
|
|||
|
||||
sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications
|
||||
|
||||
@Deprecated("Use one in ExpressNotificationsUM")
|
||||
data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.express_exchange_notification_verification_title),
|
||||
|
|
@ -31,6 +32,7 @@ internal sealed interface ExchangeStatusNotifications {
|
|||
),
|
||||
)
|
||||
|
||||
@Deprecated("Use one in ExpressNotificationsUM")
|
||||
data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.express_exchange_notification_failed_title),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.features.tokendetails.impl.R
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Deprecated("Use ExpressStatusBlock from common")
|
||||
@Composable
|
||||
internal fun ExchangeStatusBlock(
|
||||
statuses: ImmutableList<ExchangeStatusState>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue