diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d88cedb4c2..341ee0112d 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -28,6 +28,7 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.LogConfig +import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletManagersRepository @@ -196,15 +197,16 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var userTokensStore: UserTokensStore + + @Inject + lateinit var appRatingRepository: AppRatingRepository // endregion Injected override fun onCreate() { super.onCreate() store = Store( - reducer = { action, state -> - appReducer(action, state, appStateHolder) - }, + reducer = { action, state -> appReducer(action, state, appStateHolder) }, middleware = AppState.getMiddleware(), state = AppState( daggerGraphState = DaggerGraphState( @@ -277,6 +279,7 @@ internal class TapApplication : Application(), ImageLoaderFactory { // [REDACTED_JIRA] runBlocking { featureTogglesManager.init() + appRatingRepository.initialize() // learn2earnInteractor.init() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 71be2aa020..4acd6af6ef 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -6,6 +6,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.NetworkServices import com.tangem.tap.common.redux.global.GlobalMiddleware import com.tangem.tap.common.redux.global.GlobalState +import com.tangem.tap.common.redux.legacy.LegacyMiddleware import com.tangem.tap.common.redux.navigation.navigationMiddleware import com.tangem.tap.features.details.redux.DetailsMiddleware import com.tangem.tap.features.details.redux.DetailsState @@ -71,7 +72,7 @@ data class AppState( val daggerGraphState: DaggerGraphState = DaggerGraphState(), ) : StateType { - val domainState: DomainState + private val domainState: DomainState get() = domainStore.state val domainNetworks: NetworkServices @@ -111,6 +112,7 @@ data class AppState( SprinklrMiddleware().middleware, SignInMiddleware.middleware, DaggerGraphMiddleware.daggerGraphMiddleware, + LegacyMiddleware.legacyMiddleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt new file mode 100644 index 0000000000..495a45f59b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.common.redux.legacy + +import com.tangem.domain.redux.LegacyAction +import com.tangem.tap.common.feedback.RateCanBeBetterEmail +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.store +import org.rekotlin.Middleware + +internal object LegacyMiddleware { + val legacyMiddleware: Middleware = { _, _ -> + { next -> + { action -> + when (action) { + is LegacyAction.SendEmailRateCanBeBetter -> { + store.state.globalState.feedbackManager?.sendEmail(RateCanBeBetterEmail()) + } + } + next(action) + } + } + } +} \ No newline at end of file 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 a25d200f68..afe7009baa 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 @@ -5,6 +5,7 @@ import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase 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.tap.domain.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository @@ -20,8 +21,28 @@ internal object SettingsDomainModule { @Provides @ViewModelScoped - fun providesGetWalletsUseCase(settingsRepository: SettingsRepository): IsUserAlreadyRateAppUseCase { - return IsUserAlreadyRateAppUseCase(settingsRepository = settingsRepository) + fun providesIsReadyToShowRatingUseCase(appRatingRepository: AppRatingRepository): IsReadyToShowRateAppUseCase { + return IsReadyToShowRateAppUseCase(appRatingRepository = appRatingRepository) + } + + @Provides + @ViewModelScoped + fun providesRemindToRateAppLaterUseCase(appRatingRepository: AppRatingRepository): RemindToRateAppLaterUseCase { + return RemindToRateAppLaterUseCase(appRatingRepository = appRatingRepository) + } + + @Provides + @ViewModelScoped + fun providesNeverToSuggestRateAppUseCase(appRatingRepository: AppRatingRepository): NeverToSuggestRateAppUseCase { + return NeverToSuggestRateAppUseCase(appRatingRepository = appRatingRepository) + } + + @Provides + @ViewModelScoped + fun providesSetWalletWithFundsFoundUseCase( + appRatingRepository: AppRatingRepository, + ): SetWalletWithFundsFoundUseCase { + return SetWalletWithFundsFoundUseCase(appRatingRepository = appRatingRepository) } @Provides diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AppCurrencyDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AppCurrencyDataModule.kt index b6d8832f86..e25e15471b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AppCurrencyDataModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AppCurrencyDataModule.kt @@ -7,8 +7,8 @@ import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore import com.tangem.datasource.local.appcurrency.implementation.DefaultAvailableAppCurrenciesStore import com.tangem.datasource.local.appcurrency.implementation.DefaultSelectedAppCurrencyStore +import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore import com.tangem.datasource.local.datastore.RuntimeDataStore -import com.tangem.datasource.local.datastore.SharedPreferencesDataStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -35,7 +35,7 @@ internal object AppCurrencyDataModule { @NetworkMoshi moshi: Moshi, ): SelectedAppCurrencyStore { return DefaultSelectedAppCurrencyStore( - dataStore = SharedPreferencesDataStore( + dataStore = JsonSharedPreferencesDataStore( preferencesName = "selected_app_currency", context = context, adapter = moshi.adapter(CurrenciesResponse.Currency::class.java), diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AppThemeModeDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AppThemeModeDataModule.kt index 2041fdee99..a6e8020146 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AppThemeModeDataModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AppThemeModeDataModule.kt @@ -4,7 +4,7 @@ import android.content.Context import com.squareup.moshi.Moshi import com.tangem.datasource.local.apptheme.AppThemeModeStore import com.tangem.datasource.local.apptheme.DefaultAppThemeModeStore -import com.tangem.datasource.local.datastore.SharedPreferencesDataStore +import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore import com.tangem.domain.apptheme.model.AppThemeMode import dagger.Module import dagger.Provides @@ -19,7 +19,7 @@ internal object AppThemeModeDataModule { @Provides fun provideAppThemeModeStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): AppThemeModeStore { return DefaultAppThemeModeStore( - dataStore = SharedPreferencesDataStore( + dataStore = JsonSharedPreferencesDataStore( preferencesName = "app_theme", context = context, adapter = moshi.adapter(AppThemeMode::class.java), diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/CardDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/CardDataModule.kt index 9855d09182..f06e5ac8e5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/CardDataModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/CardDataModule.kt @@ -6,7 +6,7 @@ import com.squareup.moshi.Types import com.tangem.datasource.local.card.DefaultUsedCardsStore import com.tangem.datasource.local.card.UsedCardInfo import com.tangem.datasource.local.card.UsedCardsStore -import com.tangem.datasource.local.datastore.SharedPreferencesDataStore +import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -22,7 +22,7 @@ internal object CardDataModule { @Singleton fun provideUsedCardsStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): UsedCardsStore { return DefaultUsedCardsStore( - store = SharedPreferencesDataStore( + store = JsonSharedPreferencesDataStore( preferencesName = "tapPrefs", context = context, adapter = moshi.adapter( diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt index 3cb897a49f..9b59f30477 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt @@ -4,7 +4,7 @@ import android.content.Context import com.squareup.moshi.Moshi import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore import com.tangem.datasource.local.appcurrency.implementation.BalanceStateHidingSettingsStore -import com.tangem.datasource.local.datastore.SharedPreferencesDataStore +import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore import com.tangem.domain.balancehiding.BalanceHidingSettings import dagger.Module import dagger.Provides @@ -22,7 +22,7 @@ internal object HiddenBalanceDataModule { @NetworkMoshi moshi: Moshi, ): BalanceHidingSettingsStore { return BalanceStateHidingSettingsStore( - dataStore = SharedPreferencesDataStore( + dataStore = JsonSharedPreferencesDataStore( preferencesName = "balance_hiding_settings", context = context, adapter = moshi.adapter(BalanceHidingSettings::class.java), diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/QuotesStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/QuotesStoreModule.kt index a62a3e4f31..91de405a4b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/QuotesStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/QuotesStoreModule.kt @@ -2,7 +2,7 @@ package com.tangem.datasource.di import android.content.Context import com.squareup.moshi.Moshi -import com.tangem.datasource.local.datastore.SharedPreferencesDataStore +import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore import com.tangem.datasource.local.quote.DefaultQuotesStore import com.tangem.datasource.local.quote.QuotesStore import com.tangem.datasource.local.quote.model.StoredQuote @@ -21,7 +21,7 @@ internal object QuotesStoreModule { @Singleton fun provideQuotesStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): QuotesStore { return DefaultQuotesStore( - dataStore = SharedPreferencesDataStore( + dataStore = JsonSharedPreferencesDataStore( preferencesName = "quotes", context = context, adapter = moshi.adapter(StoredQuote::class.java), diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/SettingsDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/SettingsDataModule.kt new file mode 100644 index 0000000000..ba757806d6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/SettingsDataModule.kt @@ -0,0 +1,50 @@ +package com.tangem.datasource.di + +import android.content.Context +import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.LongSharedPreferencesDataStore +import com.tangem.datasource.local.settings.* +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object SettingsDataModule { + + @Provides + @Singleton + fun provideAppLaunchCountStore(@ApplicationContext context: Context): AppLaunchCountStore { + return DefaultAppLaunchCountStore( + store = IntSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context), + ) + } + + @Provides + @Singleton + fun provideAppRatingShowingCountStore(@ApplicationContext context: Context): AppRatingShowingCountStore { + return DefaultAppRatingShowingCountStore( + store = IntSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context), + ) + } + + @Provides + @Singleton + fun provideFundsFoundDateInMillisStore(@ApplicationContext context: Context): FundsFoundDateInMillisStore { + return DefaultFundsFoundDateInMillisStore( + store = LongSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context), + ) + } + + @Provides + @Singleton + fun provideUserInteractingStatusStore(@ApplicationContext context: Context): UserInteractingStatusStore { + return DefaultUserInteractingStatusStore( + store = BooleanSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/BooleanSharedPreferencesDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/BooleanSharedPreferencesDataStore.kt new file mode 100644 index 0000000000..36141207c0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/BooleanSharedPreferencesDataStore.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.local.datastore + +import android.content.Context +import androidx.core.content.edit + +internal class BooleanSharedPreferencesDataStore( + preferencesName: String, + context: Context, +) : SharedPreferencesDataStore(preferencesName, context) { + + override fun getByKey(key: String): Boolean = sharedPreferences.getBoolean(key, false) + + override fun storeByKey(key: String, value: Boolean) { + sharedPreferences.edit { putBoolean(key, value) } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/IntSharedPreferencesDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/IntSharedPreferencesDataStore.kt new file mode 100644 index 0000000000..7ef0f70fa6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/IntSharedPreferencesDataStore.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.local.datastore + +import android.content.Context +import androidx.core.content.edit + +internal class IntSharedPreferencesDataStore( + preferencesName: String, + context: Context, +) : SharedPreferencesDataStore(preferencesName, context) { + + override fun getByKey(key: String): Int = sharedPreferences.getInt(key, 0) + + override fun storeByKey(key: String, value: Int) { + sharedPreferences.edit { putInt(key, value) } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/JsonSharedPreferencesDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/JsonSharedPreferencesDataStore.kt new file mode 100644 index 0000000000..5e47ee8114 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/JsonSharedPreferencesDataStore.kt @@ -0,0 +1,24 @@ +package com.tangem.datasource.local.datastore + +import android.content.Context +import androidx.core.content.edit +import com.squareup.moshi.JsonAdapter + +internal class JsonSharedPreferencesDataStore( + preferencesName: String, + context: Context, + private val adapter: JsonAdapter, +) : SharedPreferencesDataStore(preferencesName, context) { + + override fun getByKey(key: String): Value? { + val json = sharedPreferences.getString(key, null) ?: return null + + return adapter.fromJson(json) + } + + override fun storeByKey(key: String, value: Value) { + val json = adapter.toJson(value) + + sharedPreferences.edit { putString(key, json) } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/LongSharedPreferencesDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/LongSharedPreferencesDataStore.kt new file mode 100644 index 0000000000..b1bb00e4e1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/LongSharedPreferencesDataStore.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.local.datastore + +import android.content.Context +import androidx.core.content.edit + +internal class LongSharedPreferencesDataStore( + preferencesName: String, + context: Context, +) : SharedPreferencesDataStore(preferencesName, context) { + + override fun getByKey(key: String): Long = sharedPreferences.getLong(key, 0) + + override fun storeByKey(key: String, value: Long) { + sharedPreferences.edit { putLong(key, value) } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/SharedPreferencesDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/SharedPreferencesDataStore.kt index f3854bc37d..3924bc0104 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/SharedPreferencesDataStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/SharedPreferencesDataStore.kt @@ -4,53 +4,47 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import androidx.core.content.edit -import com.squareup.moshi.JsonAdapter import com.tangem.datasource.local.datastore.core.StringKeyDataStore import com.tangem.datasource.local.datastore.utils.Trigger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import timber.log.Timber -internal class SharedPreferencesDataStore( +internal abstract class SharedPreferencesDataStore( preferencesName: String, - private val context: Context, - private val adapter: JsonAdapter, + context: Context, ) : StringKeyDataStore { - private val sharedPreferences: SharedPreferences by lazy { + protected val sharedPreferences: SharedPreferences by lazy { context.getSharedPreferences(preferencesName, MODE_PRIVATE) } private val writeTrigger = Trigger() + abstract fun getByKey(key: String): Value? + + abstract fun storeByKey(key: String, value: Value) + override fun get(key: String): Flow { return writeTrigger - .map { getInternal(key) } - .filterNotNull() + .mapNotNull { getInternal(key) } .distinctUntilChanged() } override fun getAll(): Flow> { - return writeTrigger - .map { getAllInternal() } - .distinctUntilChanged() + throw UnsupportedOperationException("Unknown key") } - override suspend fun getSyncOrNull(key: String): Value? { - return getInternal(key) - } + override suspend fun getSyncOrNull(key: String): Value? = getInternal(key) override suspend fun getAllSyncOrNull(): List { - return getAllInternal() + throw UnsupportedOperationException("Unknown key") } override suspend fun store(key: String, value: Value) { try { - val json = adapter.toJson(value) - - sharedPreferences.edit { putString(key, json) } + storeByKey(key, value) writeTrigger.trigger() } catch (e: Throwable) { Timber.e(e, "Unable to edit preferences: $key") @@ -74,26 +68,13 @@ internal class SharedPreferencesDataStore( } private fun getInternal(key: String): Value? { - return try { - val json = sharedPreferences.getString(key, null) ?: return null + if (!sharedPreferences.contains(key)) return null - adapter.fromJson(json) + return try { + getByKey(key) } catch (e: Throwable) { Timber.e(e, "Unable to get value from preferences: $key") null } } - - private fun getAllInternal(): List { - return sharedPreferences.all.mapNotNull { (key, value) -> - try { - val json = value as? String ?: return@mapNotNull null - - adapter.fromJson(json) - } catch (e: Throwable) { - Timber.e(e, "Unable to convert value from JSON: $key") - null - } - } - } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppLaunchCountStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppLaunchCountStore.kt new file mode 100644 index 0000000000..a2ee494307 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppLaunchCountStore.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.settings + +import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator +import kotlinx.coroutines.flow.Flow + +interface AppLaunchCountStore { + + fun get(): Flow + + suspend fun getSyncOrNull(): Int? + + suspend fun store(item: Int) +} + +internal class DefaultAppLaunchCountStore( + store: IntSharedPreferencesDataStore, +) : AppLaunchCountStore, KeylessDataStoreDecorator( + wrappedDataStore = store, + key = "launchCount", +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppRatingShowingCountStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppRatingShowingCountStore.kt new file mode 100644 index 0000000000..dfbb452f22 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/settings/AppRatingShowingCountStore.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.settings + +import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator +import kotlinx.coroutines.flow.Flow + +interface AppRatingShowingCountStore { + + fun get(): Flow + + suspend fun getSyncOrNull(): Int? + + suspend fun store(item: Int) +} + +internal class DefaultAppRatingShowingCountStore( + store: IntSharedPreferencesDataStore, +) : AppRatingShowingCountStore, KeylessDataStoreDecorator( + wrappedDataStore = store, + key = "showRatingDialogAtLaunchCount", +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/settings/FundsFoundDateInMillisStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/settings/FundsFoundDateInMillisStore.kt new file mode 100644 index 0000000000..1e6a50f959 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/settings/FundsFoundDateInMillisStore.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.settings + +import com.tangem.datasource.local.datastore.LongSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator +import kotlinx.coroutines.flow.Flow + +interface FundsFoundDateInMillisStore { + + fun get(): Flow + + suspend fun getSyncOrNull(): Long? + + suspend fun store(item: Long) +} + +internal class DefaultFundsFoundDateInMillisStore( + store: LongSharedPreferencesDataStore, +) : FundsFoundDateInMillisStore, KeylessDataStoreDecorator( + wrappedDataStore = store, + key = "fundsFoundDate", +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/settings/UserInteractingStatusStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/settings/UserInteractingStatusStore.kt new file mode 100644 index 0000000000..1a1ed3bf03 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/settings/UserInteractingStatusStore.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.settings + +import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore +import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator +import kotlinx.coroutines.flow.Flow + +interface UserInteractingStatusStore { + + fun get(): Flow + + suspend fun getSyncOrNull(): Boolean? + + suspend fun store(item: Boolean) +} + +internal class DefaultUserInteractingStatusStore( + store: BooleanSharedPreferencesDataStore, +) : UserInteractingStatusStore, KeylessDataStoreDecorator( + wrappedDataStore = store, + key = "userWasInteractWithRating", +) \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultAppRatingRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultAppRatingRepository.kt new file mode 100644 index 0000000000..395542253b --- /dev/null +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultAppRatingRepository.kt @@ -0,0 +1,102 @@ +package com.tangem.data.settings + +import com.tangem.datasource.local.settings.AppLaunchCountStore +import com.tangem.datasource.local.settings.AppRatingShowingCountStore +import com.tangem.datasource.local.settings.FundsFoundDateInMillisStore +import com.tangem.datasource.local.settings.UserInteractingStatusStore +import com.tangem.domain.settings.repositories.AppRatingRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.withContext +import java.util.Calendar + +internal class DefaultAppRatingRepository( + private val fundsFoundDateInMillisStore: FundsFoundDateInMillisStore, + private val appLaunchCountStore: AppLaunchCountStore, + private val appRatingShowingCountStore: AppRatingShowingCountStore, + private val userInteractingStatusStore: UserInteractingStatusStore, + private val dispatchers: CoroutineDispatcherProvider, +) : AppRatingRepository { + + override suspend fun initialize() { + fundsFoundDateInMillisStore.getSyncOrNull() + ?: fundsFoundDateInMillisStore.store(item = FUNDS_FOUND_DATE_UNDEFINED) + + appLaunchCountStore.getSyncOrNull() + ?: appLaunchCountStore.store(item = DEFAULT_APP_LAUNCH_COUNT) + + appRatingShowingCountStore.getSyncOrNull() + ?: appRatingShowingCountStore.store(item = FIRST_SHOWING_COUNT) + + userInteractingStatusStore.getSyncOrNull() + ?: userInteractingStatusStore.store(item = false) + } + + override suspend fun setWalletWithFundsFound() { + withContext(dispatchers.io) { + val foundDate = fundsFoundDateInMillisStore.getSyncOrNull() + if (foundDate != null && foundDate != FUNDS_FOUND_DATE_UNDEFINED) return@withContext + + fundsFoundDateInMillisStore.store(item = Calendar.getInstance().timeInMillis) + + val appLaunchCount = appLaunchCountStore.getSyncOrNull().let { count -> + if (count == null) { + appLaunchCountStore.store(item = DEFAULT_APP_LAUNCH_COUNT) + DEFAULT_APP_LAUNCH_COUNT + } else { + count + } + } + + appRatingShowingCountStore.store(item = appLaunchCount + FIRST_SHOWING_COUNT) + + userInteractingStatusStore.getSyncOrNull() + ?: userInteractingStatusStore.store(item = false) + } + } + + override fun isReadyToShow(): Flow { + // TODO: [REDACTED_JIRA] + return combine( + userInteractingStatusStore.get(), + appRatingShowingCountStore.get(), + appLaunchCountStore.get(), + fundsFoundDateInMillisStore.get(), + ) { isInteracting, ratingShowingCount, appLaunchCount, fundsFoundDate -> + if (!isInteracting) { + val diff = Calendar.getInstance().timeInMillis - fundsFoundDate + val diffInDays = diff / DAY_IN_MILLIS + + appLaunchCount >= ratingShowingCount && diffInDays >= FIRST_SHOWING_COUNT + } else { + appLaunchCount >= ratingShowingCount + } + } + } + + override suspend fun remindLater() { + appLaunchCountStore.getSyncOrNull()?.let { + updateNextShowing(at = it + DEFER_SHOWING_COUNT) + } + } + + override suspend fun setNeverToShow() { + updateNextShowing(at = Int.MAX_VALUE) + } + + private suspend fun updateNextShowing(at: Int) { + withContext(dispatchers.io) { + appRatingShowingCountStore.store(item = at) + userInteractingStatusStore.store(item = true) + } + } + + private companion object { + const val FUNDS_FOUND_DATE_UNDEFINED = -1L + const val DEFAULT_APP_LAUNCH_COUNT = 1 + const val FIRST_SHOWING_COUNT = 3 + const val DEFER_SHOWING_COUNT = 20 + const val DAY_IN_MILLIS = 1000 * 60 * 60 * 24 + } +} \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index c4d0074263..0fed0929d9 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -10,12 +10,6 @@ internal class DefaultSettingsRepository( private val dispatchers: CoroutineDispatcherProvider, ) : SettingsRepository { - override suspend fun isUserAlreadyRateApp(): Boolean { - return withContext(dispatchers.io) { - preferencesDataSource.appRatingLaunchObserver.isReadyToShow() - } - } - override suspend fun shouldShowSaveUserWalletScreen(): Boolean { return withContext(dispatchers.io) { preferencesDataSource.shouldShowSaveUserWalletScreen } } 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 6d0736bdf1..68355c5d61 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 @@ -1,7 +1,13 @@ package com.tangem.data.settings.di +import com.tangem.data.settings.DefaultAppRatingRepository import com.tangem.data.settings.DefaultSettingsRepository import com.tangem.data.source.preferences.PreferencesDataSource +import com.tangem.datasource.local.settings.AppLaunchCountStore +import com.tangem.datasource.local.settings.AppRatingShowingCountStore +import com.tangem.datasource.local.settings.FundsFoundDateInMillisStore +import com.tangem.datasource.local.settings.UserInteractingStatusStore +import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -25,4 +31,22 @@ internal object SettingsDataModule { dispatchers = dispatchers, ) } + + @Provides + @Singleton + fun provideAppRatingRepository( + fundsFoundDateInMillisStore: FundsFoundDateInMillisStore, + appLaunchCountStore: AppLaunchCountStore, + appRatingShowingCountStore: AppRatingShowingCountStore, + userInteractingStatusStore: UserInteractingStatusStore, + dispatchers: CoroutineDispatcherProvider, + ): AppRatingRepository { + return DefaultAppRatingRepository( + fundsFoundDateInMillisStore = fundsFoundDateInMillisStore, + appLaunchCountStore = appLaunchCountStore, + appRatingShowingCountStore = appRatingShowingCountStore, + userInteractingStatusStore = userInteractingStatusStore, + dispatchers = dispatchers, + ) + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt new file mode 100644 index 0000000000..2565d2f226 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.redux + +import org.rekotlin.Action + +sealed interface LegacyAction : Action { + + object SendEmailRateCanBeBetter : LegacyAction +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsReadyToShowRateAppUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsReadyToShowRateAppUseCase.kt new file mode 100644 index 0000000000..0a7ecd2473 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/IsReadyToShowRateAppUseCase.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.AppRatingRepository +import kotlinx.coroutines.flow.Flow + +class IsReadyToShowRateAppUseCase(private val appRatingRepository: AppRatingRepository) { + + operator fun invoke(): Flow = appRatingRepository.isReadyToShow() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsUserAlreadyRateAppUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsUserAlreadyRateAppUseCase.kt deleted file mode 100644 index fa71e80df8..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/IsUserAlreadyRateAppUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.domain.settings - -import com.tangem.domain.settings.repositories.SettingsRepository - -class IsUserAlreadyRateAppUseCase(private val settingsRepository: SettingsRepository) { - - suspend operator fun invoke(): Boolean = settingsRepository.isUserAlreadyRateApp() -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/NeverToSuggestRateAppUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/NeverToSuggestRateAppUseCase.kt new file mode 100644 index 0000000000..282dd05a54 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/NeverToSuggestRateAppUseCase.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.AppRatingRepository + +class NeverToSuggestRateAppUseCase(private val appRatingRepository: AppRatingRepository) { + + suspend operator fun invoke() { + appRatingRepository.setNeverToShow() + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/RemindToRateAppLaterUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/RemindToRateAppLaterUseCase.kt new file mode 100644 index 0000000000..94f46f9512 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/RemindToRateAppLaterUseCase.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.AppRatingRepository + +class RemindToRateAppLaterUseCase(private val appRatingRepository: AppRatingRepository) { + + suspend operator fun invoke() { + appRatingRepository.remindLater() + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/SetWalletWithFundsFoundUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/SetWalletWithFundsFoundUseCase.kt new file mode 100644 index 0000000000..3653df521a --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/SetWalletWithFundsFoundUseCase.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.AppRatingRepository + +class SetWalletWithFundsFoundUseCase(private val appRatingRepository: AppRatingRepository) { + + suspend operator fun invoke() = appRatingRepository.setWalletWithFundsFound() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/AppRatingRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/AppRatingRepository.kt new file mode 100644 index 0000000000..e10dc94046 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/AppRatingRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.settings.repositories + +import kotlinx.coroutines.flow.Flow + +interface AppRatingRepository { + + // FIXME: We must to initialize all stores before calling [isReadyToShow], otherwise flow will not emit data + suspend fun initialize() + + suspend fun setWalletWithFundsFound() + + fun isReadyToShow(): Flow + + suspend fun remindLater() + + suspend fun setNeverToShow() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 4b6810e2d4..cdfe2702da 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -2,7 +2,5 @@ package com.tangem.domain.settings.repositories interface SettingsRepository { - suspend fun isUserAlreadyRateApp(): Boolean - suspend fun shouldShowSaveUserWalletScreen(): Boolean } \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 8de129fa16..6edd0059b2 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -29,11 +29,13 @@ dependencies { /** Other libraries */ implementation(deps.arrow.core) + implementation(deps.googlePlay.core) implementation(deps.jodatime) implementation(deps.kotlin.immutable.collections) implementation(deps.reKotlin) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) + implementation(deps.timber) /** DI */ implementation(deps.hilt.android) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt index 8b7d294ae4..d399e75ad4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt @@ -15,4 +15,6 @@ internal sealed class WalletEvent { data class CopyAddress(val address: String) : WalletEvent() data class ShowWalletAlreadySignedHashesMessage(val onUnderstandClick: () -> Unit) : WalletEvent() + + data class RateApp(val onDismissClick: () -> Unit) : WalletEvent() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt index c38b2b3bfa..a3828feae4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt @@ -135,8 +135,8 @@ sealed class WalletNotification(val config: NotificationConfig) { ) data class RateApp( - val onPositiveClick: () -> Unit, - val onNegativeClick: () -> Unit, + val onLikeClick: () -> Unit, + val onDislikeClick: () -> Unit, val onCloseClick: () -> Unit, ) : WalletNotification( config = NotificationConfig( @@ -145,9 +145,9 @@ sealed class WalletNotification(val config: NotificationConfig) { iconResId = R.drawable.ic_star_24, buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig( primaryText = resourceReference(id = R.string.warning_button_love_it), - onPrimaryClick = onPositiveClick, + onPrimaryClick = onLikeClick, secondaryText = resourceReference(id = R.string.warning_button_can_be_better), - onSecondaryClick = onNegativeClick, + onSecondaryClick = onDislikeClick, ), onCloseClick = onCloseClick, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index b667aa10ad..8f1fa60262 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui +import android.app.Activity import android.widget.Toast import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.SnackbarHostState @@ -7,11 +8,16 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString +import com.google.android.play.core.review.ReviewInfo +import com.google.android.play.core.review.ReviewManager +import com.google.android.play.core.review.ReviewManagerFactory +import com.google.android.play.core.tasks.Task import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent +import timber.log.Timber @Composable internal fun WalletEventEffect( @@ -46,7 +52,40 @@ internal fun WalletEventEffect( WalletAlertState.WalletAlreadySignedHashes(onUnderstandClick = value.onUnderstandClick), ) } + is WalletEvent.RateApp -> { + val reviewManager = ReviewManagerFactory.create(context) + val requestTask = reviewManager.requestReviewFlow() + + requestTask + .addOnCompleteListener { + handleOnCompleteRequestTask( + reviewManager = reviewManager, + activity = context as? Activity ?: return@addOnCompleteListener, + task = it, + onDismissClick = value.onDismissClick, + ) + } + .addOnFailureListener(Timber::e) + } } }, ) +} + +private fun handleOnCompleteRequestTask( + reviewManager: ReviewManager, + activity: Activity, + task: Task, + onDismissClick: () -> Unit, +) { + if (task.isSuccessful) { + val reviewFlow = reviewManager.launchReviewFlow(activity, task.result) + reviewFlow + .addOnCompleteListener { resultReviewTask -> + if (!resultReviewTask.isSuccessful) onDismissClick() + } + .addOnFailureListener(Timber::e) + } else { + Timber.e(task.exception) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt index 23fe4c6ba0..59acf7726c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt @@ -19,9 +19,11 @@ internal interface WalletClickIntents { fun onMultiWalletSignedHashesNotificationClick() - fun onLikeTangemAppClick() + fun onLikeAppClick() - fun onRateTheAppClick() + fun onDislikeAppClick() + + fun onCloseRateAppNotificationClick() fun onWalletChange(index: Int) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt index d891658958..928b02cc55 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt @@ -3,22 +3,21 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels import com.tangem.domain.card.WasCardScannedUseCase import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase +import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlin.collections.count /** * Wallet notifications list factory * * @property isDemoCardUseCase use case that check if card is demo - * @property isUserAlreadyRateAppUseCase use case that check if card is user already rate app + * @property isReadyToShowRateAppUseCase use case that check if card is user already rate app * @property wasCardScannedUseCase use case that check if card was scanned * @property clickIntents screen click intents * @@ -26,7 +25,7 @@ import kotlin.collections.count */ internal class WalletNotificationsListFactory( private val isDemoCardUseCase: IsDemoCardUseCase, - private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase, + private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val wasCardScannedUseCase: WasCardScannedUseCase, private val clickIntents: WalletClickIntents, ) { @@ -35,19 +34,20 @@ internal class WalletNotificationsListFactory( cardTypesResolver: CardTypesResolver, cryptoCurrencyList: List, ): Flow> { - return wasCardScannedUseCase.invoke(cardTypesResolver.getCardId()) - .distinctUntilChanged() - .map { - buildList { - addCriticalNotifications(cardTypesResolver) + return combine( + flow = wasCardScannedUseCase(cardTypesResolver.getCardId()), + flow2 = isReadyToShowRateAppUseCase(), + ) { wasCardScanned, isReadyToShowRating -> + buildList { + addCriticalNotifications(cardTypesResolver) - addMissingAddressesNotification(cryptoCurrencyList) + addMissingAddressesNotification(cryptoCurrencyList) - addRateTheAppNotification() + addRateTheAppNotification(isReadyToShowRating) - addWarningNotifications(cardTypesResolver, cryptoCurrencyList, it) - }.toImmutableList() - } + addWarningNotifications(cardTypesResolver, cryptoCurrencyList, wasCardScanned) + }.toImmutableList() + } } private fun MutableList.addCriticalNotifications(cardTypesResolver: CardTypesResolver) { @@ -101,15 +101,14 @@ internal class WalletNotificationsListFactory( .map(CryptoCurrencyStatus::currency) } - // TODO: [REDACTED_JIRA] - private suspend fun MutableList.addRateTheAppNotification() { + private fun MutableList.addRateTheAppNotification(isReadyToShowRating: Boolean) { addIf( element = WalletNotification.RateApp( - onPositiveClick = {}, - onNegativeClick = {}, - onCloseClick = {}, + onLikeClick = clickIntents::onLikeAppClick, + onDislikeClick = clickIntents::onDislikeAppClick, + onCloseClick = clickIntents::onCloseRateAppNotificationClick, ), - condition = isUserAlreadyRateAppUseCase(), + condition = isReadyToShowRating, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index df90b7378b..5002ad2f87 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.extensions.isZero import com.tangem.common.extensions.toMapKey import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.navigation.AppScreen @@ -30,13 +31,15 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.settings.CanUseBiometryUseCase -import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase -import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase +import com.tangem.domain.settings.* import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.* +import com.tangem.domain.tokens.model.CryptoCurrency +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.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.userwallets.UserWalletBuilder @@ -107,8 +110,11 @@ internal class WalletViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val analyticsEventsHandler: AnalyticsEventHandler, private val setCardWasScannedUseCase: SetCardWasScannedUseCase, + private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase, + private val neverToSuggestRateAppUseCase: NeverToSuggestRateAppUseCase, + private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, wasCardScannedUseCase: WasCardScannedUseCase, - isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase, + isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, isDemoCardUseCase: IsDemoCardUseCase, // endregion Parameters ) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents { @@ -121,7 +127,7 @@ internal class WalletViewModel @Inject constructor( private val notificationsListFactory = WalletNotificationsListFactory( wasCardScannedUseCase = wasCardScannedUseCase, - isUserAlreadyRateAppUseCase = isUserAlreadyRateAppUseCase, + isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase, isDemoCardUseCase = isDemoCardUseCase, clickIntents = this, ) @@ -431,12 +437,32 @@ internal class WalletViewModel @Inject constructor( ) } - override fun onLikeTangemAppClick() { - // TODO: [REDACTED_JIRA] + override fun onLikeAppClick() { + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = WalletEvent.RateApp( + onDismissClick = { + viewModelScope.launch(dispatchers.main) { + neverToSuggestRateAppUseCase() + } + }, + ), + setUiState = { uiState = it }, + ) } - override fun onRateTheAppClick() { - // TODO: [REDACTED_JIRA] + override fun onDislikeAppClick() { + viewModelScope.launch(dispatchers.main) { + neverToSuggestRateAppUseCase() + + reduxStateHolder.dispatch(LegacyAction.SendEmailRateCanBeBetter) + } + } + + override fun onCloseRateAppNotificationClick() { + viewModelScope.launch(dispatchers.main) { + remindToRateAppLaterUseCase() + } } override fun onWalletChange(index: Int) { @@ -780,6 +806,8 @@ internal class WalletViewModel @Inject constructor( .onEach { maybeTokenList -> uiState = stateFactory.getStateByTokensList(maybeTokenList) + maybeTokenList.onRight { checkMultiWalletWithFunds(it) } + updateNotifications( index = walletIndex, tokenList = maybeTokenList.fold(ifLeft = { null }, ifRight = { it }), @@ -790,6 +818,29 @@ internal class WalletViewModel @Inject constructor( .saveIn(tokensJobHolder) } + private suspend fun checkMultiWalletWithFunds(tokenList: TokenList) { + val hasNonZeroWallets = when (tokenList) { + is TokenList.GroupedByNetwork -> { + tokenList.groups + .flatMap(NetworkGroup::currencies) + .hasNonZeroWallets() + } + is TokenList.Ungrouped -> tokenList.currencies.hasNonZeroWallets() + TokenList.NotInitialized -> false + } + + if (hasNonZeroWallets) { + setWalletWithFundsFoundUseCase() + } + } + + private fun List.hasNonZeroWallets(): Boolean { + return any { + val amount = it.value.amount ?: return@any false + !amount.isZero() + } + } + private fun getSingleCurrencyContent(index: Int) { val wallet = getWallet(index) updatePrimaryCurrencyStatus(userWalletId = wallet.walletId) @@ -822,6 +873,11 @@ internal class WalletViewModel @Inject constructor( maybeCryptoCurrencyStatus.onRight { status -> singleWalletCryptoCurrencyStatus = status + + if (status.value.amount?.isZero() == false) { + setWalletWithFundsFoundUseCase() + } + updateButtons(userWalletId = userWalletId, currencyStatus = status) updateTxHistory(status.currency) }