diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a3a05911c7..de5864d8b1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) implementation(projects.domain.transaction) + implementation(projects.domain.analytics) implementation(projects.common) implementation(projects.core.analytics) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/TopUpEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/TopUpEventConverter.kt index 429b2e5ebc..ba62707b7f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/TopUpEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/TopUpEventConverter.kt @@ -2,16 +2,18 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.common.analytics.events.Basic /** [REDACTED_AUTHOR] */ -class TopUpEventConverter : Converter { +class TopUpEventConverter : Converter, Basic.ToppedUp?> { - override fun convert(value: CardTypesResolver): Basic.ToppedUp? { - val paramCardCurrency = ParamCardCurrencyConverter().convert(value) ?: return null + override fun convert(value: Pair): Basic.ToppedUp? { + val (userWalletId, resolver) = value + val paramCardCurrency = ParamCardCurrencyConverter().convert(resolver) ?: return null - return Basic.ToppedUp(paramCardCurrency) + return Basic.ToppedUp(userWalletId, paramCardCurrency) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 2d1bd09138..21dcd6c657 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -14,7 +14,7 @@ sealed class AnalyticsParam { // MultiCurrency or CurrencyType sealed class CardCurrency(val value: String) { - object MultiCurrency : CardCurrency("Multicurrency") + object MultiCurrency : CardCurrency(value = "Multicurrency") class SingleCurrency(type: CurrencyType) : CardCurrency(type.value) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index ade60b95cf..812527539e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -1,6 +1,8 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.OneTimeAnalyticsEvent +import com.tangem.domain.wallets.models.UserWalletId /** [REDACTED_AUTHOR] @@ -50,10 +52,15 @@ sealed class Basic( } } - class ToppedUp(currency: AnalyticsParam.CardCurrency) : Basic( - event = "Topped up", - params = mapOf(AnalyticsParam.CURRENCY to currency.value), - ) + class ToppedUp(userWalletId: UserWalletId, currency: AnalyticsParam.CardCurrency) : + Basic( + event = "Topped up", + params = mapOf(AnalyticsParam.CURRENCY to currency.value), + ), + OneTimeAnalyticsEvent { + + override val oneTimeEventId: String = id + userWalletId.stringValue + } class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom, memoType: MemoType) : Basic( event = "Transaction sent", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt index b32af636df..376143ec6b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt @@ -154,7 +154,7 @@ class TopUpController( if (cardBalanceState.isToppedUp()) { topupWalletStorage.save(topupInfo.copy(cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full)) - TopUpEventConverter().convert(cardTypesResolver)?.let { + TopUpEventConverter().convert(value = userWalletId to cardTypesResolver)?.let { Analytics.send(it) } } diff --git a/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt new file mode 100644 index 0000000000..dda9f2c253 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.domain + +import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase +import com.tangem.domain.analytics.repository.AnalyticsRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent + +@Module +@InstallIn(ViewModelComponent::class) +internal object AnalyticsDomainModule { + + @Provides + fun provideCheckIsWalletToppedUpUseCase(analyticsRepository: AnalyticsRepository): CheckIsWalletToppedUpUseCase { + return CheckIsWalletToppedUpUseCase(analyticsRepository) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 947a437152..2f455c7eae 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -11,7 +11,10 @@ import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.* +import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter +import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.extensions.removeContext @@ -117,6 +120,15 @@ object OnboardingHelper { Analytics.removeContext() } + fun sendToppedUpEvent(scanResponse: ScanResponse) { + val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver) + + if (userWalletId != null && currency != null) { + Analytics.send(Basic.ToppedUp(userWalletId, currency)) + } + } + private suspend fun proceedWithScanResponse(scanResponse: ScanResponse, backupCardsIds: List?) { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(backupCardsIds?.toSet()) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 675eb82581..266b627b65 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -152,6 +152,8 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } is OnboardingNoteAction.Balance.Set -> { if (action.balance.balanceIsToppedUp()) { + OnboardingHelper.sendToppedUpEvent(scanResponse) + store.state.globalState.topUpController?.send(scanResponse, AnalyticsParam.CardBalanceState.Full) store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 441798d55f..31072fcd23 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -260,6 +260,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } is TwinCardsAction.Balance.Set -> { if (action.balance.balanceIsToppedUp()) { + OnboardingHelper.sendToppedUpEvent(getScanResponse()) + store.state.globalState.topUpController?.send(getScanResponse(), AnalyticsParam.CardBalanceState.Full) store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done)) } diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 663fb0f33e..9d05970191 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -10,9 +10,15 @@ dependencies { implementation(deps.hilt.core) kapt(deps.hilt.kapt) - /** Core shouldn't depends on core, but in case with utils and logging its necessary */ - implementation(projects.core.utils) + /** Analytics - Models */ implementation(projects.core.analytics.models) + /** Domain */ + implementation(projects.domain.analytics) + + /** Other */ implementation(deps.kotlin.coroutines) + + /** Core shouldn't depend on core, but in case with utils and logging its necessary */ + implementation(projects.core.utils) } \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 91b8eb6880..830d2621c2 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -114,6 +114,11 @@ sealed class AnalyticsParam { object SeedImport : WalletCreationType("Seed import") } + sealed class WalletType(val value: String) { + object MultiCurrency : WalletType(value = "Multicurrency") + class SingleCurrency(currencyName: String) : WalletType(currencyName) + } + companion object Key { const val BLOCKCHAIN = "blockchain" const val TOKEN = "Token" diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt index b4bb704732..13715236c5 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt @@ -3,7 +3,7 @@ package com.tangem.core.analytics.di import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.filter.OneTimeEventFilter -import com.tangem.core.analytics.repository.AnalyticsRepository +import com.tangem.domain.analytics.repository.AnalyticsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt index c224eff75b..f7e9903e26 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt @@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventFilter import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.OneTimeAnalyticsEvent -import com.tangem.core.analytics.repository.AnalyticsRepository +import com.tangem.domain.analytics.repository.AnalyticsRepository class OneTimeEventFilter( private val analyticsRepository: AnalyticsRepository, diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt b/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt deleted file mode 100644 index 859220527a..0000000000 --- a/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.core.analytics.repository - -interface AnalyticsRepository { - - suspend fun checkIsEventSent(eventId: String): Boolean - - suspend fun setIsEventSent(eventId: String) -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt index ae7bb4ee8c..1619c322de 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt @@ -44,12 +44,20 @@ class AppPreferencesStore( return this[key]?.let(adapter::fromJson) } - /** Get nullable list of data [T] by string [key] */ + /** Get list of data [T] by string [key] */ inline fun MutablePreferences.getObjectList(key: Preferences.Key): List? { val adapter = moshi.adapter>(Types.newParameterizedType(List::class.java, T::class.java)) return this[key]?.let(adapter::fromJson) } + /** Get map with [String] key and value [V] by string [key] from [MutablePreferences] */ + inline fun MutablePreferences.getObjectMap(key: Preferences.Key): Map? { + val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) + val adapter = moshi.adapter>(type) + + return this[key]?.let(adapter::fromJson) + } + /** * Set data [T] by string [key] to [MutablePreferences] * @@ -67,4 +75,12 @@ class AppPreferencesStore( val adapter = moshi.adapter>(Types.newParameterizedType(List::class.java, T::class.java)) this[key] = adapter.toJson(value) } + + /** Set map with [String] key and value [V] by string [key] to [MutablePreferences] */ + inline fun MutablePreferences.setObjectMap(key: Preferences.Key, value: Map) { + val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) + val adapter = moshi.adapter>(type) + + this[key] = adapter.toJson(value) + } } \ 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 a030f97687..d10a9dab4f 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 @@ -39,6 +39,8 @@ object PreferencesKeys { val SENT_ONE_TIME_EVENTS_KEY by lazy { stringPreferencesKey(name = "sentOneTimeEvents") } + val WALLETS_BALANCES_STATES_KEY by lazy { stringPreferencesKey(name = "walletsBalancesStates") } + val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index 974b36147a..c0fc7e0cef 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -76,9 +76,31 @@ inline fun AppPreferencesStore.getObjectList(key: Preferences.Key AppPreferencesStore.getObjectListSync(key: Preferences.Key): List { val adapter = moshi.adapter>(Types.newParameterizedType(List::class.java, T::class.java)) + return data.firstOrNull() + ?.get(key) + ?.let(adapter::fromJson) + .orEmpty() +} + +/** Store map with [String] key and value [V] by string [key] */ +suspend inline fun AppPreferencesStore.storeObjectMap( + key: Preferences.Key, + value: Map, +) { + val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) + val adapter = moshi.adapter>(type) + + edit { it[key] = adapter.toJson(value) } +} + +/** Get map with [String] key and value [V] by string [key], or empty if data is not found */ +suspend inline fun AppPreferencesStore.getObjectMap(key: Preferences.Key): Map { + val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) + val adapter = moshi.adapter>(type) + return data.firstOrNull() ?.get(key) ?.let(adapter::fromJson) diff --git a/data/analytics/build.gradle.kts b/data/analytics/build.gradle.kts index 258c49ea37..293c5cf974 100644 --- a/data/analytics/build.gradle.kts +++ b/data/analytics/build.gradle.kts @@ -11,8 +11,11 @@ android { dependencies { + /** Project - Domain */ + implementation(projects.domain.analytics) + implementation(projects.domain.wallets.models) + /** Project - Analytics */ - implementation(projects.core.analytics) implementation(projects.core.analytics.models) /** Project - Data */ diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt index 9235045bae..4866aa1c89 100644 --- a/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt @@ -1,9 +1,12 @@ package com.tangem.data.analytics -import com.tangem.core.analytics.repository.AnalyticsRepository import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.domain.analytics.model.WalletBalanceState +import com.tangem.domain.analytics.repository.AnalyticsRepository +import com.tangem.domain.wallets.models.UserWalletId internal class DefaultAnalyticsRepository( private val appPreferencesStore: AppPreferencesStore, @@ -18,10 +21,30 @@ internal class DefaultAnalyticsRepository( override suspend fun setIsEventSent(eventId: String) { appPreferencesStore.editData { mutablePreferences -> - val sentEvents = mutablePreferences.getObject>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) + val sentEvents = mutablePreferences.getObjectList(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) val updatedSentEvents = sentEvents.orEmpty() + eventId - mutablePreferences.setObject(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents) + mutablePreferences.setObjectList(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents) + } + } + + override suspend fun getWalletBalanceState(userWalletId: UserWalletId): WalletBalanceState? { + val walletsBalanceState = appPreferencesStore.getObjectMap( + key = PreferencesKeys.WALLETS_BALANCES_STATES_KEY, + ) + + return walletsBalanceState[userWalletId.stringValue] + } + + override suspend fun setWalletBalanceState(userWalletId: UserWalletId, balanceState: WalletBalanceState) { + appPreferencesStore.editData { + val walletsBalanceState = it.getObjectMap( + key = PreferencesKeys.WALLETS_BALANCES_STATES_KEY, + ) + val updatedWalletsBalanceState = walletsBalanceState.orEmpty() + .plus(pair = userWalletId.stringValue to balanceState) + + it.setObjectMap(PreferencesKeys.WALLETS_BALANCES_STATES_KEY, updatedWalletsBalanceState) } } } \ No newline at end of file diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt index ed3e2b04ac..95fcd5add2 100644 --- a/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt @@ -1,8 +1,8 @@ package com.tangem.data.analytics.di -import com.tangem.core.analytics.repository.AnalyticsRepository import com.tangem.data.analytics.DefaultAnalyticsRepository import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.analytics.repository.AnalyticsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/domain/analytics/.gitignore b/domain/analytics/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/analytics/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/analytics/build.gradle.kts b/domain/analytics/build.gradle.kts new file mode 100644 index 0000000000..e5e871ea2f --- /dev/null +++ b/domain/analytics/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} + +dependencies { + + /** Project - Domain */ + implementation(projects.core.utils) + implementation(projects.domain.core) + implementation(projects.domain.wallets.models) +} \ No newline at end of file diff --git a/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/CheckIsWalletToppedUpUseCase.kt b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/CheckIsWalletToppedUpUseCase.kt new file mode 100644 index 0000000000..3fc031a509 --- /dev/null +++ b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/CheckIsWalletToppedUpUseCase.kt @@ -0,0 +1,56 @@ +package com.tangem.domain.analytics + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.domain.analytics.model.WalletBalanceState +import com.tangem.domain.analytics.repository.AnalyticsRepository +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Check if wallet has been topped up now. + * */ +class CheckIsWalletToppedUpUseCase( + private val analyticsRepository: AnalyticsRepository, +) { + + /** + * Check if wallet has been topped up now. + * + * @param userWalletId User wallet ID. + * @param balanceState Current wallet balance. + * + * @return Either [Throwable] or [Boolean] representing if wallet has been topped up. + * */ + suspend operator fun invoke( + userWalletId: UserWalletId, + balanceState: WalletBalanceState, + ): Either = either { + val storedBalanceState = getStoredBalanceState(userWalletId) + + when { + storedBalanceState == WalletBalanceState.ToppedUp -> false // already topped up + storedBalanceState == null && balanceState == WalletBalanceState.ToppedUp -> { + setBalanceState(userWalletId, balanceState) + false // already topped up + } + else -> { + setBalanceState(userWalletId, balanceState) + balanceState == WalletBalanceState.ToppedUp + } + } + } + + private suspend fun Raise.getStoredBalanceState(userWalletId: UserWalletId) = catch( + block = { analyticsRepository.getWalletBalanceState(userWalletId) }, + catch = ::raise, + ) + + private suspend fun Raise.setBalanceState(userWalletId: UserWalletId, balanceState: WalletBalanceState) { + catch( + block = { analyticsRepository.setWalletBalanceState(userWalletId, balanceState) }, + catch = ::raise, + ) + } +} \ No newline at end of file diff --git a/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/model/WalletBalanceState.kt b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/model/WalletBalanceState.kt new file mode 100644 index 0000000000..e9cde413a2 --- /dev/null +++ b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/model/WalletBalanceState.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.analytics.model + +enum class WalletBalanceState { + ToppedUp, Empty, Error, +} \ No newline at end of file diff --git a/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/repository/AnalyticsRepository.kt b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/repository/AnalyticsRepository.kt new file mode 100644 index 0000000000..2e80e2259c --- /dev/null +++ b/domain/analytics/src/main/kotlin/com/tangem/domain/analytics/repository/AnalyticsRepository.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.analytics.repository + +import com.tangem.domain.analytics.model.WalletBalanceState +import com.tangem.domain.wallets.models.UserWalletId + +interface AnalyticsRepository { + + suspend fun checkIsEventSent(eventId: String): Boolean + + suspend fun setIsEventSent(eventId: String) + + suspend fun getWalletBalanceState(userWalletId: UserWalletId): WalletBalanceState? + + suspend fun setWalletBalanceState(userWalletId: UserWalletId, balanceState: WalletBalanceState) +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index f96ee8b723..6c78faf07f 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -66,6 +66,7 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.analytics) //TODO: Create api/impl modules for onboarding [REDACTED_JIRA] implementation(projects.features.onboarding) 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 8d43c5d586..f244b3ffca 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 @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.OneTimeAnalyticsEvent +import com.tangem.domain.wallets.models.UserWalletId sealed class WalletScreenAnalyticsEvent { @@ -11,6 +13,16 @@ sealed class WalletScreenAnalyticsEvent { error: Throwable? = null, ) : AnalyticsEvent(category = "Basic", event = event, params = params, error = error) { + class WalletToppedUp(userWalletId: UserWalletId, walletType: AnalyticsParam.WalletType) : + Basic( + event = "Topped up", + params = mapOf(AnalyticsParam.CURRENCY to walletType.value), + ), + OneTimeAnalyticsEvent { + + override val oneTimeEventId: String = id + userWalletId.stringValue + } + object WalletOpened : Basic(event = "Wallet Opened") class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index eb62eaf6e3..666480b385 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -1,13 +1,16 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import arrow.core.Either -import com.tangem.common.extensions.isZero +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase +import com.tangem.domain.analytics.model.WalletBalanceState import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import dagger.hilt.android.scopes.ViewModelScoped import java.math.BigDecimal @@ -16,13 +19,43 @@ import javax.inject.Inject @ViewModelScoped internal class TokenListAnalyticsSender @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, + private val checkIsWalletToppedUpUseCase: CheckIsWalletToppedUpUseCase, ) { - fun send(maybeTokenList: Either) { + suspend fun send(userWallet: UserWallet, maybeTokenList: Either) { val tokenList = (maybeTokenList as? Either.Right)?.value ?: return - createCardBalanceState(tokenList)?.let { - analyticsEventHandler.send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it)) + sendBalanceLoadedEventIfNeeded(tokenList) + sendToppedUpEventIfNeeded(tokenList, userWallet) + } + + private fun sendBalanceLoadedEventIfNeeded(tokenList: TokenList) { + createCardBalanceState(tokenList)?.let { balanceState -> + analyticsEventHandler.send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balanceState)) + } + } + + private suspend fun sendToppedUpEventIfNeeded(tokenList: TokenList, userWallet: UserWallet) { + val balanceState = tokenList.toWalletBalanceState() ?: return + + val isWalletToppedUp = checkIsWalletToppedUpUseCase(userWallet.walletId, balanceState) + .getOrElse { return } + + if (isWalletToppedUp) { + val walletType = if (userWallet.isMultiCurrency) { + AnalyticsParam.WalletType.MultiCurrency + } else { + // For single currency wallets with token list, e.g. Noodle + val currency = when (tokenList) { + is TokenList.Empty -> return + is TokenList.GroupedByNetwork -> tokenList.groups.first().currencies.first() + is TokenList.Ungrouped -> tokenList.currencies.first() + } + + AnalyticsParam.WalletType.SingleCurrency(currency.currency.name) + } + + analyticsEventHandler.send(WalletScreenAnalyticsEvent.Basic.WalletToppedUp(userWallet.walletId, walletType)) } } @@ -30,10 +63,11 @@ internal class TokenListAnalyticsSender @Inject constructor( return when (val fiatBalance = tokenList.totalFiatBalance) { is TokenList.FiatBalance.Failed -> fiatBalance.toCardBalanceState(tokenList) is TokenList.FiatBalance.Loaded -> fiatBalance.toCardBalanceState() - TokenList.FiatBalance.Loading -> null + is TokenList.FiatBalance.Loading -> null } } + @Suppress("UnusedReceiverParameter") private fun TokenList.FiatBalance.Failed.toCardBalanceState(tokenList: TokenList): AnalyticsParam.CardBalanceState { val currenciesStatuses = when (tokenList) { is TokenList.Empty -> emptyList() @@ -50,13 +84,25 @@ internal class TokenListAnalyticsSender @Inject constructor( } } - private fun TokenList.FiatBalance.Loaded.toCardBalanceState(): AnalyticsParam.CardBalanceState? { + private fun TokenList.FiatBalance.Loaded.toCardBalanceState(): AnalyticsParam.CardBalanceState { return if (amount > BigDecimal.ZERO) { AnalyticsParam.CardBalanceState.Full - } else if (amount.isZero()) { - AnalyticsParam.CardBalanceState.Empty } else { - null + AnalyticsParam.CardBalanceState.Empty + } + } + + private fun TokenList.toWalletBalanceState(): WalletBalanceState? { + return when (val balance = totalFiatBalance) { + is TokenList.FiatBalance.Failed -> WalletBalanceState.Error + is TokenList.FiatBalance.Loaded -> { + if (balance.amount > BigDecimal.ZERO) { + WalletBalanceState.ToppedUp + } else { + WalletBalanceState.Empty + } + } + is TokenList.FiatBalance.Loading -> null } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index a6446944c0..64072bdc87 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -38,7 +38,7 @@ internal class SingleWalletWithTokenListSubscriber( .conflate() .distinctUntilChanged() .onEach(::updateContent) - .onEach(tokenListAnalyticsSender::send) + .onEach { tokenListAnalyticsSender.send(userWallet, maybeTokenList = it) } .onEach(walletWithFundsChecker::check) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TokenListSubscriber.kt index 4c4caf4479..c8f2a72509 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TokenListSubscriber.kt @@ -40,7 +40,7 @@ internal class TokenListSubscriber( .conflate() .distinctUntilChanged() .onEach(::updateContent) - .onEach(tokenListAnalyticsSender::send) + .onEach { tokenListAnalyticsSender.send(userWallet, maybeTokenList = it) } .onEach(walletWithFundsChecker::check) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index b13cd5c2fc..6b23060307 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ spr-client = "3.6.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-409" +tangemBlockchainSdk = "develop-411" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-312" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/settings.gradle.kts b/settings.gradle.kts index 1abc2626ae..3ab3775f15 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -128,6 +128,7 @@ include(":domain:app-theme:models") include(":domain:balance-hiding") include(":domain:balance-hiding:models") include(":domain:transaction") +include(":domain:analytics") // endregion Domain modules // region Data modules