Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-28 14:12:42 +05:00
parent ff55704d32
commit 9e2eb5710b
82 changed files with 1150 additions and 401 deletions

View file

@ -0,0 +1,26 @@
package com.tangem.feature.wallet.child.wallet.model
import arrow.core.Either
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class TangemPayMainInfoManager @Inject constructor(
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
) {
val mainScreenCustomerInfo:
StateFlow<Pair<UserWalletId, Either<TangemPayCustomerInfoError, MainScreenCustomerInfo>>?>
field = MutableStateFlow(null)
suspend fun refreshTangemPayInfo(userWalletId: UserWalletId) {
val info = tangemPayMainScreenCustomerInfoUseCase(userWalletId)
mainScreenCustomerInfo.value = Pair(userWalletId, info)
}
}

View file

@ -20,10 +20,9 @@ import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.visa.model.TangemPayCardFrozenState
@ -94,13 +93,13 @@ internal class WalletModel @Inject constructor(
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val tangemPayOnboardingRepository: OnboardingRepository,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val tangemPayMainInfoManager: TangemPayMainInfoManager,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -137,7 +136,8 @@ internal class WalletModel @Inject constructor(
subscribeOnSelectedWalletFlow()
subscribeToScreenBackgroundState()
subscribeOnPushNotificationsPermission()
subscribeToTangemPayInfo()
subscribeTangemPayOnWalletState()
subscribeOnTangemPayInfoUpdates()
enableNotificationsIfNeeded()
clickIntents.initialize(innerWalletRouter, modelScope)
@ -349,57 +349,86 @@ internal class WalletModel @Inject constructor(
.saveIn(clearNFTCacheJobHolder)
}
private fun subscribeToTangemPayInfo() {
private fun subscribeTangemPayOnWalletState() {
/**
* Update state each time a user opens/returns to wallet screen
* and every minute while user stays on the main screen
*/
screenLifecycleProvider.isBackgroundState.onEach { inBackground ->
// fast exit
if (!tangemPayFeatureToggles.isTangemPayEnabled) return@onEach
if (!tangemPayFeatureToggles.isTangemPayEnabled) return
// Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh
val savedCustomerInfo = tangemPayOnboardingRepository.getSavedCustomerInfo()
if (savedCustomerInfo?.cardInfo != null) {
updateTangemPay(MainScreenCustomerInfo(info = savedCustomerInfo, orderStatus = OrderStatus.COMPLETED))
return@onEach
}
combine(
flow = screenLifecycleProvider.isBackgroundState,
flow2 = uiState.mapNotNull {
it.wallets.getOrNull(it.selectedWalletIndex)?.walletCardState?.id
}.distinctUntilChanged(),
transform = ::Pair,
).onEach { (inBackground, userWalletId) ->
val savedCustomerInfo =
tangemPayOnboardingRepository.getSavedCustomerInfo(userWalletId)
updateTangemPayJobHolder.cancel()
val isShouldLaunchPeriodicUpdate = savedCustomerInfo?.cardInfo == null &&
tangemPayOnboardingRepository.isTangemPayInitialDataProduced(userWalletId)
modelScope.launch {
// fast exit
val initialDataProduced = tangemPayOnboardingRepository.isTangemPayInitialDataProduced()
if (!initialDataProduced) return@launch
if (!inBackground) {
refreshTangemPayInfo()
while (isActive) {
delay(TANGEM_PAY_UPDATE_INTERVAL)
refreshTangemPayInfo()
if (isShouldLaunchPeriodicUpdate) {
updateTangemPayJobHolder.cancel()
modelScope.launch {
if (!inBackground) {
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
while (isActive) {
delay(TANGEM_PAY_UPDATE_INTERVAL)
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
}
}
}
}.saveIn(updateTangemPayJobHolder)
}.saveIn(updateTangemPayJobHolder)
} else {
// Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
}
}.launchIn(modelScope)
}
private suspend fun refreshTangemPayInfo() {
val info = tangemPayMainScreenCustomerInfoUseCase()
if (info != null) updateTangemPay(info)
private fun subscribeOnTangemPayInfoUpdates() {
if (!tangemPayFeatureToggles.isTangemPayEnabled) return
tangemPayMainInfoManager.mainScreenCustomerInfo
.filterNotNull()
.distinctUntilChanged()
.onEach { (userWalletId, mainInfoData) ->
mainInfoData.onLeft { tangemPayError ->
when (tangemPayError) {
TangemPayCustomerInfoError.RefreshNeededError -> {
stateHolder.update(
transformer = TangemPayRefreshNeededStateTransformer(
onRefreshClick = { clickIntents.onRefreshPayToken(userWalletId) },
),
)
}
TangemPayCustomerInfoError.UnavailableError -> {
stateHolder.update(
transformer = TangemPayUnavailableStateTransformer,
)
}
else -> {
// do not draw TangemPay block
Timber.e("Failed when loading main screen TangemPay info: $tangemPayError")
}
}
}.onRight { data -> updateTangemPay(data, userWalletId) }
}
.launchIn(modelScope)
}
private suspend fun updateTangemPay(info: MainScreenCustomerInfo) {
private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) {
val cardFrozenState =
info.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
?: TangemPayCardFrozenState.Unfrozen
stateHolder.update(
transformer = TangemPayInitialStateTransformer(
value = info,
value = data,
cardFrozenState = cardFrozenState,
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
openDetails = { config ->
innerWalletRouter.openTangemPayDetails(
userWalletId = stateHolder.getSelectedWalletId(),
userWalletId = userWalletId,
config = config,
)
},

View file

@ -1,45 +1,45 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayInitialStateTransformer
import com.tangem.features.tangempay.TangemPayFeatureToggles
import kotlinx.coroutines.launch
import javax.inject.Inject
internal interface TangemPayIntents {
suspend fun onPullToRefresh()
fun onRefreshPayToken(userWalletId: UserWalletId)
}
@ModelScoped
internal class TangemPayClickIntentsImplementor @Inject constructor(
private val stateHolder: WalletStateController,
private val mainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val featureToggles: TangemPayFeatureToggles,
private val onboardingRepository: OnboardingRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val produceInitialDataTangemPay: ProduceTangemPayInitialDataUseCase,
private val tangemPayInfoManager: TangemPayMainInfoManager,
) : BaseWalletClickIntents(), TangemPayIntents {
override suspend fun onPullToRefresh() {
if (!featureToggles.isTangemPayEnabled || !onboardingRepository.isTangemPayInitialDataProduced()) return
val info = mainScreenCustomerInfoUseCase()
val userWalletId = stateHolder.getSelectedWalletId()
if (info != null) {
val cardFrozenState =
info.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
?: TangemPayCardFrozenState.Unfrozen
stateHolder.update(
transformer = TangemPayInitialStateTransformer(
value = info,
onClickKyc = router::openTangemPayOnboarding,
openDetails = { config -> router.openTangemPayDetails(userWalletId, config) },
cardFrozenState = cardFrozenState,
),
)
if (!featureToggles.isTangemPayEnabled ||
!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)
) {
return
}
tangemPayInfoManager.refreshTangemPayInfo(userWalletId)
}
override fun onRefreshPayToken(userWalletId: UserWalletId) {
modelScope.launch {
produceInitialDataTangemPay.invoke(userWalletId)
tangemPayInfoManager.refreshTangemPayInfo(userWalletId)
}
}
}

View file

@ -4,8 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
@ -50,16 +49,16 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
is WalletNotification.Critical.BackupError -> MainScreen.BackupError
is WalletNotification.NoteMigration -> MainScreen.NotePromo
is WalletNotification.SwapPromo -> PromoAnalyticsEvent.NoticePromotionBanner(
is WalletNotification.SwapPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Empty, // Use it on new promo action
)
is WalletNotification.Sepa -> PromoAnalyticsEvent.NoticePromotionBanner(
is WalletNotification.Sepa -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Sepa,
)
is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo
is WalletNotification.VisaPresalePromo -> PromoAnalyticsEvent.VisaWaitlistPromo
is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,
@ -73,6 +72,8 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond
is WalletNotification.PushNotifications -> WalletScreenAnalyticsEvent.PushBannerPromo.PushBanner
is WalletNotification.Warning.TangemPayRefreshNeeded -> null
WalletNotification.Warning.TangemPayUnreachable -> null
}
}
}

View file

@ -23,4 +23,10 @@ internal sealed class TangemPayState {
val balanceText: TextReference,
val onClick: () -> Unit,
) : TangemPayState()
data class RefreshNeeded(
val notification: WalletNotification,
) : TangemPayState()
data class TemporaryUnavailable(val notification: WalletNotification) : TangemPayState()
}

View file

@ -143,6 +143,24 @@ sealed class WalletNotification(val config: NotificationConfig) {
title = resourceReference(R.string.yield_module_main_view_approve_notification_title),
subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_description),
)
data object TangemPayUnreachable : Warning(
title = resourceReference(id = R.string.tangempay_temporarily_unavailable),
subtitle = resourceReference(id = R.string.tangempay_service_unreachable_try_later),
)
data class TangemPayRefreshNeeded(
@DrawableRes val tangemIcon: Int?,
val onRefreshClick: () -> Unit,
) : Warning(
title = resourceReference(id = R.string.tangempay_payment_account_sync_needed),
subtitle = resourceReference(id = R.string.tangempay_use_tangem_device_to_restore_payment_account),
buttonsState = ButtonsState.PrimaryButtonConfig(
text = resourceReference(id = R.string.home_button_scan),
iconResId = tangemIcon,
onClick = onRefreshClick,
),
)
}
sealed class Informational(

View file

@ -0,0 +1,21 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
internal class TangemPayRefreshNeededStateTransformer(
private val onRefreshClick: () -> Unit,
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
val tangemPayState = TangemPayState.RefreshNeeded(
notification = TangemPayRefreshNeeded(
tangemIcon = R.drawable.ic_tangem_24,
onRefreshClick = onRefreshClick,
),
)
return prevState.copy(tangemPayState = tangemPayState)
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
internal object TangemPayUnavailableStateTransformer : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
return prevState.copy(
tangemPayState = TangemPayState.TemporaryUnavailable(
notification = WalletNotification.Warning.TangemPayUnreachable,
),
)
}
}

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -30,7 +31,7 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo
iconSize = 36.dp,
subtitle = state.description,
iconResId = state.iconRes,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
buttonsState = SecondaryButtonConfig(
text = state.buttonText,
onClick = state.onButtonClick,
shouldShowProgress = state.showProgress,
@ -40,13 +41,15 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo
}
is TangemPayState.Card -> TangemPayCardMainBlock(state, isBalanceHidden, modifier)
is TangemPayState.Empty -> Unit
is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier)
is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier)
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ResetCardScreenPreview() {
private fun TangemPayMainScreenBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayMainScreenBlock(

View file

@ -0,0 +1,77 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
@Composable
internal fun TangemPayRefreshBlock(state: TangemPayState.RefreshNeeded, modifier: Modifier = Modifier) {
Column(modifier) {
Notification(
config = state.notification.config,
iconTint = when (state.notification) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
SpacerH12()
BlockCard(
modifier = Modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
) {
InputRowImageBase(
modifier = Modifier.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = resourceReference(R.string.tangempay_payment_account_sync_needed),
subtitleColor = TangemTheme.colors.text.tertiary,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayRefreshBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayRefreshBlock(
state = TangemPayState.RefreshNeeded(
TangemPayRefreshNeeded(
tangemIcon = R.drawable.ic_tangem_24,
onRefreshClick = {},
),
),
modifier = Modifier,
)
}
}
}

View file

@ -0,0 +1,75 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.utils.StringsSigns.DASH_SIGN
@Composable
internal fun TangemPayUnavailableBlock(state: TangemPayState.TemporaryUnavailable, modifier: Modifier = Modifier) {
Column(modifier) {
Notification(
config = state.notification.config,
iconTint = when (state.notification) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
SpacerH12()
BlockCard(
modifier = Modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
) {
InputRowImageBase(
modifier = Modifier.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = TextReference.Str(DASH_SIGN),
subtitleColor = TangemTheme.colors.text.tertiary,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayUnavailableBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayUnavailableBlock(
state = TangemPayState.TemporaryUnavailable(
WalletNotification.Warning.TangemPayUnreachable,
),
modifier = Modifier,
)
}
}
}