diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b9e46866c1..21808cc1b9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -142,6 +142,17 @@ android:scheme="tangem" /> + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt b/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt index 9bdca56b66..6cf4e25783 100644 --- a/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt +++ b/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt @@ -11,6 +11,7 @@ import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap +import androidx.core.net.toUri import coil.executeBlocking import coil.request.ImageRequest import com.google.firebase.messaging.FirebaseMessagingService @@ -36,10 +37,11 @@ internal class TangemPushNotificationService : FirebaseMessagingService() { val notification = message.notification ?: return val channelId = notification.channelId ?: TANGEM_CHANNEL_ID - // TODO refactoring: [REDACTED_JIRA] - val intent = Intent(applicationContext, MainActivity::class.java) - intent.putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + val intent = Intent(applicationContext, MainActivity::class.java).apply { + data = message.data[DEEPLINK_KEY]?.toUri() + putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } val pendingIntent = PendingIntent.getActivity( /* context = */ this, /* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE, @@ -101,5 +103,7 @@ internal class TangemPushNotificationService : FirebaseMessagingService() { private companion object { const val TANGEM_CHANNEL_ID = "Tangem General" // General channel for notifications const val PUSH_NOTIFICATION_REQUEST_CODE = 123 + + const val DEEPLINK_KEY = "deeplink" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt index 9e969c71de..8064273ab8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt @@ -15,14 +15,14 @@ internal object PromoDomainModule { @Singleton fun provideShouldShowSwapPromoWalletUseCase( promoSettingsRepository: PromoRepository, - ): ShouldShowSwapPromoWalletUseCase { - return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository) + ): ShouldShowPromoWalletUseCase { + return ShouldShowPromoWalletUseCase(promoSettingsRepository) } @Provides @Singleton - fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase { - return ShouldShowSwapPromoTokenUseCase(promoRepository) + fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowPromoTokenUseCase { + return ShouldShowPromoTokenUseCase(promoRepository) } @Provides 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 50275788dd..e1cc610e17 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 @@ -123,8 +123,12 @@ object PreferencesKeys { val WALLETS_NFT_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "walletsNftEnabledStates") } + // region Promo fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId") + fun getShouldShowPromoKey(promoId: String) = booleanPreferencesKey("shouldShowPromo_$promoId") + // endregion + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/ReferralDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/ReferralDeepLink.kt new file mode 100644 index 0000000000..763a6d80f7 --- /dev/null +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/ReferralDeepLink.kt @@ -0,0 +1,13 @@ +package com.tangem.core.deeplink.global + +import com.tangem.core.deeplink.DeepLink + +class ReferralDeepLink( + val onReceive: () -> Unit, +) : DeepLink(shouldHandleDelayed = true) { + override val uri: String = "tangem://referral" + + override fun onReceive(params: Map) { + onReceive() + } +} \ No newline at end of file diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a23e1b705f..48b2496729 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -520,6 +520,9 @@ NFT collections Unable to load the data Choose network + Join Now + Share your code - earn 5 USDT per sale. Your friend gets 10% OFF. + Get REWARDS for every friend! Last sale price Rarity label Rarity rank diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 7bd5322685..5d1f6b11fd 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -25,6 +25,8 @@ import androidx.compose.ui.semantics.Role 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 androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.components.buttons.common.TangemButtonSize @@ -55,6 +57,7 @@ fun Notification( subtitleColor: Color = TangemTheme.colors.text.tertiary, containerColor: Color? = null, iconTint: Color? = null, + iconSize: Dp = 20.dp, isEnabled: Boolean = true, ) { NotificationBaseContainer( @@ -68,6 +71,7 @@ fun Notification( MainContent( iconResId = config.iconResId, iconTint = iconTint, + iconSize = iconSize, title = config.title, subtitle = config.subtitle, subtitleColor = subtitleColor, @@ -127,6 +131,7 @@ internal fun NotificationBaseContainer( private fun MainContent( iconResId: Int, iconTint: Color?, + iconSize: Dp, title: TextReference?, subtitle: TextReference, subtitleColor: Color, @@ -137,7 +142,7 @@ private fun MainContent( iconResId = iconResId, tint = iconTint, modifier = Modifier - .size(size = TangemTheme.dimens.size20) + .size(size = iconSize) .align(alignment = Alignment.CenterVertically), ) diff --git a/core/ui/src/main/res/drawable/img_referral_promo.webp b/core/ui/src/main/res/drawable/img_referral_promo.webp new file mode 100644 index 0000000000..25a0af2e67 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_referral_promo.webp differ diff --git a/data/promo/build.gradle.kts b/data/promo/build.gradle.kts index e2dd74f43c..7573a1e454 100644 --- a/data/promo/build.gradle.kts +++ b/data/promo/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.domain.promo) implementation(projects.domain.promo.models) implementation(projects.domain.wallets.models) + implementation(projects.features.referral.domain) implementation(projects.core.datasource) implementation(projects.core.utils) diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index 5b14b75e9a..c80337d42b 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -4,16 +4,21 @@ import com.tangem.data.promo.converters.StoryContentResponseConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowStoriesKey import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.promo.PromoStoriesStore import com.tangem.domain.promo.PromoRepository +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.promo.models.StoryContent +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull @@ -23,24 +28,41 @@ internal class DefaultPromoRepository( private val appPreferencesStore: AppPreferencesStore, private val promoStoriesStore: PromoStoriesStore, private val dispatchers: CoroutineDispatcherProvider, + private val referralRepository: ReferralRepository, ) : PromoRepository { private val storyContentConverter = StoryContentResponseConverter() - override fun isReadyToShowWalletSwapPromo(): Flow { - return flowOf(false) // Use it on new promo action + override fun isReadyToShowWalletPromo(userWalletId: UserWalletId, promoId: PromoId): Flow { + return appPreferencesStore.get( + key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), + default = true, + ).map { shouldShow -> + if (promoId == PromoId.Referral) { + !referralRepository.isReferralParticipant(userWalletId) && shouldShow + } else { + shouldShow + } + } } - override fun isReadyToShowTokenSwapPromo(): Flow { - return flowOf(false) // Use it on new promo action + override fun isReadyToShowTokenPromo(promoId: PromoId): Flow { + return if (promoId == PromoId.Referral) { + flowOf(false) + } else { + appPreferencesStore.get( + PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), + default = false, + ) + } } - override suspend fun setNeverToShowWalletSwapPromo() { - // Use it on new promo action + override suspend fun setNeverToShowWalletPromo(promoId: PromoId) { + appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false) } - override suspend fun setNeverToShowTokenSwapPromo() { - // Use it on new promo action + override suspend fun setNeverToShowTokenPromo(promoId: PromoId) { + appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false) } override fun getStoryById(id: String): Flow = isReadyToShowStories(id).mapLatest { diff --git a/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt b/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt index a564ef0509..69d489a2dc 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt @@ -5,6 +5,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.promo.PromoStoriesStore import com.tangem.domain.promo.PromoRepository +import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -23,12 +24,14 @@ internal object PromoDataModule { appPreferencesStore: AppPreferencesStore, promoStoriesStore: PromoStoriesStore, dispatchers: CoroutineDispatcherProvider, + referralRepository: ReferralRepository, ): PromoRepository { return DefaultPromoRepository( tangemApi = tangemTechApi, appPreferencesStore = appPreferencesStore, promoStoriesStore = promoStoriesStore, dispatchers = dispatchers, + referralRepository = referralRepository, ) } } \ No newline at end of file diff --git a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt index 42ecbd01d8..81f592f146 100644 --- a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt +++ b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt @@ -23,4 +23,8 @@ data class PromoBanner( private companion object { const val ACTIVE_STATUS = "active" } +} + +enum class PromoId { + Referral, } \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt index 4db7b9bf87..88682deae2 100644 --- a/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt +++ b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt @@ -1,18 +1,20 @@ package com.tangem.domain.promo +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.promo.models.StoryContent +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow interface PromoRepository { // region Promo - fun isReadyToShowWalletSwapPromo(): Flow + fun isReadyToShowWalletPromo(userWalletId: UserWalletId, promoId: PromoId): Flow - fun isReadyToShowTokenSwapPromo(): Flow + fun isReadyToShowTokenPromo(promoId: PromoId): Flow - suspend fun setNeverToShowWalletSwapPromo() + suspend fun setNeverToShowWalletPromo(promoId: PromoId) - suspend fun setNeverToShowTokenSwapPromo() + suspend fun setNeverToShowTokenPromo(promoId: PromoId) // endregion // region Stories diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoTokenUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoTokenUseCase.kt new file mode 100644 index 0000000000..e6ad0e7580 --- /dev/null +++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoTokenUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.promo + +import com.tangem.domain.promo.models.PromoId +import kotlinx.coroutines.flow.Flow + +class ShouldShowPromoTokenUseCase(private val promoRepository: PromoRepository) { + + operator fun invoke(promoId: PromoId): Flow = promoRepository.isReadyToShowTokenPromo(promoId) + + suspend fun neverToShow(promoId: PromoId) = promoRepository.setNeverToShowTokenPromo(promoId) +} \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt new file mode 100644 index 0000000000..4afa0bd2a7 --- /dev/null +++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.promo + +import com.tangem.domain.promo.models.PromoId +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow + +class ShouldShowPromoWalletUseCase(private val promoRepository: PromoRepository) { + + operator fun invoke(userWalletId: UserWalletId, promoId: PromoId): Flow { + return flow { + emit(false) + emitAll(promoRepository.isReadyToShowWalletPromo(userWalletId, promoId)) + } + } + + suspend fun neverToShow(promoId: PromoId) = promoRepository.setNeverToShowWalletPromo(promoId) +} \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt deleted file mode 100644 index 9bd3a02d7f..0000000000 --- a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.domain.promo - -import kotlinx.coroutines.flow.Flow - -class ShouldShowSwapPromoTokenUseCase(private val promoRepository: PromoRepository) { - - operator fun invoke(): Flow = promoRepository.isReadyToShowTokenSwapPromo() - - suspend fun neverToShow() = promoRepository.setNeverToShowTokenSwapPromo() -} \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt deleted file mode 100644 index 5ed815ec9c..0000000000 --- a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.domain.promo - -import kotlinx.coroutines.flow.Flow - -class ShouldShowSwapPromoWalletUseCase(private val promoRepository: PromoRepository) { - - operator fun invoke(): Flow = promoRepository.isReadyToShowWalletSwapPromo() - - suspend fun neverToShow() = promoRepository.setNeverToShowWalletSwapPromo() -} \ No newline at end of file diff --git a/domain/tokens/models/build.gradle.kts b/domain/tokens/models/build.gradle.kts index 9a926341a8..f618dc0f3f 100644 --- a/domain/tokens/models/build.gradle.kts +++ b/domain/tokens/models/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.txhistory.models) implementation(projects.domain.staking.models) + implementation(projects.domain.promo.models) /** Other dependencies */ implementation(deps.kotlin.serialization) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 623fbe5c1b..36cd8062dc 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.model.warnings +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.tokens.model.CryptoCurrency import org.joda.time.DateTime import java.math.BigDecimal @@ -42,6 +43,7 @@ sealed class CryptoCurrencyWarning { data class Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning() data class SwapPromo( + val promoId: PromoId, val startDateTime: DateTime, val endDateTime: DateTime, ) : CryptoCurrencyWarning() diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index f33ab65ce4..714e0e29dc 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -39,6 +39,10 @@ internal class ReferralRepositoryImpl @Inject constructor( } } + override suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean { + return getReferralData(userWalletId.stringValue) is ReferralData.ParticipantData + } + override suspend fun startReferral( walletId: String, networkId: String, diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt index 760277f9f8..9c890ff038 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt @@ -10,6 +10,9 @@ interface ReferralRepository { /** Returns data object of [ReferralData] depends on user program status */ suspend fun getReferralData(walletId: String): ReferralData + /** Returns whether user is participating in referral program */ + suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean + /** Starts user referral program */ suspend fun startReferral(walletId: String, networkId: String, tokenId: String, address: String): ReferralData diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt index eeaaa1174a..c24e2e7736 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt @@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model import com.tangem.common.ui.bottomsheet.receive.AddressModel import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig @@ -49,9 +50,9 @@ interface TokenDetailsClickIntents { fun onGoToProviderClick(url: String) - fun onSwapPromoDismiss() + fun onSwapPromoDismiss(promoId: PromoId) - fun onSwapPromoClick() + fun onSwapPromoClick(promoId: PromoId) fun onGenerateExtendedKey() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 6ca825bee9..d76fff8838 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -35,7 +35,8 @@ import com.tangem.domain.card.NetworkHasDerivationUseCase import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.onramp.model.OnrampSource -import com.tangem.domain.promo.ShouldShowSwapPromoTokenUseCase +import com.tangem.domain.promo.ShouldShowPromoTokenUseCase +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingEntryInfoUseCase @@ -101,7 +102,7 @@ internal class TokenDetailsModel @Inject constructor( private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, - private val shouldShowSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, + private val shouldShowPromoTokenUseCase: ShouldShowPromoTokenUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase, private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase, @@ -811,9 +812,9 @@ internal class TokenDetailsModel @Inject constructor( router.openUrl(url) } - override fun onSwapPromoDismiss() { + override fun onSwapPromoDismiss(promoId: PromoId) { modelScope.launch(dispatchers.main) { - shouldShowSwapPromoTokenUseCase.neverToShow() + shouldShowPromoTokenUseCase.neverToShow(promoId) analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( source = AnalyticsParam.ScreensSources.Token, @@ -824,9 +825,9 @@ internal class TokenDetailsModel @Inject constructor( } } - override fun onSwapPromoClick() { + override fun onSwapPromoClick(promoId: PromoId) { modelScope.launch(dispatchers.main) { - shouldShowSwapPromoTokenUseCase.neverToShow() + shouldShowPromoTokenUseCase.neverToShow(promoId) analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( source = AnalyticsParam.ScreensSources.Token, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 50b4710dd8..1413bff703 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -10,10 +10,10 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.HederaWarnings import com.tangem.domain.tokens.model.warnings.KaspaWarnings +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* -import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.removeBy @@ -98,8 +98,8 @@ internal class TokenDetailsNotificationConverter( is CryptoCurrencyWarning.SwapPromo -> SwapPromo( startDateTime = warning.startDateTime, endDateTime = warning.endDateTime, - onSwapClick = clickIntents::onSwapPromoClick, - onCloseClick = clickIntents::onSwapPromoDismiss, + onSwapClick = { clickIntents.onSwapPromoClick(warning.promoId) }, + onCloseClick = { clickIntents.onSwapPromoDismiss(warning.promoId) }, ) is CryptoCurrencyWarning.BeaconChainShutdown -> NetworkShutdown( title = resourceReference(R.string.warning_beacon_chain_retirement_title), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index 418cb39368..23569c3eee 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -15,6 +15,7 @@ import com.tangem.domain.nft.FetchNFTCollectionsUseCase import com.tangem.domain.settings.* import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase @@ -356,9 +357,7 @@ internal class WalletModel @Inject constructor( coroutineScope = modelScope, ) - if (action.selectedWallet.scanResponse.cardTypesResolver.isSingleWallet()) { - fetchCurrencyStatusUseCase(userWalletId = action.selectedWallet.walletId) - } + fetchIfSingleWallet(action.selectedWallet) if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) { withContext(dispatchers.io) { delay(timeMillis = 1_800) } @@ -385,6 +384,8 @@ internal class WalletModel @Inject constructor( coroutineScope = modelScope, ) + fetchIfSingleWallet(userWallet = action.selectedWallet) + stateHolder.update( ReinitializeWalletTransformer( prevWalletId = action.prevWalletId, @@ -402,12 +403,7 @@ internal class WalletModel @Inject constructor( coroutineScope = modelScope, ) - if (action.selectedWallet.scanResponse.cardTypesResolver.isSingleWallet()) { - modelScope.launch { - fetchCurrencyStatusUseCase(userWalletId = action.selectedWallet.walletId) - .onLeft { Timber.e(it.toString()) } - } - } + fetchIfSingleWallet(userWallet = action.selectedWallet) stateHolder.update( AddWalletTransformer( @@ -506,6 +502,15 @@ internal class WalletModel @Inject constructor( } } + private fun fetchIfSingleWallet(userWallet: UserWallet) { + if (userWallet.scanResponse.cardTypesResolver.isSingleWallet()) { + modelScope.launch { + fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId) + .onLeft { Timber.e(it.toString()) } + } + } + } + inner class AskBiometryModelCallbacks : AskBiometryComponent.ModelCallbacks { override fun onAllowed() { analyticsEventsHandler.send(MainScreenAnalyticsEvent.EnableBiometrics(AnalyticsParam.OnOffState.On)) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 419db01ebc..316ee31a8a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse import com.tangem.common.TangemBlogUrlBuilder +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped @@ -13,7 +15,8 @@ import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher -import com.tangem.domain.promo.ShouldShowSwapPromoWalletUseCase +import com.tangem.domain.promo.ShouldShowPromoWalletUseCase +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.NeverToSuggestRateAppUseCase @@ -67,7 +70,9 @@ internal interface WalletWarningsClickIntents { fun onCloseRateAppWarningClick() - fun onCloseSwapPromoClick() + fun onClosePromoClick(promoId: PromoId) + + fun onPromoClick(promoId: PromoId) fun onSupportClick() @@ -98,13 +103,14 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, - private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, + private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, private val urlOpener: UrlOpener, private val tokensFeatureToggles: TokensFeatureToggles, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, + private val appRouter: AppRouter, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { override fun onAddBackupCardClick() { @@ -268,16 +274,31 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } } - override fun onCloseSwapPromoClick() { + override fun onClosePromoClick(promoId: PromoId) { analyticsEventHandler.send( - TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( - source = AnalyticsParam.ScreensSources.Main, - programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action - action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, - ), + if (promoId == PromoId.Referral) { + MainScreen.ReferralPromoButtonDismiss + } else { + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, + ) + }, ) modelScope.launch(dispatchers.main) { - shouldShowSwapPromoWalletUseCase.neverToShow() + shouldShowPromoWalletUseCase.neverToShow(promoId) + } + } + + override fun onPromoClick(promoId: PromoId) { + if (promoId == PromoId.Referral) { + val userWallet = getSelectedUserWallet() ?: return + analyticsEventHandler.send(MainScreen.ReferralPromoButtonParticipate) + modelScope.launch { + shouldShowPromoWalletUseCase.neverToShow(promoId) + appRouter.push(AppRoute.ReferralProgram(userWalletId = userWallet.walletId)) + } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index a0b28f06fd..241307e17c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -6,7 +6,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLink import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink +import com.tangem.core.deeplink.global.ReferralDeepLink import com.tangem.core.deeplink.global.SellCurrencyDeepLink +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet @@ -54,6 +56,7 @@ internal class WalletDeepLinksHandler @Inject constructor( ) return buildList { + addReferralDeepLink(userWallet) add(sellCurrencyDeepLink) if (onrampFeatureToggles.isFeatureEnabled || !userWallet.isMultiCurrency) { add( @@ -95,4 +98,18 @@ internal class WalletDeepLinksHandler @Inject constructor( analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol)) } } + + private fun MutableList.addReferralDeepLink(userWallet: UserWallet) { + add( + ReferralDeepLink( + onReceive = { + if (userWallet.cardTypesResolver.isTangemWallet()) { + router.push( + AppRoute.ReferralProgram(userWalletId = userWallet.walletId), + ) + } + }, + ), + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index a07053ba04..523e8923de 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -137,5 +137,11 @@ sealed class WalletScreenAnalyticsEvent { data object NoticeSeedPhraseSupportButtonUsed : MainScreen(event = "Button - Support Used") data object NoticeSeedPhraseSupportButtonDeclined : MainScreen(event = "Button - Support Declined") + + // region Referral Promo + data object ReferralPromo : MainScreen(event = "Referral Banner") + data object ReferralPromoButtonParticipate : MainScreen(event = "Button - Referral Participate") + data object ReferralPromoButtonDismiss : MainScreen(event = "Button - Referral Dismiss") + //endregion } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 36ed315d4b..0db4af387d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -53,6 +53,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( source = AnalyticsParam.ScreensSources.Main, programName = ProgramName.Empty, // Use it on new promo action ) + is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] is WalletNotification.Informational.NoAccount, is WalletNotification.Warning.LowSignatures, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 5ca74067c5..f50c6ea080 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -6,6 +6,8 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.Lce import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.models.StatusSource +import com.tangem.domain.promo.ShouldShowPromoWalletUseCase +import com.tangem.domain.promo.models.PromoId import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency @@ -34,6 +36,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val isNeedToBackupUseCase: IsNeedToBackupUseCase, private val backupValidator: BackupValidator, private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, + private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase, ) { @Suppress("MagicNumber", "MaximumLineLength") @@ -45,12 +48,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( flow2 = isReadyToShowRateAppUseCase(), flow3 = isNeedToBackupUseCase(userWallet.walletId), flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId), - ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus -> + flow5 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral), + ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo -> buildList { addUsedOutdatedDataNotification(maybeTokenList) addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents) + addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo) + addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents) addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents) @@ -187,6 +193,20 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( .map(CryptoCurrencyStatus::currency) } + private fun MutableList.addReferralPromoNotification( + cardTypesResolver: CardTypesResolver, + clickIntents: WalletClickIntents, + shouldShowPromo: Boolean, + ) { + addIf( + element = WalletNotification.ReferralPromo( + onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.Referral) }, + onClick = { clickIntents.onPromoClick(promoId = PromoId.Referral) }, + ), + condition = shouldShowPromo && cardTypesResolver.isTangemWallet(), + ) + } + private fun MutableList.addWarningNotifications( cardTypesResolver: CardTypesResolver, tokenList: Lce, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index 203a35a3a8..8315a590cd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -254,4 +254,20 @@ sealed class WalletNotification(val config: NotificationConfig) { iconResId = R.drawable.ic_error_sync_24, ), ) + + data class ReferralPromo( + val onCloseClick: () -> Unit, + val onClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(R.string.notification_referral_promo_title), + subtitle = resourceReference(R.string.notification_referral_promo_text), + iconResId = R.drawable.img_referral_promo, + onCloseClick = onCloseClick, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.notification_referral_promo_button), + onClick = onClick, + ), + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt index 211ce57e2d..0227a5339a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.notifications.NoteMigrationNotification import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.res.TangemTheme @@ -38,6 +39,11 @@ internal fun LazyListScope.notifications(configs: ImmutableList TangemTheme.colors.icon.warning is WalletNotification.Informational -> TangemTheme.colors.icon.accent