diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 1c596a4b4d..7334227d99 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -8,6 +8,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.repositories.SwapPromoRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module @@ -103,4 +104,20 @@ internal object SettingsDomainModule { fun provideIsWalletsScrollPreviewEnabled(settingsRepository: SettingsRepository): IsWalletsScrollPreviewEnabled { return IsWalletsScrollPreviewEnabled(settingsRepository = settingsRepository) } + + @Provides + @ViewModelScoped + fun provideShouldShowSwapPromoWalletUseCase( + swapPromoRepository: SwapPromoRepository, + ): ShouldShowSwapPromoWalletUseCase { + return ShouldShowSwapPromoWalletUseCase(swapPromoRepository) + } + + @Provides + @ViewModelScoped + fun provideShouldShowSwapPromoTokenUseCase( + swapPromoRepository: SwapPromoRepository, + ): ShouldShowSwapPromoTokenUseCase { + return ShouldShowSwapPromoTokenUseCase(swapPromoRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index d10a9dab4f..26d5b3cf08 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -42,6 +42,10 @@ object PreferencesKeys { val WALLETS_BALANCES_STATES_KEY by lazy { stringPreferencesKey(name = "walletsBalancesStates") } val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } + + val IS_WALLET_SWAP_PROMO_SHOW_KEY by lazy { booleanPreferencesKey(name = "isWalletSwapPromoShown") } + + val IS_TOKEN_SWAP_PROMO_SHOW_KEY by lazy { booleanPreferencesKey(name = "isTokenSwapPromoShown") } } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 391fe57950..21bec9195d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -244,6 +244,8 @@ 1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту Отсканируйте карту + Обменивайте свои цифровые активы между различными сетями + Кроссчейн-своп теперь доступен Токены Добавить Изменить @@ -539,6 +541,9 @@ Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. Невозможно скрыть %s Нет цены + Обменяйте этот токен на другие активы в вашем портфеле + Представляем кроссчейн-своп + Обменять контракт: %s У вас еще нет транзакций Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 08c1787e9b..3088fc97ea 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -263,6 +263,8 @@ 1INCH tokens will be credited to your %s wallet address within 48 hours To access all the networks you need to scan the card Scan your card + Swap multiple currencies across several blockchains + Cross-chain swaps are now available Tokens Add Edit @@ -569,6 +571,9 @@ The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. Unable to hide %s No rate + Exchange this token for other assets in your portfolio + Introducing cross-chain swaps + Exchange now contract: %s You don\'t have any transactions yet Failed to load transaction history.\nClick on reload button to update the information. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt index 1992043a9e..c6180da951 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt @@ -6,12 +6,13 @@ import com.tangem.core.ui.extensions.TextReference /** * Notification component state * - * @property title title - * @property subtitle subtitle - * @property iconResId icon resource id - * @property buttonsState buttons state - * @property onClick lambda be invoked when notification is clicked - * @property onCloseClick lambda be invoked when close button is clicked + * @property title title + * @property subtitle subtitle + * @property iconResId icon resource id + * @property backgroundResId background resource id + * @property buttonsState buttons state + * @property onClick lambda be invoked when notification is clicked + * @property onCloseClick lambda be invoked when close button is clicked * [REDACTED_AUTHOR] */ @@ -19,6 +20,7 @@ data class NotificationConfig( val title: TextReference, val subtitle: TextReference, @DrawableRes val iconResId: Int, + @DrawableRes val backgroundResId: Int? = null, val buttonsState: ButtonsState? = null, val onClick: (() -> Unit)? = null, val onCloseClick: (() -> Unit)? = null, @@ -33,7 +35,11 @@ data class NotificationConfig( val onClick: () -> Unit, ) : ButtonsState() - data class SecondaryButtonConfig(val text: TextReference, val onClick: () -> Unit) : ButtonsState() + data class SecondaryButtonConfig( + val text: TextReference, + @DrawableRes val iconResId: Int? = null, + val onClick: () -> Unit, + ) : ButtonsState() data class PairButtonsConfig( val primaryText: TextReference, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt new file mode 100644 index 0000000000..072a7389f6 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt @@ -0,0 +1,170 @@ +package com.tangem.core.ui.components.notifications + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +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.PreviewParameterProvider +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import androidx.constraintlayout.compose.Visibility +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SecondaryButtonIconStart +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme + +/** + * Custom notification with image background + * @see [Swap Promo](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=8713-6307&mode=dev) + */ +@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries") +@Composable +fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) { + val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig + + ConstraintLayout( + modifier = modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size62) + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium), + ) { + val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs() + + val spacing2 = TangemTheme.dimens.spacing2 + val spacing12 = TangemTheme.dimens.spacing12 + val spacing14 = TangemTheme.dimens.spacing14 + + Image( + painter = painterResource(config.backgroundResId ?: R.drawable.img_swap_promo_banner_background), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.constrainAs(backgroundRef) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + width = Dimension.fillToConstraints + height = Dimension.fillToConstraints + visibility = if (config.backgroundResId == null) Visibility.Gone else Visibility.Visible + }, + ) + Image( + painter = painterResource(config.iconResId), + contentDescription = null, + modifier = Modifier.constrainAs(iconRef) { + start.linkTo(parent.start, spacing12) + linkTo(titleRef.top, subtitleRef.bottom, bias = 0.1f) + }, + ) + Text( + text = config.title.resolveReference(), + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.constantWhite, + modifier = Modifier.constrainAs(titleRef) { + top.linkTo(parent.top, spacing12) + start.linkTo(iconRef.end, spacing12) + end.linkTo(closeIconRef.start, spacing2) + width = Dimension.fillToConstraints + }, + ) + Text( + text = config.subtitle.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.constantWhite, + modifier = Modifier.constrainAs(subtitleRef) { + top.linkTo(titleRef.bottom, spacing2) + start.linkTo(iconRef.end, spacing12) + end.linkTo(parent.end, spacing12) + bottom.linkTo(buttonRef.top, spacing12, spacing14) + width = Dimension.fillToConstraints + }, + ) + Icon( + painter = painterResource(id = R.drawable.ic_close_24), + contentDescription = null, + tint = TangemTheme.colors.text.constantWhite, + modifier = Modifier + .size(TangemTheme.dimens.size16) + .constrainAs(closeIconRef) { + top.linkTo(parent.top, spacing12) + end.linkTo(parent.end, spacing12) + } + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false), + ) { + config.onCloseClick?.invoke() + }, + ) + SecondaryButtonIconStart( + text = button?.text?.resolveReference().orEmpty(), + iconResId = button?.iconResId ?: R.drawable.ic_exchange_vertical_24, + onClick = button?.onClick ?: {}, + modifier = Modifier.constrainAs(buttonRef) { + start.linkTo(parent.start, spacing12) + end.linkTo(parent.end, spacing12) + bottom.linkTo(parent.bottom, spacing12) + width = Dimension.fillToConstraints + visibility = if (button == null) { + Visibility.Gone + } else { + Visibility.Visible + } + }, + ) + } +} + +//region preview +@Preview +@Composable +private fun NotificationWithBackgroundPreview( + @PreviewParameter(NotificationWithBackgroundPreviewProvider::class) config: NotificationConfig, +) { + TangemTheme { + NotificationWithBackground(config = config) + } +} + +private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + NotificationConfig( + title = resourceReference(id = R.string.main_swap_promotion_title), + subtitle = resourceReference(id = R.string.main_swap_promotion_message), + iconResId = R.drawable.ic_swap_promo_34, + backgroundResId = R.drawable.img_swap_promo_banner_background, + ), + NotificationConfig( + title = resourceReference(id = R.string.token_swap_promotion_title), + subtitle = stringReference( + "Swap multiple currencies between any chains you wish. Swap multiple " + + "currencies between any chains you wish. Swap multiple " + + "currencies between any chains you wish. Commission free period " + + "till Dec 31.", + ), + iconResId = R.drawable.ic_swap_promo_34, + backgroundResId = R.drawable.img_swap_promo_banner_background, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(id = R.string.token_swap_promotion_button), + onClick = {}, + ), + ), + ) +} +//endregion \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_swap_promo_34.xml b/core/ui/src/main/res/drawable/ic_swap_promo_34.xml new file mode 100644 index 0000000000..3c279910c8 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_swap_promo_34.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/core/ui/src/main/res/drawable/img_swap_promo_banner_background.webp b/core/ui/src/main/res/drawable/img_swap_promo_banner_background.webp new file mode 100644 index 0000000000..1e952cd6f7 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_swap_promo_banner_background.webp differ diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt new file mode 100644 index 0000000000..5b4611572e --- /dev/null +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt @@ -0,0 +1,54 @@ +package com.tangem.data.settings + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_SHOW_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_SHOW_KEY +import com.tangem.datasource.local.preferences.utils.get +import com.tangem.datasource.local.preferences.utils.store +import com.tangem.domain.settings.repositories.SwapPromoRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.util.Calendar + +/** + * Repository for showing swap promo notification. + */ +class DefaultSwapPromoRepository( + private val appPreferencesStore: AppPreferencesStore, +) : SwapPromoRepository { + override fun isReadyToShowWallet(): Flow { + return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_SHOW_KEY, true) + .map { it && checkPromoPeriod() } + } + + override fun isReadyToShowToken(): Flow { + return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_SHOW_KEY, true) + .map { it && checkPromoPeriod() } + } + + override suspend fun setNeverToShowWallet() { + appPreferencesStore.store( + key = IS_WALLET_SWAP_PROMO_SHOW_KEY, + value = false, + ) + } + + override suspend fun setNeverToShowToken() { + appPreferencesStore.store( + key = IS_TOKEN_SWAP_PROMO_SHOW_KEY, + value = false, + ) + } + + private fun checkPromoPeriod(): Boolean { + val currentDate = Calendar.getInstance() + return currentDate.get(Calendar.DATE) in START_DATE_KEY..END_DATE_KEY && + currentDate.get(Calendar.MONTH) == MONTH_KEY + } + + companion object { + private const val START_DATE_KEY = 15 + private const val END_DATE_KEY = 31 + private const val MONTH_KEY = 11 // December + } +} \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index ba83b09d4d..43d5796d15 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -2,10 +2,12 @@ package com.tangem.data.settings.di import com.tangem.data.settings.DefaultAppRatingRepository import com.tangem.data.settings.DefaultSettingsRepository +import com.tangem.data.settings.DefaultSwapPromoRepository import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.repositories.SwapPromoRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -36,4 +38,10 @@ internal object SettingsDataModule { fun provideAppRatingRepository(appPreferencesStore: AppPreferencesStore): AppRatingRepository { return DefaultAppRatingRepository(appPreferencesStore = appPreferencesStore) } + + @Provides + @Singleton + fun provideSwapPromoRepository(appPreferencesStore: AppPreferencesStore): SwapPromoRepository { + return DefaultSwapPromoRepository(appPreferencesStore = appPreferencesStore) + } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt new file mode 100644 index 0000000000..0c75a4f52a --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.SwapPromoRepository +import kotlinx.coroutines.flow.Flow + +class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) { + + operator fun invoke(): Flow = swapPromoRepository.isReadyToShowToken() + + suspend fun neverToShow() = swapPromoRepository.setNeverToShowToken() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt new file mode 100644 index 0000000000..573a5ea94b --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.SwapPromoRepository +import kotlinx.coroutines.flow.Flow + +class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) { + + operator fun invoke(): Flow = swapPromoRepository.isReadyToShowWallet() + + suspend fun neverToShow() = swapPromoRepository.setNeverToShowWallet() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt new file mode 100644 index 0000000000..95d31f65d8 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.settings.repositories + +import kotlinx.coroutines.flow.Flow + +interface SwapPromoRepository { + fun isReadyToShowWallet(): Flow + + fun isReadyToShowToken(): Flow + + suspend fun setNeverToShowWallet() + + suspend fun setNeverToShowToken() +} \ No newline at end of file