Updated on 2026-08-14
This commit is contained in:
commit
3c2a006fb9
331 changed files with 8261 additions and 1670 deletions
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.navigation.DummyRouter
|
||||
import com.tangem.core.navigation.url.DummyUrlOpener
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
|
|
@ -28,6 +29,7 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
val previewState = DetailsUM(
|
||||
items = previewBlocks,
|
||||
footer = previewFooter,
|
||||
selectFeedbackEmailTypeBSConfig = TangemBottomSheetConfig.Empty,
|
||||
popBack = { /* no-op */ },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
name = stringReference("My Wallet"),
|
||||
information = getInformation(cardCount = 1),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
@ -38,7 +37,6 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
name = stringReference("Old wallet"),
|
||||
information = getInformation(cardCount = 2),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
@ -47,7 +45,6 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
name = stringReference("Multi Card"),
|
||||
information = getInformation(cardCount = 3),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class DetailsUM(
|
||||
val items: ImmutableList<DetailsItemUM>,
|
||||
val footer: DetailsFooterUM,
|
||||
val selectFeedbackEmailTypeBSConfig: TangemBottomSheetConfig,
|
||||
val popBack: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.details.impl.R
|
||||
|
||||
internal data class SelectEmailFeedbackTypeBS(
|
||||
val onOptionClick: (Option) -> Unit,
|
||||
) : TangemBottomSheetConfigContent {
|
||||
|
||||
enum class Option(val text: TextReference) {
|
||||
General(resourceReference(R.string.common_contact_tangem_support)),
|
||||
Visa(resourceReference(R.string.common_contact_visa_support)),
|
||||
}
|
||||
}
|
||||
|
|
@ -7,17 +7,22 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.domain.common.TapWorkarounds.isVisa
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
|
||||
import com.tangem.features.details.utils.ItemsBuilder
|
||||
import com.tangem.features.details.utils.SocialsBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -47,6 +52,7 @@ internal class DetailsModel @Inject constructor(
|
|||
private val appStateHolder: ReduxStateHolder,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -84,6 +90,7 @@ internal class DetailsModel @Inject constructor(
|
|||
socials = socialsBuilder.buildAll(),
|
||||
appVersion = getAppVersion(),
|
||||
),
|
||||
selectFeedbackEmailTypeBSConfig = TangemBottomSheetConfig.Empty,
|
||||
popBack = router::pop,
|
||||
),
|
||||
)
|
||||
|
|
@ -99,12 +106,88 @@ internal class DetailsModel @Inject constructor(
|
|||
|
||||
private fun sendFeedback() {
|
||||
modelScope.launch {
|
||||
val userWallets = getWalletsUseCase.invokeSync()
|
||||
|
||||
val scanResponse = getSelectedWalletSyncUseCase().getOrNull()?.scanResponse
|
||||
?: error("Selected wallet is null")
|
||||
|
||||
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
|
||||
|
||||
sendFeedbackEmailUseCase(type = FeedbackEmailType.DirectUserRequest(cardInfo = cardInfo))
|
||||
val feedbackType = when {
|
||||
userWallets.all { it.scanResponse.card.isVisa } -> FeedbackEmailType.Visa.DirectUserRequest(cardInfo)
|
||||
userWallets.all { it.scanResponse.card.isVisa.not() } -> FeedbackEmailType.DirectUserRequest(cardInfo)
|
||||
else -> {
|
||||
showFeedbackEmailTypeOptionBS(cardInfo)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
sendFeedbackEmailUseCase(feedbackType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFeedbackEmailTypeOptionBS(selectedCardInfo: CardInfo) {
|
||||
state.update {
|
||||
it.copy(
|
||||
selectFeedbackEmailTypeBSConfig = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {
|
||||
state.update {
|
||||
it.copy(
|
||||
selectFeedbackEmailTypeBSConfig =
|
||||
it.selectFeedbackEmailTypeBSConfig.copy(isShown = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
content = SelectEmailFeedbackTypeBS(
|
||||
onOptionClick = { option ->
|
||||
onEmailFeedbackTypeOptionSelected(
|
||||
selectedCardInfo = selectedCardInfo,
|
||||
option = option,
|
||||
)
|
||||
|
||||
state.update {
|
||||
it.copy(
|
||||
selectFeedbackEmailTypeBSConfig =
|
||||
it.selectFeedbackEmailTypeBSConfig.copy(isShown = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEmailFeedbackTypeOptionSelected(
|
||||
selectedCardInfo: CardInfo,
|
||||
option: SelectEmailFeedbackTypeBS.Option,
|
||||
) {
|
||||
modelScope.launch {
|
||||
val feedbackType = when (option) {
|
||||
SelectEmailFeedbackTypeBS.Option.General -> {
|
||||
if (selectedCardInfo.isVisa.not()) {
|
||||
FeedbackEmailType.DirectUserRequest(selectedCardInfo)
|
||||
} else {
|
||||
val scanResponse = getWalletsUseCase.invokeSync()
|
||||
.firstOrNull { it.scanResponse.card.isVisa.not() }?.scanResponse ?: return@launch
|
||||
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
|
||||
FeedbackEmailType.DirectUserRequest(cardInfo)
|
||||
}
|
||||
}
|
||||
SelectEmailFeedbackTypeBS.Option.Visa -> {
|
||||
if (selectedCardInfo.isVisa) {
|
||||
FeedbackEmailType.Visa.DirectUserRequest(selectedCardInfo)
|
||||
} else {
|
||||
val scanResponse = getWalletsUseCase.invokeSync()
|
||||
.firstOrNull { it.scanResponse.card.isVisa }?.scanResponse ?: return@launch
|
||||
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
|
||||
FeedbackEmailType.Visa.DirectUserRequest(cardInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendFeedbackEmailUseCase(feedbackType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ internal fun DetailsScreen(
|
|||
userWalletListBlockContent = userWalletListBlockContent,
|
||||
)
|
||||
}
|
||||
|
||||
SelectFeedbackEmailTypeBottomSheet(state.selectFeedbackEmailTypeBSConfig)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.inputrow.InputRowChecked
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
|
||||
import com.tangem.features.details.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun SelectFeedbackEmailTypeBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<SelectEmailFeedbackTypeBS>(
|
||||
config = config,
|
||||
titleText = resourceReference(R.string.common_choose_action),
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
content = { Content(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(content: SelectEmailFeedbackTypeBS) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
SelectEmailFeedbackTypeBS.Option.entries.forEachIndexed { index, type ->
|
||||
DividerContainer(
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = SelectEmailFeedbackTypeBS.Option.entries.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable { content.onOptionClick(type) },
|
||||
showDivider = index != SelectEmailFeedbackTypeBS.Option.entries.lastIndex,
|
||||
) {
|
||||
InputRowChecked(
|
||||
text = type.text,
|
||||
checked = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,19 +18,23 @@ import com.tangem.domain.core.lce.Lce
|
|||
import com.tangem.domain.core.lce.lce
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.models.ArtworkModel
|
||||
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class UserWalletsFetcher @Inject constructor(
|
||||
getWalletsUseCase: GetWalletsUseCase,
|
||||
|
|
@ -39,8 +43,11 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val router: Router,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
) {
|
||||
|
||||
private var loadedArtworks: HashMap<UserWalletId, ArtworkModel> = hashMapOf()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val userWallets: Flow<ImmutableList<UserWalletItemUM>> = getWalletsUseCase().transformLatest { wallets ->
|
||||
val uiModels = UserWalletItemUMConverter(onClick = ::navigateToWalletSettings).convertList(wallets)
|
||||
|
|
@ -52,12 +59,14 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
flow = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
|
||||
flow2 = getBalanceHidingSettingsUseCase().distinctUntilChanged(),
|
||||
flow3 = getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(),
|
||||
) { maybeAppCurrency, balanceHidingSettings, maybeBalances ->
|
||||
flow4 = loadArtworks(wallets),
|
||||
) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks ->
|
||||
val models = createUiModels(
|
||||
wallets = wallets,
|
||||
maybeAppCurrency = maybeAppCurrency,
|
||||
maybeBalances = maybeBalances,
|
||||
balanceHidingSettings = balanceHidingSettings,
|
||||
artworks = artworks,
|
||||
).getOrElse(
|
||||
ifLoading = { return@combine },
|
||||
ifError = {
|
||||
|
|
@ -72,11 +81,27 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
}.collect()
|
||||
}
|
||||
|
||||
private fun loadArtworks(wallets: List<UserWallet>): Flow<HashMap<UserWalletId, ArtworkModel>> {
|
||||
return flow {
|
||||
emit(hashMapOf()) // emits right away so the transform doesn't wait for the images' loading to finish
|
||||
wallets.forEach { wallet ->
|
||||
val artwork = getCardImageUseCase(
|
||||
cardId = wallet.cardId,
|
||||
cardPublicKey = wallet.scanResponse.card.cardPublicKey,
|
||||
size = ArtworkSize.SMALL,
|
||||
)
|
||||
loadedArtworks[wallet.walletId] = artwork
|
||||
emit(loadedArtworks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUiModels(
|
||||
wallets: List<UserWallet>,
|
||||
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
|
||||
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
|
||||
balanceHidingSettings: BalanceHidingSettings,
|
||||
artworks: HashMap<UserWalletId, ArtworkModel>,
|
||||
): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
|
||||
val balances = withError(
|
||||
transform = { Error.UnableToGetBalances },
|
||||
|
|
@ -99,6 +124,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
appCurrency = appCurrency,
|
||||
balance = balance,
|
||||
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
|
||||
artwork = artworks[userWallet.walletId],
|
||||
)
|
||||
.convert(userWallet)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
|
|||
name = stringReference("Wallet 1"),
|
||||
information = stringReference("3 cards"),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.features.nft.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface NFTCollectionsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTCollectionsComponent>
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import com.tangem.core.decompose.factory.ComponentFactory
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface NFTReceiveComponent : ComposableContentComponent {
|
||||
interface NFTComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTReceiveComponent>
|
||||
interface Factory : ComponentFactory<Params, NFTComponent>
|
||||
}
|
||||
|
|
@ -5,12 +5,13 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface NFTDetailsComponent : ComposableContentComponent {
|
||||
interface NFTDetailsBlockComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTDetailsComponent>
|
||||
interface Factory : ComponentFactory<Params, NFTDetailsBlockComponent>
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.nft)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
|
|||
|
|
@ -6,17 +6,18 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.nft.collections.model.NFTCollectionsModel
|
||||
import com.tangem.features.nft.collections.ui.NFTCollections
|
||||
import com.tangem.features.nft.component.NFTCollectionsComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNFTCollectionsComponent @AssistedInject constructor(
|
||||
internal class NFTCollectionsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: NFTCollectionsComponent.Params,
|
||||
) : NFTCollectionsComponent, AppComponentContext by context {
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val model: NFTCollectionsModel = getOrCreateModel(params)
|
||||
|
||||
|
|
@ -27,11 +28,10 @@ internal class DefaultNFTCollectionsComponent @AssistedInject constructor(
|
|||
NFTCollections(state, modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTCollectionsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NFTCollectionsComponent.Params,
|
||||
): DefaultNFTCollectionsComponent
|
||||
}
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val onBackClick: () -> Unit,
|
||||
val onAssetClick: (asset: NFTAsset, collectionName: String) -> Unit,
|
||||
val onReceiveClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ internal class UpdateDataStateTransformer(
|
|||
private val onRetryClick: () -> Unit,
|
||||
private val onExpandCollectionClick: (NFTCollection) -> Unit,
|
||||
private val onRetryAssetsClick: (NFTCollection) -> Unit,
|
||||
private val onAssetClick: (NFTAsset) -> Unit,
|
||||
private val onAssetClick: (NFTAsset, String) -> Unit,
|
||||
private val initialSearchBarFactory: () -> SearchBarUM,
|
||||
) : Transformer<NFTCollectionsStateUM> {
|
||||
|
||||
|
|
@ -61,7 +61,25 @@ internal class UpdateDataStateTransformer(
|
|||
state: NFTCollectionsStateUM,
|
||||
query: String,
|
||||
): ImmutableList<NFTCollectionUM> = mapNotNull {
|
||||
if (query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true) {
|
||||
val assetsFulfillQuery = if (query.isEmpty()) {
|
||||
true
|
||||
} else {
|
||||
when (val assets = it.assets) {
|
||||
is NFTCollection.Assets.Empty,
|
||||
is NFTCollection.Assets.Failed,
|
||||
is NFTCollection.Assets.Loading,
|
||||
-> true
|
||||
is NFTCollection.Assets.Value -> {
|
||||
assets.items.any { asset ->
|
||||
asset.name?.lowercase()?.contains(query.lowercase()) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true
|
||||
|
||||
if (collectionFulfillQuery || assetsFulfillQuery) {
|
||||
NFTCollectionUM(
|
||||
id = it.id.toString(),
|
||||
networkIconId = getActiveIconRes(it.network.id.value),
|
||||
|
|
@ -105,12 +123,12 @@ internal class UpdateDataStateTransformer(
|
|||
is NFTCollection.Assets.Value -> NFTCollectionAssetsListUM.Content(
|
||||
items = assets
|
||||
.items
|
||||
.map { it.transform() }
|
||||
.map { it.transform(name.orEmpty()) }
|
||||
.toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun NFTAsset.transform(): NFTCollectionAssetUM = NFTCollectionAssetUM(
|
||||
private fun NFTAsset.transform(collectionName: String): NFTCollectionAssetUM = NFTCollectionAssetUM(
|
||||
id = id.toString(),
|
||||
name = name.orEmpty(),
|
||||
imageUrl = media?.url,
|
||||
|
|
@ -121,7 +139,7 @@ internal class UpdateDataStateTransformer(
|
|||
is NFTSalePrice.Value -> NFTSalePriceUM.Content(salePrice.value.toString())
|
||||
},
|
||||
onItemClick = {
|
||||
onAssetClick(this)
|
||||
onAssetClick(this, collectionName)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
package com.tangem.features.nft.collections.model
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.features.nft.collections.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsUM
|
||||
import com.tangem.features.nft.collections.entity.transformer.ChangeCollectionExpandedStateTransformer
|
||||
import com.tangem.features.nft.collections.entity.transformer.ToggleSearchBarTransformer
|
||||
import com.tangem.features.nft.collections.entity.transformer.UpdateDataStateTransformer
|
||||
import com.tangem.features.nft.collections.entity.transformer.UpdateSearchQueryTransformer
|
||||
import com.tangem.features.nft.component.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -29,23 +26,22 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class NFTCollectionsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val searchManager: InputManager,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<NFTCollectionsStateUM> get() = _state
|
||||
private val params: NFTCollectionsComponent.Params = paramsContainer.require()
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
value = NFTCollectionsStateUM(
|
||||
onBackClick = ::navigateBack,
|
||||
content = NFTCollectionsUM.Loading(::onReceiveClick),
|
||||
onBackClick = params.onBackClick,
|
||||
content = NFTCollectionsUM.Loading(params.onBackClick),
|
||||
),
|
||||
)
|
||||
|
||||
private val params: NFTCollectionsComponent.Params = paramsContainer.require()
|
||||
val state: StateFlow<NFTCollectionsStateUM> get() = _state
|
||||
|
||||
init {
|
||||
subscribeToNFTCollections()
|
||||
|
|
@ -60,11 +56,15 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
UpdateDataStateTransformer(
|
||||
nftCollections = nftCollections,
|
||||
searchQuery = query,
|
||||
onReceiveClick = ::onReceiveClick,
|
||||
onReceiveClick = {
|
||||
params.onReceiveClick()
|
||||
},
|
||||
onRetryClick = ::onRetryClick,
|
||||
onExpandCollectionClick = ::onExpandCollectionClick,
|
||||
onRetryAssetsClick = ::onRetryAssetsClick,
|
||||
onAssetClick = ::onAssetClick,
|
||||
onAssetClick = { asset, collectionName ->
|
||||
params.onAssetClick(asset, collectionName)
|
||||
},
|
||||
initialSearchBarFactory = ::getInitialSearchBar,
|
||||
).transform(it)
|
||||
}
|
||||
|
|
@ -115,27 +115,6 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
// TODO refresh all
|
||||
}
|
||||
|
||||
private fun onAssetClick(asset: NFTAsset) {
|
||||
router.push(
|
||||
AppRoute.NFTDetails(
|
||||
userWalletId = params.userWalletId,
|
||||
nftAsset = asset,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onReceiveClick() {
|
||||
router.push(
|
||||
AppRoute.NFTReceive(
|
||||
userWalletId = params.userWalletId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
private fun loadCollectionAssets(collection: NFTCollection) {
|
||||
modelScope.launch {
|
||||
fetchNFTCollectionAssetsUseCase(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.nft.collections.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -11,25 +10,19 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconTopBadge
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionAssetsListUM
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionUM
|
||||
import com.tangem.features.nft.common.ui.NFTLogo
|
||||
import com.tangem.features.nft.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -52,7 +45,11 @@ internal fun NFTCollection(state: NFTCollectionUM, modifier: Modifier = Modifier
|
|||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Logo(state)
|
||||
NFTLogo(
|
||||
imageUrl = state.logoUrl,
|
||||
networkIconId = state.networkIconId,
|
||||
background = TangemTheme.colors.background.primary,
|
||||
)
|
||||
|
||||
Text(state)
|
||||
|
||||
|
|
@ -81,46 +78,6 @@ internal fun NFTCollection(state: NFTCollectionUM, modifier: Modifier = Modifier
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Logo(state: NFTCollectionUM) {
|
||||
val networkBadgeOffset = TangemTheme.dimens.spacing6
|
||||
|
||||
Box(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(state.logoUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(radius = TangemTheme.dimens.radius8)
|
||||
},
|
||||
error = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(shape = TangemTheme.shapes.roundedCorners8)
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
)
|
||||
},
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
)
|
||||
CurrencyIconTopBadge(
|
||||
modifier = Modifier
|
||||
.offset(x = networkBadgeOffset, y = -networkBadgeOffset)
|
||||
.align(Alignment.TopEnd),
|
||||
iconResId = state.networkIconId,
|
||||
alpha = 1f,
|
||||
colorFilter = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Text(state: NFTCollectionUM) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
package com.tangem.features.nft.common
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.value.ObserveLifecycleMode
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.nft.collections.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.common.ui.NFTContent
|
||||
import com.tangem.features.nft.component.NFTComponent
|
||||
import com.tangem.features.nft.details.NFTDetailsComponent
|
||||
import com.tangem.features.nft.receive.NFTReceiveComponent
|
||||
import com.tangem.features.nft.traits.NFTAssetTraitsComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultNFTComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: NFTComponent.Params,
|
||||
) : NFTComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val stackNavigation = StackNavigation<NFTRoute>()
|
||||
|
||||
private val innerRouter = InnerRouter<NFTRoute>(
|
||||
stackNavigation = stackNavigation,
|
||||
popCallback = { onChildBack() },
|
||||
)
|
||||
|
||||
private val initialRoute: NFTRoute = NFTRoute.Collections(params.userWalletId)
|
||||
private val currentRoute = MutableStateFlow(initialRoute)
|
||||
|
||||
private val childStack = childStack(
|
||||
key = "sendInnerStack",
|
||||
source = stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = initialRoute,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, factoryContext ->
|
||||
createChild(
|
||||
configuration,
|
||||
childByContext(
|
||||
componentContext = factoryContext,
|
||||
router = innerRouter,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
init {
|
||||
childStack.subscribe(
|
||||
lifecycle = lifecycle,
|
||||
mode = ObserveLifecycleMode.CREATE_DESTROY,
|
||||
) { stack ->
|
||||
componentScope.launch {
|
||||
currentRoute.emit(stack.active.configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val stackState by childStack.subscribeAsState()
|
||||
|
||||
BackHandler(onBack = ::onChildBack)
|
||||
NFTContent(
|
||||
stackState = stackState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createChild(route: NFTRoute, factoryContext: AppComponentContext) = when (route) {
|
||||
is NFTRoute.Collections -> getCollectionsComponent(factoryContext, route)
|
||||
is NFTRoute.Receive -> getReceiveComponent(factoryContext, route)
|
||||
is NFTRoute.Details -> getDetailsComponent(factoryContext, route)
|
||||
is NFTRoute.AssetTraits -> getAssetTraitsComponent(factoryContext, route)
|
||||
}
|
||||
|
||||
private fun getCollectionsComponent(
|
||||
factoryContext: AppComponentContext,
|
||||
route: NFTRoute.Collections,
|
||||
): ComposableContentComponent = NFTCollectionsComponent(
|
||||
context = factoryContext,
|
||||
params = NFTCollectionsComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
onBackClick = ::onChildBack,
|
||||
onReceiveClick = {
|
||||
innerRouter.push(
|
||||
NFTRoute.Receive(
|
||||
userWalletId = route.userWalletId,
|
||||
),
|
||||
)
|
||||
},
|
||||
onAssetClick = { asset, collectionName ->
|
||||
innerRouter.push(
|
||||
NFTRoute.Details(
|
||||
userWalletId = route.userWalletId,
|
||||
nftAsset = asset,
|
||||
collectionName = collectionName,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
private fun getReceiveComponent(
|
||||
factoryContext: AppComponentContext,
|
||||
route: NFTRoute.Receive,
|
||||
): ComposableContentComponent = NFTReceiveComponent(
|
||||
context = factoryContext,
|
||||
params = NFTReceiveComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
onBackClick = ::onChildBack,
|
||||
),
|
||||
)
|
||||
|
||||
private fun getDetailsComponent(
|
||||
factoryContext: AppComponentContext,
|
||||
route: NFTRoute.Details,
|
||||
): ComposableContentComponent = NFTDetailsComponent(
|
||||
context = factoryContext,
|
||||
params = NFTDetailsComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
nftAsset = route.nftAsset,
|
||||
nftCollectionName = route.collectionName,
|
||||
onBackClick = ::onChildBack,
|
||||
onAllTraitsClick = {
|
||||
innerRouter.push(
|
||||
NFTRoute.AssetTraits(
|
||||
nftAsset = route.nftAsset,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
private fun getAssetTraitsComponent(
|
||||
factoryContext: AppComponentContext,
|
||||
route: NFTRoute.AssetTraits,
|
||||
): ComposableContentComponent = NFTAssetTraitsComponent(
|
||||
context = factoryContext,
|
||||
params = NFTAssetTraitsComponent.Params(
|
||||
nftAsset = route.nftAsset,
|
||||
onBackClick = ::onChildBack,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onChildBack() {
|
||||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
|
||||
if (isEmptyStack) {
|
||||
router.pop()
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: NFTComponent.Params): DefaultNFTComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.nft.common
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal sealed class NFTRoute : Route {
|
||||
|
||||
@Serializable
|
||||
data class Collections(
|
||||
val userWalletId: UserWalletId,
|
||||
) : NFTRoute()
|
||||
|
||||
@Serializable
|
||||
data class Receive(
|
||||
val userWalletId: UserWalletId,
|
||||
) : NFTRoute()
|
||||
|
||||
@Serializable
|
||||
data class Details(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val collectionName: String,
|
||||
) : NFTRoute()
|
||||
|
||||
@Serializable
|
||||
data class AssetTraits(
|
||||
val nftAsset: NFTAsset,
|
||||
) : NFTRoute()
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.nft.common.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.nft.common.NFTRoute
|
||||
|
||||
@Composable
|
||||
internal fun NFTContent(stackState: ChildStack<NFTRoute, ComposableContentComponent>) {
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
.background(color = TangemTheme.colors.background.tertiary)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
horizontalAlignment = Alignment.Companion.CenterHorizontally,
|
||||
) {
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
it.instance.Content(Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.features.nft.common.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconTopBadge
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun NFTLogo(imageUrl: String?, @DrawableRes networkIconId: Int, background: Color = Color.Transparent) {
|
||||
val networkBadgeOffset = TangemTheme.dimens.spacing6
|
||||
|
||||
Box(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(imageUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(radius = TangemTheme.dimens.radius8)
|
||||
},
|
||||
error = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(shape = TangemTheme.shapes.roundedCorners8)
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
)
|
||||
},
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
)
|
||||
CurrencyIconTopBadge(
|
||||
modifier = Modifier
|
||||
.offset(x = networkBadgeOffset, y = -networkBadgeOffset)
|
||||
.align(Alignment.TopEnd),
|
||||
iconResId = networkIconId,
|
||||
alpha = 1f,
|
||||
colorFilter = null,
|
||||
background = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,17 +6,18 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.nft.component.NFTDetailsComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.nft.details.model.NFTDetailsModel
|
||||
import com.tangem.features.nft.details.ui.NFTDetails
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNFTDetailsComponent @AssistedInject constructor(
|
||||
internal class NFTDetailsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: NFTDetailsComponent.Params,
|
||||
) : NFTDetailsComponent, AppComponentContext by context {
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val model: NFTDetailsModel = getOrCreateModel(params)
|
||||
|
||||
|
|
@ -27,11 +28,11 @@ internal class DefaultNFTDetailsComponent @AssistedInject constructor(
|
|||
NFTDetails(state, modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTDetailsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NFTDetailsComponent.Params,
|
||||
): DefaultNFTDetailsComponent
|
||||
}
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
val onBackClick: () -> Unit,
|
||||
val onAllTraitsClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.nft.details.block
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.nft.details.block.ui.NFTDetailsBlock
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
class DefaultNFTDetailsBlockComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: NFTDetailsBlockComponent.Params,
|
||||
) : NFTDetailsBlockComponent, AppComponentContext by context {
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
NFTDetailsBlock(
|
||||
assetName = stringReference(params.nftAsset.name.orEmpty()),
|
||||
collectionName = stringReference(params.nftCollectionName),
|
||||
assetImage = params.nftAsset.media?.url,
|
||||
networkIconRes = getActiveIconRes(params.nftAsset.network.id.value),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTDetailsBlockComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NFTDetailsBlockComponent.Params,
|
||||
): DefaultNFTDetailsBlockComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.features.nft.details.block.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.tooling.preview.Preview
|
||||
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.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.nft.common.ui.NFTLogo
|
||||
import com.tangem.features.nft.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun NFTDetailsBlock(
|
||||
assetName: TextReference,
|
||||
collectionName: TextReference,
|
||||
assetImage: String?,
|
||||
networkIconRes: Int,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "NFT Asset",
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
NFTLogo(
|
||||
assetImage,
|
||||
networkIconRes,
|
||||
background = TangemTheme.colors.background.action,
|
||||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = assetName.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = collectionName.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun NFTDetailsBlock_Preview() {
|
||||
TangemThemePreview {
|
||||
NFTDetailsBlock(
|
||||
assetName = stringReference("NFT Asset Name"),
|
||||
collectionName = stringReference("NFT Collection"),
|
||||
assetImage = null,
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -6,7 +6,7 @@ internal data class NFTDetailsUM(
|
|||
val nftAsset: NFTAssetUM,
|
||||
val onBackClick: () -> Unit,
|
||||
val onReadMoreClick: () -> Unit,
|
||||
val onSeeAllClick: () -> Unit,
|
||||
val onSeeAllTraitsClick: () -> Unit,
|
||||
val onExploreClick: () -> Unit,
|
||||
val onSendClick: () -> Unit,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.nft.details.entity
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal data class NFTInfoBottomSheetConfig(
|
||||
val title: TextReference,
|
||||
val text: TextReference,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
package com.tangem.features.nft.details.model
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.nft.component.NFTDetailsComponent
|
||||
import com.tangem.features.nft.details.NFTDetailsComponent
|
||||
import com.tangem.features.nft.details.entity.NFTAssetUM
|
||||
import com.tangem.features.nft.details.entity.NFTDetailsUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
|
|
@ -33,9 +35,9 @@ internal class NFTDetailsModel @Inject constructor(
|
|||
traits = persistentListOf(),
|
||||
baseInfoItems = persistentListOf(),
|
||||
),
|
||||
onBackClick = ::navigateBack,
|
||||
onBackClick = params.onBackClick,
|
||||
onReadMoreClick = ::onReadMoreClick,
|
||||
onSeeAllClick = ::onSeeAllClick,
|
||||
onSeeAllTraitsClick = params.onAllTraitsClick,
|
||||
onExploreClick = ::onExploreClick,
|
||||
onSendClick = ::onSendClick,
|
||||
bottomSheetConfig = null,
|
||||
|
|
@ -46,19 +48,17 @@ internal class NFTDetailsModel @Inject constructor(
|
|||
// TODO implement
|
||||
}
|
||||
|
||||
private fun onSeeAllClick() {
|
||||
// TODO implement
|
||||
}
|
||||
|
||||
private fun onExploreClick() {
|
||||
// TODO implement
|
||||
}
|
||||
|
||||
private fun onSendClick() {
|
||||
// TODO implement
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
router.pop()
|
||||
router.push(
|
||||
AppRoute.NFTSend(
|
||||
userWalletId = params.userWalletId,
|
||||
nftAsset = params.nftAsset,
|
||||
nftCollectionName = params.nftCollectionName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,12 @@ import androidx.compose.ui.Modifier
|
|||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.nft.details.entity.NFTDetailsUM
|
||||
import com.tangem.features.nft.details.entity.NFTInfoBottomSheetConfig
|
||||
import com.tangem.features.nft.details.ui.bottomsheet.NFTInfoBottomSheet
|
||||
import com.tangem.features.nft.impl.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -37,11 +40,12 @@ internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) {
|
|||
NFTDetailsAsset(
|
||||
state = state.nftAsset,
|
||||
onReadMoreClick = state.onReadMoreClick,
|
||||
onSeeAllClick = state.onSeeAllClick,
|
||||
onSeeAllTraitsClick = state.onSeeAllTraitsClick,
|
||||
onExploreClick = state.onExploreClick,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding),
|
||||
)
|
||||
ShowBottomSheet(state.bottomSheetConfig)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
floatingActionButton = {
|
||||
|
|
@ -54,4 +58,12 @@ internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) {
|
|||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
||||
if (bottomSheetConfig == null) return
|
||||
when (bottomSheetConfig.content) {
|
||||
is NFTInfoBottomSheetConfig -> NFTInfoBottomSheet(bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
internal fun NFTDetailsAsset(
|
||||
state: NFTAssetUM,
|
||||
onReadMoreClick: () -> Unit,
|
||||
onSeeAllClick: () -> Unit,
|
||||
onSeeAllTraitsClick: () -> Unit,
|
||||
onExploreClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -72,7 +72,7 @@ internal fun NFTDetailsAsset(
|
|||
NFTBlocksGroupAction(
|
||||
text = resourceReference(R.string.common_see_all),
|
||||
startIcon = { },
|
||||
onClick = onSeeAllClick,
|
||||
onClick = onSeeAllTraitsClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -103,7 +103,7 @@ private fun Preview_NFTDetailsAssetAsset(@PreviewParameter(NFTAssetProvider::cla
|
|||
NFTDetailsAsset(
|
||||
state = state,
|
||||
onReadMoreClick = { },
|
||||
onSeeAllClick = { },
|
||||
onSeeAllTraitsClick = { },
|
||||
onExploreClick = { },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.features.nft.details.ui.bottomsheet
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.nft.details.entity.NFTInfoBottomSheetConfig
|
||||
|
||||
@Composable
|
||||
fun NFTInfoBottomSheet(config: TangemBottomSheetConfig) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
TangemBottomSheet<NFTInfoBottomSheetConfig>(
|
||||
config = config,
|
||||
title = { content ->
|
||||
TangemBottomSheetTitle(title = content.title)
|
||||
},
|
||||
) { content ->
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(scrollState),
|
||||
) {
|
||||
Text(
|
||||
text = content.text.resolveReference(),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_StakingInfoBottomSheet() {
|
||||
TangemThemePreview {
|
||||
NFTInfoBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = NFTInfoBottomSheetConfig(
|
||||
title = stringReference("Title"),
|
||||
text = stringReference(
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius neque vel ligula
|
||||
tincidunt, nec faucibus nulla ultricies. Maecenas euismod arcu in nunc volutpat,
|
||||
at bibendum eros lacinia. Proin hendrerit massa non velit congue,
|
||||
in volutpat nisi consequat. Sed vitae justo nec orci tincidunt malesuada.
|
||||
Nullam feugiat purus vel lectus efficitur, vel fringilla urna volutpat.
|
||||
Donec sagittis enim in metus lacinia, vel tempor nunc bibendum.
|
||||
""".trimIndent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package com.tangem.features.nft
|
||||
package com.tangem.features.nft.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.nft.collections.DefaultNFTCollectionsComponent
|
||||
import com.tangem.features.nft.DefaultNFTFeatureToggles
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.features.nft.collections.model.NFTCollectionsModel
|
||||
import com.tangem.features.nft.component.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.component.NFTDetailsComponent
|
||||
import com.tangem.features.nft.component.NFTReceiveComponent
|
||||
import com.tangem.features.nft.details.DefaultNFTDetailsComponent
|
||||
import com.tangem.features.nft.common.DefaultNFTComponent
|
||||
import com.tangem.features.nft.component.*
|
||||
import com.tangem.features.nft.details.block.DefaultNFTDetailsBlockComponent
|
||||
import com.tangem.features.nft.details.model.NFTDetailsModel
|
||||
import com.tangem.features.nft.receive.DefaultNFTReceiveComponent
|
||||
import com.tangem.features.nft.receive.model.NFTReceiveModel
|
||||
import com.tangem.features.nft.traits.model.NFTAssetTraitsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -36,30 +36,31 @@ internal object NFTFeatureModule {
|
|||
internal interface NFTFeatureModuleBinds {
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTCollectionsComponentFactory(
|
||||
impl: DefaultNFTCollectionsComponent.Factory,
|
||||
): NFTCollectionsComponent.Factory
|
||||
fun bindNFTComponentFactory(impl: DefaultNFTComponent.Factory): NFTComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTCollectionsModel::class)
|
||||
fun bindNFTCollectionsModel(model: NFTCollectionsModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTReceiveComponentFactory(impl: DefaultNFTReceiveComponent.Factory): NFTReceiveComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTReceiveModel::class)
|
||||
fun bindNFTReceiveModel(model: NFTReceiveModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTDetailsComponentFactory(impl: DefaultNFTDetailsComponent.Factory): NFTDetailsComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTDetailsModel::class)
|
||||
fun bindNFTDetailsModel(model: NFTDetailsModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTDetailsBlockComponentFactory(
|
||||
impl: DefaultNFTDetailsBlockComponent.Factory,
|
||||
): NFTDetailsBlockComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTAssetTraitsModel::class)
|
||||
fun bindNFTTraitsModel(model: NFTAssetTraitsModel): Model
|
||||
}
|
||||
|
|
@ -6,17 +6,17 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.nft.component.NFTReceiveComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.nft.receive.model.NFTReceiveModel
|
||||
import com.tangem.features.nft.receive.ui.NFTReceive
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNFTReceiveComponent @AssistedInject constructor(
|
||||
internal class NFTReceiveComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: NFTReceiveComponent.Params,
|
||||
) : NFTReceiveComponent, AppComponentContext by context {
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val model: NFTReceiveModel = getOrCreateModel(params)
|
||||
|
||||
|
|
@ -27,11 +27,8 @@ internal class DefaultNFTReceiveComponent @AssistedInject constructor(
|
|||
NFTReceive(state, modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTReceiveComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NFTReceiveComponent.Params,
|
||||
): DefaultNFTReceiveComponent
|
||||
}
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.nft.receive.model
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
|
|
@ -14,8 +13,8 @@ import com.tangem.domain.nft.GetNFTAvailableNetworksUseCase
|
|||
import com.tangem.domain.nft.GetNFTNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.features.nft.component.NFTReceiveComponent
|
||||
import com.tangem.features.nft.impl.R
|
||||
import com.tangem.features.nft.receive.NFTReceiveComponent
|
||||
import com.tangem.features.nft.receive.entity.NFTReceiveUM
|
||||
import com.tangem.features.nft.receive.entity.transformer.ShowReceiveBottomSheetTransformer
|
||||
import com.tangem.features.nft.receive.entity.transformer.ToggleSearchBarTransformer
|
||||
|
|
@ -31,7 +30,6 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class NFTReceiveModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val searchManager: InputManager,
|
||||
private val getNFTAvailableNetworksUseCase: GetNFTAvailableNetworksUseCase,
|
||||
private val filterNFTAvailableNetworksUseCase: FilterNFTAvailableNetworksUseCase,
|
||||
|
|
@ -41,18 +39,18 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<NFTReceiveUM> get() = _state
|
||||
private val params: NFTReceiveComponent.Params = paramsContainer.require()
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
value = NFTReceiveUM(
|
||||
onBackClick = ::navigateBack,
|
||||
onBackClick = params.onBackClick,
|
||||
search = getInitialSearchBar(),
|
||||
networks = NFTReceiveUM.Networks.Content(persistentListOf()),
|
||||
bottomSheetConfig = null,
|
||||
),
|
||||
)
|
||||
|
||||
private val params: NFTReceiveComponent.Params = paramsContainer.require()
|
||||
val state: StateFlow<NFTReceiveUM> get() = _state
|
||||
|
||||
init {
|
||||
subscribeToNFTAvailableNetworks()
|
||||
|
|
@ -140,8 +138,4 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
private fun onShareClick(text: String) {
|
||||
shareManager.shareText(text = text)
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.nft.traits
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.features.nft.traits.model.NFTAssetTraitsModel
|
||||
import com.tangem.features.nft.traits.ui.NFTAssetTraits
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class NFTAssetTraitsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val model: NFTAssetTraitsModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
NFTAssetTraits(state)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val nftAsset: NFTAsset,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.features.nft.traits.entity
|
||||
|
||||
data class NFTAssetTraitUM(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val value: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.nft.traits.entity
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class NFTAssetTraitsUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val traits: ImmutableList<NFTAssetTraitUM>,
|
||||
)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.nft.traits.model
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.features.nft.traits.NFTAssetTraitsComponent
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitUM
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitsUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class NFTAssetTraitsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: NFTAssetTraitsComponent.Params = paramsContainer.require()
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
value = NFTAssetTraitsUM(
|
||||
traits = params.nftAsset.transform(),
|
||||
onBackClick = { params.onBackClick() },
|
||||
),
|
||||
)
|
||||
|
||||
val state: StateFlow<NFTAssetTraitsUM> get() = _state
|
||||
|
||||
private fun NFTAsset.transform(): ImmutableList<NFTAssetTraitUM> = this
|
||||
.traits
|
||||
.mapIndexed { index, trait ->
|
||||
NFTAssetTraitUM(
|
||||
id = index.toString(),
|
||||
name = trait.name,
|
||||
value = trait.value,
|
||||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.nft.traits.ui
|
||||
|
||||
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.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitUM
|
||||
|
||||
@Composable
|
||||
internal fun NFTAssetTrait(state: NFTAssetTraitUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = state.name,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing4),
|
||||
text = state.value,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.nft.traits.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.nft.impl.R
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitsUM
|
||||
|
||||
@Composable
|
||||
internal fun NFTAssetTraits(state: NFTAssetTraitsUM, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = state.onBackClick)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
topBar = {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
onIconClicked = state.onBackClick,
|
||||
),
|
||||
title = stringResourceSafe(R.string.nft_traits_title),
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
NFTAssetTraitsContent(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.features.nft.traits.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitUM
|
||||
import com.tangem.features.nft.traits.entity.NFTAssetTraitsUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun NFTAssetTraitsContent(state: NFTAssetTraitsUM, modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
modifier = modifier
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
title = null,
|
||||
contentHorizontalPadding = 0.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
state.traits.forEach { trait ->
|
||||
key(trait.id) {
|
||||
NFTAssetTrait(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
state = trait,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NFTDetailsAssetAsset(@PreviewParameter(NFTAssetTraitsProvider::class) state: NFTAssetTraitsUM) {
|
||||
TangemThemePreview {
|
||||
NFTAssetTraitsContent(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class NFTAssetTraitsProvider : CollectionPreviewParameterProvider<NFTAssetTraitsUM>(
|
||||
collection = listOf(
|
||||
NFTAssetTraitsUM(
|
||||
traits = persistentListOf(
|
||||
NFTAssetTraitUM(
|
||||
id = "1",
|
||||
name = "Trait 1",
|
||||
value = "Value",
|
||||
),
|
||||
NFTAssetTraitUM(
|
||||
id = "2",
|
||||
name = "Trait 2",
|
||||
value = "Value",
|
||||
),
|
||||
NFTAssetTraitUM(
|
||||
id = "3",
|
||||
name = "Trait 3",
|
||||
value = "Value",
|
||||
),
|
||||
NFTAssetTraitUM(
|
||||
id = "4",
|
||||
name = "Trait 4",
|
||||
value = "Value",
|
||||
),
|
||||
NFTAssetTraitUM(
|
||||
id = "5",
|
||||
name = "Trait 5",
|
||||
value = "Value",
|
||||
),
|
||||
),
|
||||
onBackClick = { },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -7,14 +7,15 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun WalletCard(url: String?, modifier: Modifier = Modifier) {
|
||||
internal fun WalletCard(artwork: ArtworkUM?, modifier: Modifier = Modifier) {
|
||||
AsyncImage(
|
||||
modifier = modifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(url)
|
||||
.data(artwork?.verifiedArtwork ?: artwork?.defaultUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
placeholder = painterResource(R.drawable.card_placeholder_black),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation
|
|||
import com.arkivanov.decompose.router.stack.replaceAll
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -12,7 +13,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -29,6 +29,7 @@ import com.tangem.features.onboarding.v2.entry.impl.analytics.OnboardingEntryEve
|
|||
import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
|
||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
import com.tangem.features.onboarding.v2.twin.api.OnboardingTwinComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
|
|
@ -150,10 +151,16 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
if (askBiometryFeatureToggles.isAskForBiometryEnabled) {
|
||||
modelScope.launch {
|
||||
if (tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen()) {
|
||||
doIfVisa {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.BiometricScreenOpened)
|
||||
}
|
||||
stackNavigation.replaceAll(
|
||||
OnboardingRoute.AskBiometry(modelCallbacks = AskBiometryModelCallbacks(doneMode)),
|
||||
)
|
||||
} else {
|
||||
doIfVisa {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
|
||||
}
|
||||
stackNavigation.replaceAll(
|
||||
OnboardingRoute.Done(
|
||||
mode = doneMode,
|
||||
|
|
@ -163,6 +170,9 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
doIfVisa {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
|
||||
}
|
||||
stackNavigation.replaceAll(
|
||||
OnboardingRoute.Done(
|
||||
mode = doneMode,
|
||||
|
|
@ -177,6 +187,9 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
) : AskBiometryComponent.ModelCallbacks {
|
||||
override fun onAllowed() {
|
||||
analyticsEventHandler.send(OnboardingEntryEvent.Biometric(OnboardingEntryEvent.Biometric.State.On))
|
||||
doIfVisa {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
|
||||
}
|
||||
stackNavigation.replaceAll(
|
||||
OnboardingRoute.Done(
|
||||
mode = doneMode,
|
||||
|
|
@ -187,6 +200,9 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
|
||||
override fun onDenied() {
|
||||
analyticsEventHandler.send(OnboardingEntryEvent.Biometric(OnboardingEntryEvent.Biometric.State.Off))
|
||||
doIfVisa {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
|
||||
}
|
||||
stackNavigation.replaceAll(
|
||||
OnboardingRoute.Done(
|
||||
mode = doneMode,
|
||||
|
|
@ -232,6 +248,12 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun doIfVisa(function: () -> Unit) {
|
||||
if (params.scanResponse.productType == ProductType.Visa) {
|
||||
function()
|
||||
}
|
||||
}
|
||||
|
||||
inner class OnboardingTwinModelCallbacks : OnboardingTwinComponent.ModelCallbacks {
|
||||
override fun onDone() {
|
||||
navigateToFinalScreenFlow()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.features.onboarding.v2.common.ui.interruptBackupDialog
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -78,18 +80,26 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
backups.collectLatest { backup ->
|
||||
when {
|
||||
_uiState.value.artwork2Url == null && backup.card2 != null -> {
|
||||
_uiState.value.artwork2 == null && backup.card2 != null -> {
|
||||
val artwork =
|
||||
getCardImageUseCase.invoke(backup.card2.cardId, backup.card2.cardPublicKey)
|
||||
getCardImageUseCase.invoke(
|
||||
cardId = backup.card2.cardId,
|
||||
cardPublicKey = backup.card2.cardPublicKey,
|
||||
size = ArtworkSize.LARGE,
|
||||
)
|
||||
_uiState.update {
|
||||
it.copy(artwork2Url = artwork)
|
||||
it.copy(artwork2 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl))
|
||||
}
|
||||
}
|
||||
_uiState.value.artwork3Url == null && backup.card3 != null -> {
|
||||
_uiState.value.artwork3 == null && backup.card3 != null -> {
|
||||
val artwork =
|
||||
getCardImageUseCase.invoke(backup.card3.cardId, backup.card3.cardPublicKey)
|
||||
getCardImageUseCase.invoke(
|
||||
cardId = backup.card3.cardId,
|
||||
cardPublicKey = backup.card3.cardPublicKey,
|
||||
size = ArtworkSize.LARGE,
|
||||
)
|
||||
_uiState.update {
|
||||
it.copy(artwork3Url = artwork)
|
||||
it.copy(artwork3 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,12 +166,14 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
|||
private fun loadCardArtwork() {
|
||||
modelScope.launch {
|
||||
val artwork =
|
||||
getCardImageUseCase.invoke(params.scanResponse.card.cardId, params.scanResponse.card.cardPublicKey)
|
||||
getCardImageUseCase.invoke(
|
||||
cardId = params.scanResponse.card.cardId,
|
||||
cardPublicKey = params.scanResponse.card.cardPublicKey,
|
||||
size = ArtworkSize.LARGE,
|
||||
)
|
||||
|
||||
if (artwork != getCardImageUseCase.getDefaultFallbackUrl()) {
|
||||
_uiState.update {
|
||||
it.copy(artwork1Url = artwork)
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(artwork1 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ internal fun OnboardingMultiWallet(
|
|||
) {
|
||||
if (isSeedPhraseState.not()) {
|
||||
WalletArtworks(
|
||||
url1 = state.artwork1Url,
|
||||
url2 = state.artwork2Url,
|
||||
url3 = state.artwork3Url,
|
||||
artwork1 = state.artwork1,
|
||||
artwork2 = state.artwork2,
|
||||
artwork3 = state.artwork3,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 34.dp)
|
||||
.padding(top = 20.dp)
|
||||
|
|
@ -53,7 +53,7 @@ internal fun OnboardingMultiWallet(
|
|||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingMultiWallet(
|
||||
state = OnboardingMultiWalletUM(artwork1Url = null),
|
||||
state = OnboardingMultiWalletUM(artwork1 = null),
|
||||
artworksState = WalletArtworksState.Folded,
|
||||
isSeedPhraseState = false,
|
||||
childContent = { md ->
|
||||
|
|
@ -73,7 +73,7 @@ private fun Preview() {
|
|||
private fun Preview2() {
|
||||
TangemThemePreview {
|
||||
OnboardingMultiWallet(
|
||||
state = OnboardingMultiWalletUM(artwork1Url = null),
|
||||
state = OnboardingMultiWalletUM(artwork1 = null),
|
||||
artworksState = WalletArtworksState.Folded,
|
||||
isSeedPhraseState = true,
|
||||
childContent = { md ->
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.common.ui.WalletCard
|
||||
|
|
@ -57,9 +58,9 @@ sealed class WalletArtworksState {
|
|||
|
||||
@Composable
|
||||
fun WalletArtworks(
|
||||
url1: String?,
|
||||
url2: String?,
|
||||
url3: String?,
|
||||
artwork1: ArtworkUM?,
|
||||
artwork2: ArtworkUM?,
|
||||
artwork3: ArtworkUM?,
|
||||
state: WalletArtworksState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -120,9 +121,9 @@ fun WalletArtworks(
|
|||
}
|
||||
|
||||
AnimatedWalletCards(
|
||||
url1 = url1,
|
||||
url2 = url2,
|
||||
url3 = url3,
|
||||
artwork1 = artwork1,
|
||||
artwork2 = artwork2,
|
||||
artwork3 = artwork3,
|
||||
modifier = Modifier
|
||||
.widthIn(max = 450.dp)
|
||||
.matchParentSize(),
|
||||
|
|
@ -152,9 +153,9 @@ private data class WalletCardTransitionState(
|
|||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun AnimatedWalletCards(
|
||||
url1: String?,
|
||||
url2: String?,
|
||||
url3: String?,
|
||||
artwork1: ArtworkUM?,
|
||||
artwork2: ArtworkUM?,
|
||||
artwork3: ArtworkUM?,
|
||||
transition1: Transition<WalletCardTransitionState>,
|
||||
transition2: Transition<WalletCardTransitionState>,
|
||||
transition3: Transition<WalletCardTransitionState>,
|
||||
|
|
@ -199,7 +200,7 @@ private fun AnimatedWalletCards(
|
|||
scaleY = scaleY3
|
||||
rotationZ = rotation3
|
||||
},
|
||||
url = url3,
|
||||
artwork = artwork3,
|
||||
)
|
||||
|
||||
WalletCard(
|
||||
|
|
@ -217,7 +218,7 @@ private fun AnimatedWalletCards(
|
|||
scaleX = scaleX2
|
||||
scaleY = scaleY2
|
||||
},
|
||||
url = url2,
|
||||
artwork = artwork2,
|
||||
)
|
||||
|
||||
WalletCard(
|
||||
|
|
@ -234,7 +235,7 @@ private fun AnimatedWalletCards(
|
|||
translationY = translationY1
|
||||
rotationZ = rotation1
|
||||
},
|
||||
url = url1,
|
||||
artwork = artwork1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -447,9 +448,9 @@ private fun Preview() {
|
|||
var state: WalletArtworksState by remember { mutableStateOf(WalletArtworksState.Folded) }
|
||||
|
||||
WalletArtworks(
|
||||
url1 = null,
|
||||
url2 = null,
|
||||
url3 = null,
|
||||
artwork1 = null,
|
||||
artwork2 = null,
|
||||
artwork3 = null,
|
||||
state = state,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state
|
||||
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
|
||||
internal data class OnboardingMultiWalletUM(
|
||||
val artwork1Url: String? = null,
|
||||
val artwork2Url: String? = null,
|
||||
val artwork3Url: String? = null,
|
||||
val artwork1: ArtworkUM? = null,
|
||||
val artwork2: ArtworkUM? = null,
|
||||
val artwork3: ArtworkUM? = null,
|
||||
)
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
|
|
@ -93,9 +94,11 @@ internal class OnboardingNoteCreateWalletModel @Inject constructor(
|
|||
|
||||
private fun observeArtwork() {
|
||||
modelScope.launch {
|
||||
params.childParams.commonState.collect {
|
||||
params.childParams.commonState.collect { state ->
|
||||
_uiState.value = _uiState.value.copy(
|
||||
artworkUrl = it.cardArtworkUrl,
|
||||
artwork = state.cardArtwork?.let {
|
||||
ArtworkUM(it.verifiedArtwork, it.defaultUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
|
|||
verticalArrangement = Arrangement.Bottom,
|
||||
) {
|
||||
WalletArtworks(
|
||||
url1 = state.artworkUrl,
|
||||
url2 = null,
|
||||
url3 = null,
|
||||
artwork1 = state.artwork,
|
||||
artwork2 = null,
|
||||
artwork3 = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 64.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
|
|
@ -86,7 +86,7 @@ private fun OnboardingNoteCreatePreview() {
|
|||
TangemThemePreview {
|
||||
OnboardingNoteCreateWallet(
|
||||
state = OnboardingNoteCreateWalletUM(
|
||||
artworkUrl = null,
|
||||
artwork = null,
|
||||
createWalletInProgress = false,
|
||||
onCreateClick = {},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.onboarding.v2.note.impl.child.create.ui.state
|
||||
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
|
||||
data class OnboardingNoteCreateWalletUM(
|
||||
val artworkUrl: String? = null,
|
||||
val artwork: ArtworkUM? = null,
|
||||
val createWalletInProgress: Boolean = false,
|
||||
val onCreateClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -129,7 +129,7 @@ internal class OnboardingNoteTopUpModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
params.childParams.commonState.collect {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
cardArtworkUrl = it.cardArtworkUrl,
|
||||
cardArtwork = it.cardArtwork,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalTangemShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -21,11 +22,12 @@ import com.tangem.features.onboarding.v2.common.ui.RefreshButton
|
|||
import com.tangem.features.onboarding.v2.common.ui.WalletCard
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun OnboardingNoteTopUpHeader(
|
||||
balance: String,
|
||||
cardArtworkUrl: String?,
|
||||
cardArtwork: ArtworkUM?,
|
||||
isRefreshing: Boolean,
|
||||
onRefreshBalanceClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -77,7 +79,7 @@ fun OnboardingNoteTopUpHeader(
|
|||
}
|
||||
WalletCard(
|
||||
modifier = Modifier.width(120.dp).align(Alignment.TopCenter),
|
||||
url = cardArtworkUrl,
|
||||
artwork = cardArtwork,
|
||||
)
|
||||
RefreshButton(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
|
|
@ -93,7 +95,7 @@ private fun OnboardinNoteTopUpHeaderPreview() {
|
|||
TangemThemePreview {
|
||||
OnboardingNoteTopUpHeader(
|
||||
balance = "0.00000001 BTC",
|
||||
cardArtworkUrl = "",
|
||||
cardArtwork = ArtworkUM(null as ImmutableList<Byte>?, ""),
|
||||
onRefreshBalanceClick = {},
|
||||
isRefreshing = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ fun OnboardingNoteTopUp(state: OnboardingNoteTopUpUM, modifier: Modifier = Modif
|
|||
) {
|
||||
OnboardingNoteTopUpHeader(
|
||||
balance = state.balance,
|
||||
cardArtworkUrl = state.cardArtworkUrl,
|
||||
cardArtwork = state.cardArtwork,
|
||||
onRefreshBalanceClick = state.onRefreshBalanceClick,
|
||||
isRefreshing = state.isRefreshing,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state
|
||||
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
||||
data class OnboardingNoteTopUpUM(
|
||||
val cardArtworkUrl: String? = null,
|
||||
val cardArtwork: ArtworkUM? = null,
|
||||
val availableForBuy: Boolean = false,
|
||||
val availableForBuyLoading: Boolean = true,
|
||||
val balance: String = "",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.onboarding.v2.note.impl.model
|
||||
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -7,6 +8,6 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
internal data class OnboardingNoteCommonState(
|
||||
val scanResponse: ScanResponse,
|
||||
val userWallet: UserWallet? = null,
|
||||
val cardArtworkUrl: String? = null,
|
||||
val cardArtwork: ArtworkUM? = null,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus? = null,
|
||||
)
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.features.onboarding.v2.common.ui.exitOnboardingDialog
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent
|
|||
import com.tangem.features.onboarding.v2.note.impl.OnboardingNoteInnerNavigationState
|
||||
import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute
|
||||
import com.tangem.features.onboarding.v2.note.impl.route.stepNum
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -50,7 +52,7 @@ internal class OnboardingNoteModel @Inject constructor(
|
|||
val commonUiState: MutableStateFlow<OnboardingNoteCommonState> = _uiState
|
||||
|
||||
init {
|
||||
loadArtworkUrl()
|
||||
loadArtwork()
|
||||
}
|
||||
|
||||
fun updateStepForNewRoute(route: OnboardingNoteRoute) {
|
||||
|
|
@ -85,15 +87,16 @@ internal class OnboardingNoteModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadArtworkUrl() {
|
||||
private fun loadArtwork() {
|
||||
modelScope.launch {
|
||||
val cardInfo = params.scanResponse.card
|
||||
val url = getCardImageUseCase(
|
||||
val artwork = getCardImageUseCase.invoke(
|
||||
cardId = cardInfo.cardId,
|
||||
cardPublicKey = cardInfo.cardPublicKey,
|
||||
size = ArtworkSize.LARGE,
|
||||
)
|
||||
_uiState.update {
|
||||
it.copy(cardArtworkUrl = url)
|
||||
it.copy(cardArtwork = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.common.TapWorkarounds.isVisa
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
|
@ -40,7 +41,13 @@ internal class DefaultOnboardingStepperComponent @AssistedInject constructor(
|
|||
|
||||
componentScope.launch {
|
||||
val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch
|
||||
sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
sendFeedbackEmailUseCase(
|
||||
if (params.scanResponse.card.isVisa) {
|
||||
FeedbackEmailType.Visa.Activation(cardInfo)
|
||||
} else {
|
||||
FeedbackEmailType.DirectUserRequest(cardInfo)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.util.fastForEach
|
|||
import androidx.compose.ui.zIndex
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -212,7 +213,7 @@ private fun AnimatedTwinCards(
|
|||
scaleX = scaleX2
|
||||
scaleY = scaleY2
|
||||
},
|
||||
url = Artwork.TWIN_CARD_2_URL,
|
||||
artwork = ArtworkUM(defaultUrl = Artwork.TWIN_CARD_2_URL),
|
||||
)
|
||||
|
||||
WalletCard(
|
||||
|
|
@ -229,7 +230,7 @@ private fun AnimatedTwinCards(
|
|||
translationY = translationY1
|
||||
rotationZ = rotation1
|
||||
},
|
||||
url = Artwork.TWIN_CARD_1_URL,
|
||||
artwork = ArtworkUM(defaultUrl = Artwork.TWIN_CARD_1_URL),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,9 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
params = OnboardingVisaOtherWalletComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = {
|
||||
model.stackNavigation.pushNew(OnboardingVisaRoute.PinCode(activationOrderInfo = it))
|
||||
model.stackNavigation.pushNew(
|
||||
OnboardingVisaRoute.PinCode(activationOrderInfo = it, pinCodeValidationError = false),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -176,6 +178,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
config = OnboardingVisaPinCodeComponent.Config(
|
||||
scanResponse = model.currentScanResponse.value,
|
||||
activationOrderInfo = route.activationOrderInfo,
|
||||
wasValidationError = route.pinCodeValidationError,
|
||||
),
|
||||
params = OnboardingVisaPinCodeComponent.Params(
|
||||
childParams = childParams,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -19,6 +20,9 @@ import com.tangem.domain.visa.repository.VisaActivationRepository
|
|||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
|
||||
|
|
@ -30,6 +34,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
||||
|
|
@ -40,6 +45,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require()
|
||||
|
|
@ -59,6 +65,10 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
val onBack = MutableSharedFlow<Unit>()
|
||||
val onDone = MutableSharedFlow<ActivationReadyEvent>()
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.SettingAccessCodeStarted)
|
||||
}
|
||||
|
||||
fun onBack() {
|
||||
if (uiState.value.buttonLoading) return
|
||||
|
||||
|
|
@ -82,6 +92,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
_uiState.update {
|
||||
it.copy(
|
||||
accessCodeFirst = textFieldValue,
|
||||
accessCodeSecond = TextFieldValue(),
|
||||
atLeastMinCharsError = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -99,11 +110,14 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
private fun onContinue() {
|
||||
when (uiState.value.step) {
|
||||
OnboardingVisaAccessCodeUM.Step.Enter -> {
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.AccessCodeEntered)
|
||||
if (checkAccessCodeMinChars().not()) return
|
||||
_uiState.update { it.copy(step = OnboardingVisaAccessCodeUM.Step.ReEnter) }
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.AccessCodeReenterScreen)
|
||||
}
|
||||
OnboardingVisaAccessCodeUM.Step.ReEnter -> {
|
||||
if (checkAccessCodesMatch().not()) return
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.OnboardingVisa)
|
||||
startActivationProcess(accessCode = uiState.value.accessCodeFirst.text)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,6 +174,9 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
is CompletionResult.Failure -> {
|
||||
loading(false)
|
||||
uiMessageSender.showErrorDialog(result.error.universalError)
|
||||
analyticsEventsHandler.send(
|
||||
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> result.data
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -15,6 +16,9 @@ import com.tangem.domain.visa.model.VisaDataForApprove
|
|||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -32,6 +36,7 @@ internal class OnboardingVisaApproveModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<OnboardingVisaApproveComponent.Config>()
|
||||
|
|
@ -47,6 +52,10 @@ internal class OnboardingVisaApproveModel @Inject constructor(
|
|||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.WalletPrepare)
|
||||
}
|
||||
|
||||
private fun getInitialState(): OnboardingVisaApproveUM {
|
||||
return OnboardingVisaApproveUM(
|
||||
onApproveClick = ::onApproveClick,
|
||||
|
|
@ -56,6 +65,8 @@ internal class OnboardingVisaApproveModel @Inject constructor(
|
|||
private fun onApproveClick() {
|
||||
loading(true)
|
||||
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonApprove)
|
||||
|
||||
modelScope.launch {
|
||||
val dataToSign = runCatching {
|
||||
visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request)
|
||||
|
|
@ -77,6 +88,9 @@ internal class OnboardingVisaApproveModel @Inject constructor(
|
|||
is CompletionResult.Failure -> {
|
||||
loading(false)
|
||||
uiMessageSender.showErrorDialog(result.error.universalError)
|
||||
analyticsEventHandler.send(
|
||||
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> result.data
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.features.onboarding.v2.impl.R
|
|||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent.Params.Event
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.OnboardingVisaChooseWalletUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.SelectableChainRowUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -21,6 +23,7 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class OnboardingVisaChooseWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
|
@ -53,7 +56,18 @@ internal class OnboardingVisaChooseWalletModel @Inject constructor(
|
|||
private fun onContinueClicked() {
|
||||
modelScope.launch {
|
||||
val selectedOption = _uiState.value.selectedOption ?: return@launch
|
||||
val value = getAnalyticsValue(selectedOption.event)
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ChooseWalletScreen(value))
|
||||
onEvent.emit(selectedOption.event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnalyticsValue(event: Event) = when (event) {
|
||||
Event.TangemWallet -> {
|
||||
"Tangem"
|
||||
}
|
||||
Event.OtherWallet -> {
|
||||
"Other"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -41,6 +43,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<Config>()
|
||||
|
|
@ -53,6 +56,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
val onDone = MutableSharedFlow<Params.DoneEvent>()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ActivationInProgressScreen)
|
||||
modelScope.launch {
|
||||
while (true) {
|
||||
val result = runCatching {
|
||||
|
|
@ -71,9 +75,10 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
VisaActivationRemoteState.WaitingForActivationFinishing,
|
||||
-> {
|
||||
}
|
||||
is VisaActivationRemoteState.WaitingPinCode -> {
|
||||
navigateToPinCode(result.activationOrderInfo)
|
||||
return@launch
|
||||
is VisaActivationRemoteState.AwaitingPinCode -> {
|
||||
navigateToPinCodeIfNeeded(result) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
VisaActivationRemoteState.Activated -> {
|
||||
finishActivation()
|
||||
|
|
@ -86,8 +91,37 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateToPinCode(activationOrderInfo: VisaActivationOrderInfo) {
|
||||
onDone.emit(Params.DoneEvent.NavigateTo(OnboardingVisaRoute.PinCode(activationOrderInfo)))
|
||||
private suspend inline fun navigateToPinCodeIfNeeded(
|
||||
remoteState: VisaActivationRemoteState.AwaitingPinCode,
|
||||
returnBlock: () -> Unit,
|
||||
) {
|
||||
when (remoteState.status) {
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode -> {
|
||||
onDone.emit(
|
||||
Params.DoneEvent.NavigateTo(
|
||||
OnboardingVisaRoute.PinCode(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
pinCodeValidationError = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
returnBlock()
|
||||
}
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WasError -> {
|
||||
onDone.emit(
|
||||
Params.DoneEvent.NavigateTo(
|
||||
OnboardingVisaRoute.PinCode(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
pinCodeValidationError = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
returnBlock()
|
||||
}
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.InProgress -> {
|
||||
/** waiting for new state */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun finishActivation() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.domain.visa.model.VisaCardId
|
|||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -29,6 +31,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val config = paramsContainer.require<OnboardingVisaOtherWalletComponent.Config>()
|
||||
|
|
@ -44,13 +47,14 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
|||
val onDone = MutableSharedFlow<VisaActivationOrderInfo>()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.GoToWebsiteOpened)
|
||||
modelScope.launch {
|
||||
while (true) {
|
||||
val result = runCatching {
|
||||
visaActivationRepository.getActivationRemoteStateLongPoll()
|
||||
}.getOrNull()
|
||||
|
||||
if (result is VisaActivationRemoteState.WaitingPinCode) {
|
||||
if (result is VisaActivationRemoteState.AwaitingPinCode) {
|
||||
onDone.emit(result.activationOrderInfo)
|
||||
break
|
||||
}
|
||||
|
|
@ -68,10 +72,12 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onShareClicked() {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonShareLink)
|
||||
shareManager.shareText("https://tangem.com/") // TODO
|
||||
}
|
||||
|
||||
private fun onOpenInBrowserClicked() {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonBrowser)
|
||||
urlOpener.openUrl("https://tangem.com/") // TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ internal class OnboardingVisaPinCodeComponent(
|
|||
data class Config(
|
||||
val scanResponse: ScanResponse,
|
||||
val activationOrderInfo: VisaActivationOrderInfo,
|
||||
val wasValidationError: Boolean,
|
||||
)
|
||||
|
||||
data class Params(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -17,6 +18,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -37,6 +39,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
private val authTokenStorage: VisaAuthTokenStorage,
|
||||
private val otpStorage: VisaAuthTokenStorage,
|
||||
private val setVisaPinCodeUseCase: SetVisaPinCodeUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<OnboardingVisaPinCodeComponent.Config>()
|
||||
|
|
@ -49,11 +52,20 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.PinCodeScreenOpened)
|
||||
}
|
||||
|
||||
private fun getInitialState(): OnboardingVisaPinCodeUM {
|
||||
return OnboardingVisaPinCodeUM(
|
||||
submitButtonEnabled = false,
|
||||
onPinCodeChange = ::onPinCodeChange,
|
||||
onSubmitClick = ::onSubmitClick,
|
||||
error = if (params.wasValidationError) {
|
||||
resourceReference(R.string.visa_onboarding_pin_not_accepted)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +78,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
pinCode = pin,
|
||||
submitButtonEnabled = PinCodeValidation.validate(pin),
|
||||
error = if (isError) {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ErrorPinValidation)
|
||||
resourceReference(R.string.visa_onboarding_pin_validation_error_message)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -76,6 +89,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onSubmitClick() {
|
||||
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.PinEntered)
|
||||
val pinCode = _uiState.value.pinCode
|
||||
if (PinCodeValidation.validate(pinCode).not()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -14,6 +15,9 @@ import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
|
|||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.Config
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.DoneEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
|
|
@ -33,6 +37,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<Config>()
|
||||
|
|
@ -42,6 +47,10 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
|
|||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<DoneEvent>()
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.ActivationScreenOpened)
|
||||
}
|
||||
|
||||
private fun getInitialState(): OnboardingVisaWelcomeUM {
|
||||
return OnboardingVisaWelcomeUM(
|
||||
mode = if (params is Config.WelcomeBack) {
|
||||
|
|
@ -54,6 +63,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onContinueClick() {
|
||||
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.ButtonActivate)
|
||||
if (params !is Config.WelcomeBack) {
|
||||
modelScope.launch { onDone.emit(DoneEvent.WelcomeDone) }
|
||||
return
|
||||
|
|
@ -86,6 +96,9 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
|
|||
is CompletionResult.Failure -> {
|
||||
loading(false)
|
||||
uiMessageSender.showErrorDialog(result.error.universalError)
|
||||
analyticsEventsHandler.send(
|
||||
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> result.data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal sealed class OnboardingVisaAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Onboarding / Visa", event, params) {
|
||||
|
||||
data object ActivationScreenOpened : OnboardingVisaAnalyticsEvent(
|
||||
event = "Activation Screen Opened",
|
||||
)
|
||||
|
||||
data object ButtonActivate : OnboardingVisaAnalyticsEvent(
|
||||
event = "Button - Activate",
|
||||
)
|
||||
|
||||
data object SettingAccessCodeStarted : OnboardingVisaAnalyticsEvent(
|
||||
event = "Setting Access Code Started",
|
||||
)
|
||||
|
||||
data object AccessCodeEntered : OnboardingVisaAnalyticsEvent(
|
||||
event = "Access Code Entered",
|
||||
)
|
||||
|
||||
data object AccessCodeReenterScreen : OnboardingVisaAnalyticsEvent(
|
||||
event = "Access Code Re-enter Screen",
|
||||
)
|
||||
|
||||
data object OnboardingVisa : OnboardingVisaAnalyticsEvent(
|
||||
event = "Onboarding / Visa",
|
||||
)
|
||||
|
||||
data class ChooseWalletScreen(
|
||||
val type: String,
|
||||
) : OnboardingVisaAnalyticsEvent(
|
||||
event = "Choose Wallet Screen",
|
||||
params = mapOf("Type" to type),
|
||||
)
|
||||
|
||||
data object WalletPrepare : OnboardingVisaAnalyticsEvent(
|
||||
event = "Wallet Prepare",
|
||||
)
|
||||
|
||||
data object ButtonApprove : OnboardingVisaAnalyticsEvent(
|
||||
event = "Button - Approve",
|
||||
)
|
||||
|
||||
data object GoToWebsiteOpened : OnboardingVisaAnalyticsEvent(
|
||||
event = "Go To Website Opened",
|
||||
)
|
||||
|
||||
data object ButtonBrowser : OnboardingVisaAnalyticsEvent(
|
||||
event = "Button - Browser",
|
||||
)
|
||||
|
||||
data object ButtonShareLink : OnboardingVisaAnalyticsEvent(
|
||||
event = "Button - Share Link",
|
||||
)
|
||||
|
||||
data object ActivationInProgressScreen : OnboardingVisaAnalyticsEvent(
|
||||
event = "Activation In Progress Screen",
|
||||
)
|
||||
|
||||
data object PinCodeScreenOpened : OnboardingVisaAnalyticsEvent(
|
||||
event = "PIN Code Screen Opened",
|
||||
)
|
||||
|
||||
data object PinEntered : OnboardingVisaAnalyticsEvent(
|
||||
event = "PIN Entered",
|
||||
params = mapOf("Type" to "Visa"),
|
||||
)
|
||||
|
||||
data object BiometricScreenOpened : OnboardingVisaAnalyticsEvent(
|
||||
event = "Biometric Screen Opened",
|
||||
)
|
||||
|
||||
data object SuccessScreenOpened : OnboardingVisaAnalyticsEvent(
|
||||
event = "Success Screen Opened",
|
||||
)
|
||||
|
||||
data object ErrorPinValidation : OnboardingVisaAnalyticsEvent(
|
||||
event = "Error - Pin Validation",
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal const val ONBOARDING_SOURCE = "Onboarding"
|
||||
internal const val MAIN_SOURCE = "Main"
|
||||
|
||||
internal sealed class VisaAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Visa", event, params) {
|
||||
|
||||
data class Errors(
|
||||
val errorCode: String,
|
||||
val source: String,
|
||||
) : VisaAnalyticsEvent(
|
||||
event = "Errors",
|
||||
params = mapOf(
|
||||
"Error Code" to errorCode,
|
||||
"Source" to source,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -159,8 +159,11 @@ internal class OnboardingVisaModel @Inject constructor(
|
|||
VisaActivationRemoteState.WaitingForActivationFinishing -> {
|
||||
OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode)
|
||||
}
|
||||
is VisaActivationRemoteState.WaitingPinCode -> {
|
||||
OnboardingVisaRoute.PinCode(activationOrderInfo = remoteState.activationOrderInfo)
|
||||
is VisaActivationRemoteState.AwaitingPinCode -> {
|
||||
OnboardingVisaRoute.PinCode(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
pinCodeValidationError = false,
|
||||
)
|
||||
}
|
||||
VisaActivationRemoteState.Activated,
|
||||
VisaActivationRemoteState.BlockedForActivation,
|
||||
|
|
|
|||
|
|
@ -48,5 +48,8 @@ internal sealed class OnboardingVisaRoute {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class PinCode(val activationOrderInfo: VisaActivationOrderInfo) : OnboardingVisaRoute()
|
||||
data class PinCode(
|
||||
val activationOrderInfo: VisaActivationOrderInfo,
|
||||
val pinCodeValidationError: Boolean,
|
||||
) : OnboardingVisaRoute()
|
||||
}
|
||||
|
|
@ -16,4 +16,5 @@ dependencies {
|
|||
/** Domain models */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.send.v2.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface NFTSendComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTSendComponent>
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
/** Api */
|
||||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.txhistory.api)
|
||||
implementation(projects.features.nft.api)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.crypto)
|
||||
|
|
@ -54,6 +55,7 @@ dependencies {
|
|||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.nft.models)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -1,35 +1,34 @@
|
|||
package com.tangem.features.send.v2.send
|
||||
package com.tangem.features.send.v2.common
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class SendRoute : Route {
|
||||
internal sealed class CommonSendRoute : Route {
|
||||
|
||||
abstract val isEditMode: Boolean
|
||||
|
||||
@Serializable
|
||||
data object Empty : SendRoute() {
|
||||
data object Empty : CommonSendRoute() {
|
||||
override val isEditMode = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Confirm : SendRoute() {
|
||||
data object Confirm : CommonSendRoute() {
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Destination(
|
||||
override val isEditMode: Boolean,
|
||||
) : SendRoute()
|
||||
) : CommonSendRoute()
|
||||
|
||||
@Serializable
|
||||
data class Amount(
|
||||
override val isEditMode: Boolean,
|
||||
) : SendRoute()
|
||||
) : CommonSendRoute()
|
||||
|
||||
@Serializable
|
||||
data class Fee(
|
||||
override val isEditMode: Boolean = true,
|
||||
) : SendRoute()
|
||||
data object Fee : CommonSendRoute() {
|
||||
override val isEditMode: Boolean = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.send.v2.common
|
||||
|
||||
sealed class PredefinedValues {
|
||||
data object Empty : PredefinedValues()
|
||||
|
||||
sealed class Content : PredefinedValues() {
|
||||
abstract val amount: String
|
||||
abstract val address: String
|
||||
abstract val memo: String?
|
||||
|
||||
data class Deeplink(
|
||||
override val amount: String,
|
||||
override val address: String,
|
||||
override val memo: String?,
|
||||
val transactionId: String,
|
||||
) : Content()
|
||||
|
||||
data class QrCode(
|
||||
override val amount: String,
|
||||
override val address: String,
|
||||
override val memo: String?,
|
||||
) : Content()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.tangem.features.send.v2.common
|
||||
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.txhistory.TxHistoryFeatureToggles
|
||||
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SendBalanceUpdater @AssistedInject constructor(
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val updateDelayedNetworkStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val txHistoryFeatureToggles: TxHistoryFeatureToggles,
|
||||
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val cryptoCurrency: CryptoCurrency,
|
||||
) {
|
||||
fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
networks = setOf(cryptoCurrency.network),
|
||||
)
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
updateTxHistory()
|
||||
},
|
||||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNetworkStatuses(delay: Long = BALANCE_UPDATE_DELAY) {
|
||||
updateDelayedNetworkStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrency.network,
|
||||
delayMillis = delay,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateTxHistory() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
)
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
if (txHistoryFeatureToggles.isFeatureEnabled) {
|
||||
txHistoryContentUpdateEmitter.triggerUpdate()
|
||||
} else {
|
||||
getTxHistoryItemsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(cryptoCurrency: CryptoCurrency, userWallet: UserWallet): SendBalanceUpdater
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.send.v2.common
|
||||
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
|
||||
internal interface SendNavigationModelCallback {
|
||||
fun onNavigationResult(navigationUM: NavigationUM)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.send.v2.send.ui
|
||||
package com.tangem.features.send.v2.common.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -13,12 +13,14 @@ import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.v2.send.SendRoute
|
||||
import com.tangem.features.send.v2.common.NavigationUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
|
||||
@Composable
|
||||
internal fun SendContent(state: SendUM, stackState: ChildStack<SendRoute, ComposableContentComponent>) {
|
||||
internal fun SendContent(
|
||||
navigationUM: NavigationUM,
|
||||
stackState: ChildStack<CommonSendRoute, ComposableContentComponent>,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
.background(color = TangemTheme.colors.background.tertiary)
|
||||
|
|
@ -27,7 +29,7 @@ internal fun SendContent(state: SendUM, stackState: ChildStack<SendRoute, Compos
|
|||
.systemBarsPadding(),
|
||||
horizontalAlignment = Alignment.Companion.CenterHorizontally,
|
||||
) {
|
||||
SendAppBar(navigationUM = state.navigationUM)
|
||||
SendAppBar(navigationUM = navigationUM)
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
|
|
@ -35,7 +37,7 @@ internal fun SendContent(state: SendUM, stackState: ChildStack<SendRoute, Compos
|
|||
) {
|
||||
it.instance.Content(Modifier.weight(1f))
|
||||
}
|
||||
SendNavigationButtons(navigationUM = state.navigationUM)
|
||||
SendNavigationButtons(navigationUM = navigationUM)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.send.v2.send.ui
|
||||
package com.tangem.features.send.v2.common.ui
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -29,7 +29,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
|||
import com.tangem.features.send.v2.send.ui.state.ButtonsUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.v2.common.NavigationUM
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(navigationUM: NavigationUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.features.send.v2.common.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val TAP_HELP_KEY = "TAP_HELP_KEY"
|
||||
private const val TAP_HELP_ANIMATION_DELAY = 500L
|
||||
|
||||
internal fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modifier) {
|
||||
item(key = TAP_HELP_KEY) {
|
||||
var wrappedIsDisplay by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(key1 = isDisplay) {
|
||||
delay(TAP_HELP_ANIMATION_DELAY)
|
||||
wrappedIsDisplay = isDisplay
|
||||
}
|
||||
|
||||
if (wrappedIsDisplay) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.animateItem()
|
||||
.padding(top = TangemTheme.dimens.spacing20),
|
||||
) {
|
||||
val background = TangemTheme.colors.button.secondary
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_send_hint_shape_12),
|
||||
tint = TangemTheme.colors.button.secondary,
|
||||
contentDescription = null,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.send_summary_tap_hint),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(background)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.send.v2.send.confirm.ui.state
|
||||
package com.tangem.features.send.v2.common.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.send.v2.common
|
||||
package com.tangem.features.send.v2.common.ui.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
|
@ -2,8 +2,6 @@ package com.tangem.features.send.v2.di
|
|||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel
|
||||
import com.tangem.features.send.v2.send.model.SendModel
|
||||
import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountModel
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationModel
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeModel
|
||||
|
|
@ -16,12 +14,7 @@ import dagger.multibindings.IntoMap
|
|||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface SendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendModel::class)
|
||||
fun provideSendModel(model: SendModel): Model
|
||||
internal interface CommonSendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
|
@ -38,11 +31,6 @@ internal interface SendModelModule {
|
|||
@ClassKey(SendFeeModel::class)
|
||||
fun provideSendFeeModel(model: SendFeeModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendConfirmModel::class)
|
||||
fun provideSendConfirmModel(model: SendConfirmModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NotificationsModel::class)
|
||||
|
|
@ -2,9 +2,11 @@ package com.tangem.features.send.v2.di
|
|||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.send.v2.DefaultSendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.send.DefaultSendComponent
|
||||
import com.tangem.features.send.v2.sendnft.DefaultNFTSendComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -29,4 +31,8 @@ internal interface SendFeatureModuleBinds {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun provideSendComponentFactory(impl: DefaultSendComponent.Factory): SendComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideNFTSendComponentFactory(impl: DefaultNFTSendComponent.Factory): NFTSendComponent.Factory
|
||||
}
|
||||
|
|
@ -19,11 +19,13 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.ui.SendContent
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
|
||||
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.model.SendModel
|
||||
import com.tangem.features.send.v2.send.ui.SendContent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent
|
||||
|
|
@ -44,17 +46,17 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : SendComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val stackNavigation = StackNavigation<SendRoute>()
|
||||
private val stackNavigation = StackNavigation<CommonSendRoute>()
|
||||
|
||||
private val innerRouter = InnerRouter<SendRoute>(
|
||||
private val innerRouter = InnerRouter<CommonSendRoute>(
|
||||
stackNavigation = stackNavigation,
|
||||
popCallback = { onChildBack() },
|
||||
)
|
||||
|
||||
private val initialRoute = if (params.amount == null) {
|
||||
SendRoute.Destination(isEditMode = false)
|
||||
CommonSendRoute.Destination(isEditMode = false)
|
||||
} else {
|
||||
SendRoute.Empty
|
||||
CommonSendRoute.Empty
|
||||
}
|
||||
private val currentRoute = MutableStateFlow(initialRoute)
|
||||
|
||||
|
|
@ -112,47 +114,61 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
|
||||
BackHandler(onBack = ::onChildBack)
|
||||
SendContent(
|
||||
state = state,
|
||||
navigationUM = state.navigationUM,
|
||||
stackState = stackState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createChild(route: SendRoute, factoryContext: AppComponentContext) = when (route) {
|
||||
SendRoute.Empty -> getStubComponent()
|
||||
is SendRoute.Destination -> getDestinationComponent(factoryContext, route)
|
||||
is SendRoute.Amount -> getAmountComponent(factoryContext, route)
|
||||
is SendRoute.Fee -> getFeeComponent(factoryContext)
|
||||
SendRoute.Confirm -> getConfirmComponent(factoryContext)
|
||||
private fun createChild(route: CommonSendRoute, factoryContext: AppComponentContext) = when (route) {
|
||||
CommonSendRoute.Empty -> getStubComponent()
|
||||
is CommonSendRoute.Destination -> getDestinationComponent(factoryContext, route)
|
||||
is CommonSendRoute.Amount -> getAmountComponent(factoryContext, route)
|
||||
is CommonSendRoute.Fee -> getFeeComponent(factoryContext)
|
||||
CommonSendRoute.Confirm -> getConfirmComponent(factoryContext)
|
||||
}
|
||||
|
||||
private fun getDestinationComponent(factoryContext: AppComponentContext, route: SendRoute) =
|
||||
private fun getDestinationComponent(factoryContext: AppComponentContext, route: CommonSendRoute) =
|
||||
SendDestinationComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendDestinationComponentParams.DestinationParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
currentRoute = currentRoute.filterIsInstance<SendRoute.Destination>(),
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY,
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.currency,
|
||||
callback = model,
|
||||
isEditMode = route.isEditMode,
|
||||
onBackClick = ::onChildBack,
|
||||
onNextClick = {
|
||||
if (route.isEditMode) {
|
||||
onChildBack()
|
||||
} else {
|
||||
innerRouter.push(CommonSendRoute.Amount(isEditMode = false))
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
private fun getAmountComponent(factoryContext: AppComponentContext, route: SendRoute) = SendAmountComponent(
|
||||
private fun getAmountComponent(factoryContext: AppComponentContext, route: CommonSendRoute) = SendAmountComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendAmountComponentParams.AmountParams(
|
||||
state = model.uiState.value.amountUM,
|
||||
currentRoute = currentRoute.filterIsInstance<SendRoute.Amount>(),
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Amount>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY,
|
||||
userWallet = model.userWallet,
|
||||
appCurrency = model.appCurrency,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
callback = model,
|
||||
isEditMode = route.isEditMode,
|
||||
predefinedAmountValue = model.predefinedAmountValue,
|
||||
predefinedValues = model.predefinedValues,
|
||||
onBackClick = ::onChildBack,
|
||||
onNextClick = {
|
||||
if (route.isEditMode) {
|
||||
onChildBack()
|
||||
} else {
|
||||
innerRouter.push(CommonSendRoute.Confirm)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -165,7 +181,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
appComponentContext = factoryContext,
|
||||
params = SendFeeComponentParams.FeeParams(
|
||||
state = model.uiState.value.feeUM,
|
||||
currentRoute = currentRoute.filterIsInstance<SendRoute.Fee>(),
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Fee>(),
|
||||
analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY,
|
||||
userWallet = model.userWallet,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
|
|
@ -174,6 +190,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
sendAmount = sendAmount,
|
||||
destinationAddress = destinationAddress,
|
||||
callback = model,
|
||||
onNextClick = ::onChildBack,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -187,21 +204,21 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
val predefinedAddress = params.destinationAddress
|
||||
val predefinedValues =
|
||||
if (predefinedAmount != null && predefinedTxId != null && predefinedAddress != null) {
|
||||
SendConfirmComponent.Params.PredefinedValues.Content(
|
||||
PredefinedValues.Content.Deeplink(
|
||||
amount = predefinedAmount,
|
||||
address = predefinedAddress,
|
||||
tag = params.tag,
|
||||
memo = params.tag,
|
||||
transactionId = predefinedTxId,
|
||||
)
|
||||
} else {
|
||||
SendConfirmComponent.Params.PredefinedValues.Empty
|
||||
PredefinedValues.Empty
|
||||
}
|
||||
return SendConfirmComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendConfirmComponent.Params(
|
||||
state = model.uiState.value,
|
||||
userWallet = model.userWallet,
|
||||
currentRoute = currentRoute.filterIsInstance<SendRoute.Confirm>(),
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Confirm>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
|
|
@ -216,7 +233,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
private fun getStubComponent() = ComposableContentComponent { }
|
||||
|
||||
private fun onChildBack() {
|
||||
val isEmptyRoute = childStack.value.active.configuration == SendRoute.Empty
|
||||
val isEmptyRoute = childStack.value.active.configuration == CommonSendRoute.Empty
|
||||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success
|
||||
|
||||
|
|
|
|||
|
|
@ -11,18 +11,20 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.v2.send.SendRoute
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel
|
||||
import com.tangem.features.send.v2.send.confirm.ui.SendConfirmContent
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
|
|
@ -38,15 +40,13 @@ internal class SendConfirmComponent(
|
|||
private val destinationBlockComponent =
|
||||
SendDestinationBlockComponent(
|
||||
appComponentContext = child("sendConfirmDestinationBlock"),
|
||||
params = SendDestinationComponentParams.DestinationBlockParams(
|
||||
params = DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
|
||||
predefinedAddressValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.address,
|
||||
predefinedMemoValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.tag,
|
||||
predefinedValues = params.predefinedValues,
|
||||
),
|
||||
onResult = model::onDestinationResult,
|
||||
onClick = model::showEditDestination,
|
||||
|
|
@ -61,8 +61,7 @@ internal class SendConfirmComponent(
|
|||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
|
||||
predefinedAmountValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.amount,
|
||||
predefinedValues = params.predefinedValues,
|
||||
),
|
||||
onResult = model::onAmountResult,
|
||||
onClick = model::showEditAmount,
|
||||
|
|
@ -77,8 +76,8 @@ internal class SendConfirmComponent(
|
|||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
sendAmount = model.enteredAmount.orZero(),
|
||||
destinationAddress = model.enteredDestination.orEmpty(),
|
||||
sendAmount = model.confirmData.enteredAmount.orZero(),
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
|
|
@ -93,13 +92,15 @@ internal class SendConfirmComponent(
|
|||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
destinationAddress = model.enteredDestination.orEmpty(),
|
||||
memo = model.enteredMemo,
|
||||
amountValue = model.enteredAmount.orZero(),
|
||||
reduceAmountBy = model.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = model.isIgnoreReduce,
|
||||
fee = model.fee,
|
||||
feeError = model.feeError,
|
||||
notificationData = NotificationData(
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
memo = model.confirmData.enteredMemo,
|
||||
amountValue = model.confirmData.enteredAmount.orZero(),
|
||||
reduceAmountBy = model.confirmData.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = model.confirmData.isIgnoreReduce,
|
||||
fee = model.confirmData.fee,
|
||||
feeError = model.confirmData.feeError,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -140,20 +141,10 @@ internal class SendConfirmComponent(
|
|||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val appCurrency: AppCurrency,
|
||||
val callback: ModelCallback,
|
||||
val currentRoute: Flow<SendRoute.Confirm>,
|
||||
val currentRoute: Flow<CommonSendRoute.Confirm>,
|
||||
val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
val predefinedValues: PredefinedValues,
|
||||
) {
|
||||
sealed class PredefinedValues {
|
||||
data object Empty : PredefinedValues()
|
||||
data class Content(
|
||||
val transactionId: String,
|
||||
val amount: String,
|
||||
val address: String,
|
||||
val tag: String?,
|
||||
) : PredefinedValues()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(sendUM: SendUM)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class ConfirmData(
|
||||
val enteredAmount: BigDecimal?,
|
||||
val reduceAmountBy: BigDecimal,
|
||||
val isIgnoreReduce: Boolean,
|
||||
val enteredDestination: String?,
|
||||
val enteredMemo: String?,
|
||||
val fee: Fee?,
|
||||
val feeError: GetFeeError?,
|
||||
)
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -25,20 +24,17 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.features.send.v2.common.NavigationUM
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.SendBalanceUpdater
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.SendRoute
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents.SendScreenSource
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticHelper
|
||||
|
|
@ -47,7 +43,6 @@ import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmIn
|
|||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSendingStateTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSentStateTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.ui.state.ButtonsUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
|
||||
|
|
@ -58,17 +53,13 @@ import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
|||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.features.txhistory.TxHistoryFeatureToggles
|
||||
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
|
|
@ -89,21 +80,15 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val sendFeeCheckReloadTrigger: SendFeeCheckReloadTrigger,
|
||||
private val sendFeeCheckReloadListener: SendFeeCheckReloadListener,
|
||||
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
|
||||
private val notificationsUpdateTrigger: NotificationsUpdateTrigger,
|
||||
private val alertFactory: SendConfirmAlertFactory,
|
||||
private val sendAnalyticHelper: SendAnalyticHelper,
|
||||
private val txHistoryFeatureToggles: TxHistoryFeatureToggles,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
|
||||
) : Model(), SendConfirmClickIntents {
|
||||
|
||||
private val params: SendConfirmComponent.Params = paramsContainer.require()
|
||||
|
|
@ -113,6 +98,8 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val cryptoCurrencyStatus = params.cryptoCurrencyStatus
|
||||
private val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
private val sendBalanceUpdater = sendBalanceUpdaterFactory.create(cryptoCurrency, userWallet)
|
||||
|
||||
private val _uiState = MutableStateFlow(params.state)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
|
|
@ -125,20 +112,16 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val feeSelectorUM
|
||||
get() = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
|
||||
val enteredAmount: BigDecimal?
|
||||
get() = amountState?.amountTextField?.cryptoAmount?.value
|
||||
val reduceAmountBy: BigDecimal
|
||||
get() = amountState?.reduceAmountBy.orZero()
|
||||
val isIgnoreReduce: Boolean
|
||||
get() = amountState?.isIgnoreReduce == true
|
||||
val enteredDestination: String?
|
||||
get() = destinationUM?.addressTextField?.value
|
||||
val enteredMemo: String?
|
||||
get() = destinationUM?.memoTextField?.value
|
||||
val fee: Fee?
|
||||
get() = feeSelectorUM?.selectedFee
|
||||
val feeError: GetFeeError?
|
||||
get() = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error
|
||||
val confirmData: ConfirmData
|
||||
get() = ConfirmData(
|
||||
enteredAmount = amountState?.amountTextField?.cryptoAmount?.value,
|
||||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
reduceAmountBy = amountState?.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = amountState?.isIgnoreReduce == true,
|
||||
enteredDestination = destinationUM?.addressTextField?.value,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
)
|
||||
|
||||
private var sendIdleTimer: Long = 0L
|
||||
private var isAmountSubtractAvailable = false
|
||||
|
|
@ -183,7 +166,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address))
|
||||
router.push(SendRoute.Destination(isEditMode = true))
|
||||
router.push(CommonSendRoute.Destination(isEditMode = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +178,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount))
|
||||
router.push(SendRoute.Amount(isEditMode = true))
|
||||
router.push(CommonSendRoute.Amount(isEditMode = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +190,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee))
|
||||
router.push(SendRoute.Fee())
|
||||
router.push(CommonSendRoute.Fee)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,15 +219,15 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
override fun onFailedTxEmailClick(errorMessage: String) {
|
||||
val amountValue = amountState?.amountTextField?.cryptoAmount?.value
|
||||
val feeValue = fee?.amount?.value
|
||||
val feeValue = confirmData.fee?.amount?.value
|
||||
|
||||
val receivingAmount = if (amountValue != null && feeValue != null) {
|
||||
checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = enteredAmount.orZero(),
|
||||
amountValue = confirmData.enteredAmount.orZero(),
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
reduceAmountBy = confirmData.reduceAmountBy,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -257,7 +240,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
errorMessage = errorMessage,
|
||||
blockchainId = cryptoCurrency.network.id.value,
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
destinationAddress = enteredDestination.orEmpty(),
|
||||
destinationAddress = confirmData.enteredDestination.orEmpty(),
|
||||
tokenSymbol = if (amount?.type is AmountType.Token) {
|
||||
amount.currencySymbol
|
||||
} else {
|
||||
|
|
@ -322,7 +305,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy.orZero(),
|
||||
reduceAmountBy = confirmData.reduceAmountBy.orZero(),
|
||||
)
|
||||
|
||||
modelScope.launch {
|
||||
|
|
@ -369,7 +352,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
ifRight = {
|
||||
updateTransactionStatus(txData)
|
||||
addTokenToWalletIfNeeded()
|
||||
scheduleUpdates()
|
||||
sendBalanceUpdater.scheduleUpdates()
|
||||
sendAnalyticHelper.sendSuccessAnalytics(cryptoCurrency, uiState.value)
|
||||
},
|
||||
)
|
||||
|
|
@ -380,7 +363,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
val wallets = destinationUM?.wallets ?: return
|
||||
|
||||
val receivingUserWallet = wallets
|
||||
.firstOrNull { it.address == enteredDestination }
|
||||
.firstOrNull { it.address == confirmData.enteredDestination }
|
||||
?: return
|
||||
|
||||
val userWalletId = receivingUserWallet.userWalletId ?: return
|
||||
|
|
@ -403,49 +386,6 @@ internal class SendConfirmModel @Inject constructor(
|
|||
_uiState.update(SendConfirmSentStateTransformer(txData, txUrl))
|
||||
}
|
||||
|
||||
private fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrency.network))
|
||||
},
|
||||
// we should update tx history and network for new balance
|
||||
async {
|
||||
updateTxHistory()
|
||||
},
|
||||
async {
|
||||
updateDelayedCurrencyStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrency.network,
|
||||
delayMillis = BALANCE_UPDATE_DELAY,
|
||||
refresh = true,
|
||||
)
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateTxHistory() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
)
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
if (txHistoryFeatureToggles.isFeatureEnabled) {
|
||||
txHistoryContentUpdateEmitter.triggerUpdate()
|
||||
} else {
|
||||
getTxHistoryItemsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCheckFeeResultUpdates() {
|
||||
sendFeeCheckReloadListener.checkReloadResultFlow.onEach { isFeeResultSuccess ->
|
||||
if (isFeeResultSuccess) {
|
||||
|
|
@ -462,13 +402,13 @@ internal class SendConfirmModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
notificationsUpdateTrigger.triggerUpdate(
|
||||
data = NotificationData(
|
||||
destinationAddress = enteredDestination.orEmpty(),
|
||||
memo = enteredMemo,
|
||||
amountValue = enteredAmount.orZero(),
|
||||
reduceAmountBy = reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = isIgnoreReduce,
|
||||
fee = fee,
|
||||
feeError = feeError,
|
||||
destinationAddress = confirmData.enteredDestination.orEmpty(),
|
||||
memo = confirmData.enteredMemo,
|
||||
amountValue = confirmData.enteredAmount.orZero(),
|
||||
reduceAmountBy = confirmData.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = confirmData.isIgnoreReduce,
|
||||
fee = confirmData.fee,
|
||||
feeError = confirmData.feeError,
|
||||
),
|
||||
)
|
||||
_uiState.update {
|
||||
|
|
@ -554,6 +494,5 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
private companion object {
|
||||
const val CHECK_FEE_UPDATE_DELAY = 10_000L
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SendConfirmSentStateTransformer(
|
||||
val txData: TransactionData.Uncompiled,
|
||||
val txUrl: String,
|
||||
private val txData: TransactionData.Uncompiled,
|
||||
private val txUrl: String,
|
||||
) : Transformer<SendUM> {
|
||||
override fun transform(prevState: SendUM): SendUM {
|
||||
return prevState.copy(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.checkIfFeeTooHigh
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
|
||||
|
|
|
|||
|
|
@ -5,20 +5,15 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
|
|
@ -26,12 +21,16 @@ import com.tangem.core.ui.components.Keyboard
|
|||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
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.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.tapHelp
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
|
||||
|
|
@ -39,11 +38,8 @@ import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
|||
import com.tangem.features.send.v2.subcomponents.notifications
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val TAP_HELP_KEY = "TAP_HELP_KEY"
|
||||
private const val BLOCKS_KEY = "BLOCKS_KEY"
|
||||
private const val TAP_HELP_ANIMATION_DELAY = 500L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -148,45 +144,4 @@ private fun LazyListScope.blocks(
|
|||
feeBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modifier) {
|
||||
item(key = TAP_HELP_KEY) {
|
||||
var wrappedIsDisplay by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(key1 = isDisplay) {
|
||||
delay(TAP_HELP_ANIMATION_DELAY)
|
||||
wrappedIsDisplay = isDisplay
|
||||
}
|
||||
|
||||
if (wrappedIsDisplay) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.animateItem()
|
||||
.padding(top = TangemTheme.dimens.spacing20),
|
||||
) {
|
||||
val background = TangemTheme.colors.button.secondary
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_send_hint_shape_12),
|
||||
tint = TangemTheme.colors.button.secondary,
|
||||
contentDescription = null,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.send_summary_tap_hint),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(background)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.send.v2.send.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel
|
||||
import com.tangem.features.send.v2.send.model.SendModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface CommonSendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendModel::class)
|
||||
fun provideSendModel(model: SendModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendConfirmModel::class)
|
||||
fun provideSendConfirmModel(model: SendConfirmModel): Model
|
||||
}
|
||||
|
|
@ -29,11 +29,12 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.common.NavigationUM
|
||||
import com.tangem.features.send.v2.send.SendRoute
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmAlertFactory
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmAlertFactory
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent
|
||||
|
|
@ -90,7 +91,7 @@ internal class SendModel @Inject constructor(
|
|||
var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
var appCurrency: AppCurrency = AppCurrency.Default
|
||||
var predefinedAmountValue: String? = null
|
||||
var predefinedValues: PredefinedValues = PredefinedValues.Empty
|
||||
|
||||
private var balanceHidingJobHolder = JobHolder()
|
||||
|
||||
|
|
@ -214,7 +215,7 @@ internal class SendModel @Inject constructor(
|
|||
feeCryptoCurrencyStatus = feeCurrencyStatus
|
||||
|
||||
if (params.amount != null) {
|
||||
router.replaceAll(SendRoute.Confirm)
|
||||
router.replaceAll(CommonSendRoute.Confirm)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +228,11 @@ internal class SendModel @Inject constructor(
|
|||
|
||||
private fun onQrCodeScanned(address: String) {
|
||||
val parsedQrCode = parseQrCodeUseCase(address, cryptoCurrency).getOrNull()
|
||||
predefinedAmountValue = parsedQrCode?.amount?.parseBigDecimal(cryptoCurrency.decimals)
|
||||
predefinedValues = PredefinedValues.Content.QrCode(
|
||||
amount = parsedQrCode?.amount?.parseBigDecimal(cryptoCurrency.decimals).orEmpty(),
|
||||
address = parsedQrCode?.address.orEmpty(),
|
||||
memo = parsedQrCode?.memo,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onFailedTxEmailClick(errorMessage: String? = null) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.send.v2.send.ui.state
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.features.send.v2.common.NavigationUM
|
||||
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.state.NavigationUM
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
package com.tangem.features.send.v2.sendnft
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.value.ObserveLifecycleMode
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.ui.SendContent
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.sendnft.confirm.NFTSendConfirmComponent
|
||||
import com.tangem.features.send.v2.sendnft.model.NFTSendModel
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultNFTSendComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: NFTSendComponent.Params,
|
||||
private val nftDetailsBlockComponentFactory: NFTDetailsBlockComponent.Factory,
|
||||
) : NFTSendComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val stackNavigation = StackNavigation<CommonSendRoute>()
|
||||
|
||||
private val innerRouter = InnerRouter<CommonSendRoute>(
|
||||
stackNavigation = stackNavigation,
|
||||
popCallback = { onChildBack() },
|
||||
)
|
||||
|
||||
private val initialRoute = CommonSendRoute.Empty
|
||||
private val currentRouteFlow = MutableStateFlow<CommonSendRoute>(initialRoute)
|
||||
|
||||
private val model: NFTSendModel = getOrCreateModel(params = params, router = innerRouter)
|
||||
|
||||
private val childStack = childStack(
|
||||
key = "NFTSendInnerStack",
|
||||
source = stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = initialRoute,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, factoryContext ->
|
||||
createChild(
|
||||
configuration,
|
||||
childByContext(
|
||||
componentContext = factoryContext,
|
||||
router = innerRouter,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
init {
|
||||
childStack.subscribe(
|
||||
lifecycle = lifecycle,
|
||||
mode = ObserveLifecycleMode.CREATE_DESTROY,
|
||||
) { stack ->
|
||||
componentScope.launch {
|
||||
when (val activeComponent = stack.active.instance) {
|
||||
is NFTSendConfirmComponent -> if (currentRouteFlow.value.isEditMode) {
|
||||
// analyticsEventHandler.send(SendAnalyticEvents.ConfirmationScreenOpened)
|
||||
activeComponent.updateState(model.uiState.value)
|
||||
}
|
||||
is SendDestinationComponent -> {
|
||||
// analyticsEventHandler.send(SendAnalyticEvents.AddressScreenOpened)
|
||||
activeComponent.updateState(model.uiState.value.destinationUM)
|
||||
}
|
||||
is SendFeeComponent -> {
|
||||
// analyticsEventHandler.send(SendAnalyticEvents.FeeScreenOpened)
|
||||
}
|
||||
}
|
||||
currentRouteFlow.emit(stack.active.configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val stackState by childStack.subscribeAsState()
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = ::onChildBack)
|
||||
SendContent(
|
||||
navigationUM = state.navigationUM,
|
||||
stackState = stackState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createChild(route: CommonSendRoute, factoryContext: AppComponentContext) = when (route) {
|
||||
is CommonSendRoute.Destination -> getDestinationComponent(factoryContext, route)
|
||||
is CommonSendRoute.Fee -> getFeeComponent(factoryContext)
|
||||
CommonSendRoute.Confirm -> getConfirmComponent(factoryContext)
|
||||
else -> getStubComponent()
|
||||
}
|
||||
|
||||
private fun getDestinationComponent(factoryContext: AppComponentContext, route: CommonSendRoute) =
|
||||
SendDestinationComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendDestinationComponentParams.DestinationParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
currentRoute = currentRouteFlow.filterIsInstance<CommonSendRoute.Destination>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = "", // todo
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = model.cryptoCurrency,
|
||||
callback = model,
|
||||
onBackClick = ::onChildBack,
|
||||
onNextClick = {
|
||||
if (route.isEditMode) {
|
||||
onChildBack()
|
||||
} else {
|
||||
innerRouter.push(CommonSendRoute.Confirm)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
return if (destinationAddress != null) {
|
||||
SendFeeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendFeeComponentParams.FeeParams(
|
||||
state = model.uiState.value.feeUM,
|
||||
currentRoute = currentRouteFlow.filterIsInstance<CommonSendRoute.Fee>(),
|
||||
analyticsCategoryName = "", // todo
|
||||
userWallet = model.userWallet,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatus,
|
||||
appCurrency = model.appCurrency,
|
||||
sendAmount = BigDecimal.ZERO,
|
||||
destinationAddress = destinationAddress,
|
||||
callback = model,
|
||||
onNextClick = ::onChildBack,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
getStubComponent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfirmComponent(factoryContext: AppComponentContext) = NFTSendConfirmComponent(
|
||||
appComponentContext = factoryContext,
|
||||
nftDetailsBlockComponentFactory = nftDetailsBlockComponentFactory,
|
||||
params = NFTSendConfirmComponent.Params(
|
||||
state = model.uiState.value,
|
||||
analyticsCategoryName = "", // todo
|
||||
userWallet = model.userWallet,
|
||||
nftAsset = params.nftAsset,
|
||||
nftCollectionName = params.nftCollectionName,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatus,
|
||||
appCurrency = model.appCurrency,
|
||||
callback = model,
|
||||
currentRoute = currentRouteFlow.filterIsInstance<CommonSendRoute.Confirm>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
),
|
||||
)
|
||||
|
||||
private fun getStubComponent() = ComposableContentComponent { }
|
||||
|
||||
private fun onChildBack() {
|
||||
val isEmptyRoute = childStack.value.active.configuration == CommonSendRoute.Empty
|
||||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success
|
||||
|
||||
if (isEmptyRoute || isEmptyStack || isSuccess) {
|
||||
router.pop()
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTSendComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: NFTSendComponent.Params): DefaultNFTSendComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.tangem.features.send.v2.sendnft.confirm
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.sendnft.confirm.model.NFTSendConfirmModel
|
||||
import com.tangem.features.send.v2.sendnft.confirm.ui.NFTSendConfirmContent
|
||||
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class NFTSendConfirmComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
nftDetailsBlockComponentFactory: NFTDetailsBlockComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: NFTSendConfirmModel = getOrCreateModel(params = params)
|
||||
|
||||
private val blockClickEnableFlow = MutableStateFlow(false)
|
||||
|
||||
private val destinationBlockComponent =
|
||||
SendDestinationBlockComponent(
|
||||
appComponentContext = child("NFTSendConfirmDestinationBlock"),
|
||||
params = DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
predefinedValues = PredefinedValues.Empty,
|
||||
),
|
||||
onResult = model::onDestinationResult,
|
||||
onClick = model::showEditDestination,
|
||||
)
|
||||
|
||||
private val feeBlockComponent = SendFeeBlockComponent(
|
||||
appComponentContext = child("NFTSendConfirmFeeBlock"),
|
||||
params = SendFeeComponentParams.FeeBlockParams(
|
||||
state = model.uiState.value.feeUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWallet = params.userWallet,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
sendAmount = BigDecimal.ZERO,
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
onClick = model::showEditFee,
|
||||
)
|
||||
|
||||
private val nftDetailsBlockComponent = nftDetailsBlockComponentFactory.create(
|
||||
context = child("NFTDetailsBlock"),
|
||||
params = NFTDetailsBlockComponent.Params(
|
||||
userWalletId = params.userWallet.walletId,
|
||||
nftAsset = params.nftAsset,
|
||||
nftCollectionName = params.nftCollectionName,
|
||||
),
|
||||
)
|
||||
|
||||
private val notificationsComponent = NotificationsComponent(
|
||||
appComponentContext = child("NFTSendConfirmNotifications"),
|
||||
params = NotificationsComponent.Params(
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
notificationData = NotificationData(
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
memo = model.confirmData.enteredMemo,
|
||||
amountValue = BigDecimal.ZERO,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
isIgnoreReduce = false,
|
||||
fee = model.confirmData.fee,
|
||||
feeError = model.confirmData.feeError,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
model.uiState.onEach { state ->
|
||||
val confirmUM = state.confirmUM as? ConfirmUM.Content
|
||||
blockClickEnableFlow.value = confirmUM?.isSending == false
|
||||
}.launchIn(componentScope)
|
||||
}
|
||||
|
||||
fun updateState(state: NFTSendUM) {
|
||||
destinationBlockComponent.updateState(state.destinationUM)
|
||||
feeBlockComponent.updateState(state.feeUM)
|
||||
model.updateState(state)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val notificationState by notificationsComponent.state.collectAsStateWithLifecycle()
|
||||
|
||||
NFTSendConfirmContent(
|
||||
nftSendUM = state,
|
||||
destinationBlockComponent = destinationBlockComponent,
|
||||
feeBlockComponent = feeBlockComponent,
|
||||
nftDetailsBlockComponent = nftDetailsBlockComponent,
|
||||
notificationsComponent = notificationsComponent,
|
||||
notificationsUM = notificationState,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val state: NFTSendUM,
|
||||
val analyticsCategoryName: String,
|
||||
val userWallet: UserWallet,
|
||||
val appCurrency: AppCurrency,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val callback: ModelCallback,
|
||||
val currentRoute: Flow<CommonSendRoute.Confirm>,
|
||||
val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(nftSendUM: NFTSendUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.send.v2.sendnft.confirm.model
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
|
||||
data class ConfirmData(
|
||||
val enteredDestination: String?,
|
||||
val enteredMemo: String?,
|
||||
val fee: Fee?,
|
||||
val feeError: GetFeeError?,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.send.v2.sendnft.confirm.model
|
||||
|
||||
internal interface NFTSendConfirmClickIntents {
|
||||
|
||||
fun showEditDestination()
|
||||
|
||||
fun showEditFee()
|
||||
|
||||
fun onSendClick()
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
fun onShareClick()
|
||||
|
||||
fun onFailedTxEmailClick(errorMessage: String)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue