Updated on 2026-08-14
This commit is contained in:
commit
04c1325ca0
120 changed files with 3503 additions and 316 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 */ },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.nft.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
|
||||
interface NFTAssetTraitsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val nftAsset: NFTAsset,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTAssetTraitsComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.nft.component
|
||||
|
||||
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 NFTDetailsBlockComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTDetailsBlockComponent>
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ interface NFTDetailsComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
val nftCollectionName: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NFTDetailsComponent>
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
@ -105,12 +105,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 +121,7 @@ internal class UpdateDataStateTransformer(
|
|||
is NFTSalePrice.Value -> NFTSalePriceUM.Content(salePrice.value.toString())
|
||||
},
|
||||
onItemClick = {
|
||||
onAssetClick(this)
|
||||
onAssetClick(this, collectionName)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -115,11 +115,12 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
// TODO refresh all
|
||||
}
|
||||
|
||||
private fun onAssetClick(asset: NFTAsset) {
|
||||
private fun onAssetClick(asset: NFTAsset, collectionName: String) {
|
||||
router.push(
|
||||
AppRoute.NFTDetails(
|
||||
userWalletId = params.userWalletId,
|
||||
nftAsset = asset,
|
||||
collectionName = collectionName,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,5 +1,6 @@
|
|||
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
|
||||
|
|
@ -9,7 +10,8 @@ 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
|
||||
|
|
@ -35,7 +37,7 @@ internal class NFTDetailsModel @Inject constructor(
|
|||
),
|
||||
onBackClick = ::navigateBack,
|
||||
onReadMoreClick = ::onReadMoreClick,
|
||||
onSeeAllClick = ::onSeeAllClick,
|
||||
onSeeAllTraitsClick = ::onSeeAllTraitsClick,
|
||||
onExploreClick = ::onExploreClick,
|
||||
onSendClick = ::onSendClick,
|
||||
bottomSheetConfig = null,
|
||||
|
|
@ -46,8 +48,12 @@ internal class NFTDetailsModel @Inject constructor(
|
|||
// TODO implement
|
||||
}
|
||||
|
||||
private fun onSeeAllClick() {
|
||||
// TODO implement
|
||||
private fun onSeeAllTraitsClick() {
|
||||
router.push(
|
||||
AppRoute.NFTAssetTraits(
|
||||
nftAsset = params.nftAsset,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onExploreClick() {
|
||||
|
|
@ -56,6 +62,13 @@ internal class NFTDetailsModel @Inject constructor(
|
|||
|
||||
private fun onSendClick() {
|
||||
// TODO implement
|
||||
// router.push(
|
||||
// AppRoute.NFTSend(
|
||||
// userWalletId = params.userWalletId,
|
||||
// nftAsset = params.nftAsset,
|
||||
// nftCollectionName = params.nftCollectionName,
|
||||
// ),
|
||||
// )
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
|
|
|
|||
|
|
@ -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,23 @@
|
|||
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.DefaultNFTFeatureToggles
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.features.nft.collections.DefaultNFTCollectionsComponent
|
||||
import com.tangem.features.nft.collections.model.NFTCollectionsModel
|
||||
import com.tangem.features.nft.component.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.nft.component.NFTDetailsComponent
|
||||
import com.tangem.features.nft.component.NFTReceiveComponent
|
||||
import com.tangem.features.nft.component.NFTAssetTraitsComponent
|
||||
import com.tangem.features.nft.details.DefaultNFTDetailsComponent
|
||||
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.DefaultNFTAssetTraitsComponent
|
||||
import com.tangem.features.nft.traits.model.NFTAssetTraitsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -62,4 +69,19 @@ internal interface NFTFeatureModuleBinds {
|
|||
@IntoMap
|
||||
@ClassKey(NFTDetailsModel::class)
|
||||
fun bindNFTDetailsModel(model: NFTDetailsModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTDetailsBlockComponentFactory(
|
||||
impl: DefaultNFTDetailsBlockComponent.Factory,
|
||||
): NFTDetailsBlockComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTTraitsComponentFactory(impl: DefaultNFTAssetTraitsComponent.Factory): NFTAssetTraitsComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTAssetTraitsModel::class)
|
||||
fun bindNFTTraitsModel(model: NFTAssetTraitsModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
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.features.nft.component.NFTAssetTraitsComponent
|
||||
import com.tangem.features.nft.traits.ui.NFTAssetTraits
|
||||
import com.tangem.features.nft.traits.model.NFTAssetTraitsModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNFTAssetTraitsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: NFTAssetTraitsComponent.Params,
|
||||
) : NFTAssetTraitsComponent, AppComponentContext by context {
|
||||
|
||||
private val model: NFTAssetTraitsModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
NFTAssetTraits(state)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NFTAssetTraitsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NFTAssetTraitsComponent.Params,
|
||||
): DefaultNFTAssetTraitsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -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,48 @@
|
|||
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.core.decompose.navigation.Router
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.features.nft.component.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,
|
||||
private val router: Router,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: NFTAssetTraitsComponent.Params = paramsContainer.require()
|
||||
|
||||
val state: StateFlow<NFTAssetTraitsUM> get() = _state
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
value = NFTAssetTraitsUM(
|
||||
traits = params.nftAsset.transform(),
|
||||
onBackClick = ::navigateBack,
|
||||
),
|
||||
)
|
||||
|
||||
private fun NFTAsset.transform(): ImmutableList<NFTAssetTraitUM> = this
|
||||
.traits
|
||||
.mapIndexed { index, trait ->
|
||||
NFTAssetTraitUM(
|
||||
id = index.toString(),
|
||||
name = trait.name,
|
||||
value = trait.value,
|
||||
)
|
||||
}.toPersistentList()
|
||||
|
||||
private fun navigateBack() {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = { },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -18,11 +16,6 @@ import dagger.multibindings.IntoMap
|
|||
@InstallIn(ModelComponent::class)
|
||||
internal interface SendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendModel::class)
|
||||
fun provideSendModel(model: SendModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendAmountModel::class)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 SendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendModel::class)
|
||||
fun provideSendModel(model: SendModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendConfirmModel::class)
|
||||
fun provideSendConfirmModel(model: SendConfirmModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.features.send.v2.sendnft
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.sendnft.model.NFTSendModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNFTSendComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: NFTSendComponent.Params,
|
||||
) : NFTSendComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
// private val stackNavigation = StackNavigation<NFTSendRoute>()
|
||||
//
|
||||
// private val innerRouter = InnerRouter<NFTSendRoute>(
|
||||
// stackNavigation = stackNavigation,
|
||||
// popCallback = { onChildBack() },
|
||||
// )
|
||||
|
||||
// private val initialRoute = NFTSendRoute.Empty
|
||||
// private val currentRouteFlow = MutableStateFlow<NFTSendRoute>(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 ->
|
||||
// // todo
|
||||
// ComposableContentComponent { }
|
||||
// },
|
||||
// )
|
||||
|
||||
init {
|
||||
// childStack.subscribe(
|
||||
// lifecycle = lifecycle,
|
||||
// mode = ObserveLifecycleMode.CREATE_DESTROY,
|
||||
// ) { stack ->
|
||||
// componentScope.launch {
|
||||
// when (val activeComponent = stack.active.instance) {
|
||||
// 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)
|
||||
// TODO
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
// TODO
|
||||
// val isEmptyRoute = childStack.value.active.configuration == NFTSendRoute.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,20 @@
|
|||
package com.tangem.features.send.v2.sendnft.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.send.v2.sendnft.model.NFTSendModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface NFTSendModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NFTSendModel::class)
|
||||
fun provideNFTSendModel(model: NFTSendModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.tangem.features.send.v2.sendnft.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
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.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
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.NFTSendComponent
|
||||
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.sendnft.ui.state.NFTSendUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent
|
||||
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.ui.state.FeeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
internal interface SendNFTComponentCallback :
|
||||
SendFeeComponent.ModelCallback,
|
||||
SendDestinationComponent.ModelCallback
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class NFTSendModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
) : Model(), SendNFTComponentCallback {
|
||||
|
||||
val params: NFTSendComponent.Params = paramsContainer.require()
|
||||
private val userWalletId = params.userWalletId
|
||||
|
||||
private val _uiState = MutableStateFlow(initialState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _isBalanceHiddenFlow = MutableStateFlow(false)
|
||||
val isBalanceHiddenFlow = _isBalanceHiddenFlow.asStateFlow()
|
||||
|
||||
var cryptoCurrency: CryptoCurrency by Delegates.notNull()
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
var appCurrency: AppCurrency = AppCurrency.Default
|
||||
|
||||
init {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
initAppCurrency()
|
||||
}
|
||||
|
||||
override fun onNavigationResult(navigationUM: NavigationUM) {
|
||||
_uiState.update { it.copy(navigationUM = navigationUM) }
|
||||
}
|
||||
|
||||
override fun onDestinationResult(destinationUM: DestinationUM) {
|
||||
_uiState.update { it.copy(destinationUM = destinationUM) }
|
||||
|
||||
// todo
|
||||
}
|
||||
|
||||
override fun onFeeResult(feeUM: FeeUM) {
|
||||
_uiState.update { it.copy(feeUM = feeUM) }
|
||||
router.pop()
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
modelScope.launch {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
modelScope.launch {
|
||||
getUserWalletUseCase(params.userWalletId).fold(
|
||||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
|
||||
// cryptoCurrency = getCryptoCurrenciesUseCase(userWalletId).getOrNull()
|
||||
// ?.filterIsInstance<CryptoCurrency.Coin>()
|
||||
// ?.firstOrNull { it.network == nftAsset.network }
|
||||
// ?: return@launch
|
||||
|
||||
getCurrenciesStatusUpdates(
|
||||
isSingleWalletWithToken = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
|
||||
)
|
||||
},
|
||||
ifLeft = {
|
||||
// sendConfirmAlertFactory.getGenericErrorState(::onFailedTxEmailClick)
|
||||
return@launch
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean) {
|
||||
getCurrencyStatusUpdatesUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = isSingleWalletWithToken,
|
||||
).onEach { maybeCryptoCurrency ->
|
||||
maybeCryptoCurrency.fold(
|
||||
ifRight = { cryptoStatus ->
|
||||
cryptoCurrencyStatus = cryptoStatus
|
||||
feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoStatus,
|
||||
).getOrNull() ?: cryptoStatus
|
||||
|
||||
// router.push(NFTSendRoute.Destination(isEditMode = false))
|
||||
},
|
||||
ifLeft = {
|
||||
// sendConfirmAlertFactory.getGenericErrorState {
|
||||
// onFailedTxEmailClick(it.toString())
|
||||
// }
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun initialState(): NFTSendUM = NFTSendUM(
|
||||
destinationUM = DestinationUM.Empty(),
|
||||
feeUM = FeeUM.Empty(),
|
||||
confirmUM = ConfirmUM.Empty,
|
||||
navigationUM = NavigationUM.Empty,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.send.v2.sendnft.ui.state
|
||||
|
||||
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.subcomponents.destination.ui.state.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
|
||||
internal data class NFTSendUM(
|
||||
val destinationUM: DestinationUM,
|
||||
val feeUM: FeeUM,
|
||||
val confirmUM: ConfirmUM,
|
||||
val navigationUM: NavigationUM,
|
||||
)
|
||||
|
|
@ -2,8 +2,13 @@ package com.tangem.feature.wallet.child.wallet.model.intents
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.visa.GetVisaCurrencyUseCase
|
||||
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
|
||||
import com.tangem.domain.visa.model.VisaTxDetails
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter
|
||||
|
|
@ -20,14 +25,20 @@ internal interface VisaWalletIntents {
|
|||
fun onVisaTransactionClick(id: String)
|
||||
|
||||
fun onExploreClick(exploreUrl: String)
|
||||
|
||||
fun onDisputeClick(txDetails: VisaTxDetails)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class VisaWalletIntentsImplementor @Inject constructor(
|
||||
private val stateController: WalletStateController,
|
||||
private val eventSender: WalletEventSender,
|
||||
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
|
||||
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val getUserWalletsUseCase: GetWalletsUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : BaseWalletClickIntents(), VisaWalletIntents {
|
||||
|
||||
|
|
@ -78,4 +89,20 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
|
|||
override fun onExploreClick(exploreUrl: String) {
|
||||
router.openUrl(exploreUrl)
|
||||
}
|
||||
|
||||
override fun onDisputeClick(txDetails: VisaTxDetails) {
|
||||
modelScope.launch {
|
||||
val userWalletId = stateController.getSelectedWalletId()
|
||||
val userWallet = getUserWalletsUseCase.invokeSync()
|
||||
.firstOrNull { it.walletId == userWalletId } ?: return@launch
|
||||
val cardInfo = getCardInfoUseCase.invoke(userWallet.scanResponse).getOrNull() ?: return@launch
|
||||
|
||||
sendFeedbackEmailUseCase(
|
||||
FeedbackEmailType.Visa.Dispute(
|
||||
cardInfo = cardInfo,
|
||||
visaTxDetails = txDetails,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
internal data class VisaTxDetailsBottomSheetConfig(
|
||||
val transaction: Transaction,
|
||||
val requests: ImmutableList<Request>,
|
||||
val onDisputeClick: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent {
|
||||
|
||||
data class Transaction(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ internal class VisaTxDetailsBottomSheetConverter(
|
|||
return VisaTxDetailsBottomSheetConfig(
|
||||
transaction = createTransaction(value),
|
||||
requests = value.requests.map(::createRequest).toImmutableList(),
|
||||
onDisputeClick = { clickIntents.onDisputeClick(value) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -36,26 +38,45 @@ internal fun VisaTxDetailsBottomSheet(config: TangemBottomSheetConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun VisaTxDetailsBottomSheetContent(config: VisaTxDetailsBottomSheetConfig, modifier: Modifier = Modifier) {
|
||||
ContentContainer(
|
||||
modifier = modifier,
|
||||
blocksCount = config.requests.size.inc(),
|
||||
title = {
|
||||
Column {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size44)
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.visa_transaction_details_header),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
},
|
||||
block = { index ->
|
||||
if (index == 0) {
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.background(TangemTheme.colors.background.secondary),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item {
|
||||
TransactionBlock(config.transaction)
|
||||
} else {
|
||||
BlockchainRequestBlock(config.requests[index - 1])
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
items(config.requests) { item ->
|
||||
BlockchainRequestBlock(item)
|
||||
}
|
||||
|
||||
item {
|
||||
DisputeButton(config.onDisputeClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -173,38 +194,14 @@ private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Reque
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ContentContainer(
|
||||
blocksCount: Int,
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
block: @Composable ColumnScope.(Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.background(TangemTheme.colors.background.secondary),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
stickyHeader {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size44)
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = title,
|
||||
)
|
||||
}
|
||||
items(blocksCount) { index ->
|
||||
Column {
|
||||
block(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun DisputeButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
SecondaryButtonIconStart(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.visa_tx_dispute_button),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
|
@ -249,20 +246,8 @@ private class VisaTxDetailsBottomSheetParameterProvider :
|
|||
txStatus = "confirmed",
|
||||
onExploreClick = {},
|
||||
),
|
||||
VisaTxDetailsBottomSheetConfig.Request(
|
||||
id = "524582128501966799",
|
||||
type = "settlement",
|
||||
status = "accepted",
|
||||
blockchainAmount = "1.0614 USDT",
|
||||
transactionAmount = "0.99 €",
|
||||
currencyCode = "978",
|
||||
errorCode = 0,
|
||||
date = "2023-12-01 00:01:00.000 +0300",
|
||||
txHash = "0x635841d5fbdf1087cdd929019c863ee88a7165e4340bc17ddd0b1d04dfb11daa",
|
||||
txStatus = "confirmed",
|
||||
onExploreClick = {},
|
||||
),
|
||||
),
|
||||
onDisputeClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue