Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 14:00:15 +02:00
commit c72fb58654
72 changed files with 552 additions and 989 deletions

View file

@ -30,7 +30,6 @@ 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.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -209,9 +208,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var userTokensStore: UserTokensStore
@Inject
lateinit var appRatingRepository: AppRatingRepository
@Inject
lateinit var getAppThemeModeUseCase: GetAppThemeModeUseCase
@ -248,10 +244,8 @@ internal class TapApplication : Application(), ImageLoaderFactory {
// TODO: Try to performance and user experience.
// [REDACTED_JIRA]
runBlocking {
walletsRepository.initialize()
initUserWalletsListManager()
featureTogglesManager.init()
appRatingRepository.initialize()
// learn2earnInteractor.init()
}

View file

@ -36,8 +36,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Suppress("LargeClass")
class TradeCryptoMiddleware {
@ -259,7 +257,7 @@ class TradeCryptoMiddleware {
private fun openSwap(currency: CryptoCurrency) {
val bundle = bundleOf(
SwapFragment.CURRENCY_BUNDLE_KEY to currency
SwapFragment.CURRENCY_BUNDLE_KEY to currency,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))

View file

@ -1,18 +1,11 @@
package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
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 dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@ -23,23 +16,6 @@ internal object AppCurrencyDataModule {
@Provides
@Singleton
fun provideAvailableAppCurrenciesStore(): AvailableAppCurrenciesStore {
return DefaultAvailableAppCurrenciesStore(
dataStore = RuntimeDataStore(),
)
}
@Provides
@Singleton
fun provideSelectedAppCurrencyStore(
@ApplicationContext context: Context,
@NetworkMoshi moshi: Moshi,
): SelectedAppCurrencyStore {
return DefaultSelectedAppCurrencyStore(
dataStore = JsonSharedPreferencesDataStore(
preferencesName = "selected_app_currency",
context = context,
adapter = moshi.adapter(CurrenciesResponse.Currency::class.java),
),
)
return DefaultAvailableAppCurrenciesStore(dataStore = RuntimeDataStore())
}
}

View file

@ -1,29 +0,0 @@
package com.tangem.datasource.di
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.JsonSharedPreferencesDataStore
import com.tangem.domain.apptheme.model.AppThemeMode
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal object AppThemeModeDataModule {
@Provides
fun provideAppThemeModeStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): AppThemeModeStore {
return DefaultAppThemeModeStore(
dataStore = JsonSharedPreferencesDataStore(
preferencesName = "app_theme",
context = context,
adapter = moshi.adapter(AppThemeMode::class.java),
),
)
}
}

View file

@ -1,34 +0,0 @@
package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
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.JsonSharedPreferencesDataStore
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 CardDataModule {
@Provides
@Singleton
fun provideUsedCardsStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): UsedCardsStore {
return DefaultUsedCardsStore(
store = JsonSharedPreferencesDataStore(
preferencesName = "tapPrefs",
context = context,
adapter = moshi.adapter(
Types.newParameterizedType(List::class.java, UsedCardInfo::class.java),
),
),
)
}
}

View file

@ -1,32 +0,0 @@
package com.tangem.datasource.di
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.JsonSharedPreferencesDataStore
import com.tangem.domain.balancehiding.BalanceHidingSettings
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal object HiddenBalanceDataModule {
@Provides
fun provideHiddenBalanceStateStore(
@ApplicationContext context: Context,
@NetworkMoshi moshi: Moshi,
): BalanceHidingSettingsStore {
return BalanceStateHidingSettingsStore(
dataStore = JsonSharedPreferencesDataStore(
preferencesName = "balance_hiding_settings",
context = context,
adapter = moshi.adapter(BalanceHidingSettings::class.java),
),
)
}
}

View file

@ -1,50 +0,0 @@
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),
)
}
}

View file

@ -1,26 +0,0 @@
package com.tangem.datasource.di
import android.content.Context
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
import com.tangem.datasource.local.settings.*
import com.tangem.datasource.local.userwallet.DefaultShouldSaveUserWalletStore
import com.tangem.datasource.local.userwallet.ShouldSaveUserWalletStore
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 WalletsDataModule {
@Provides
@Singleton
fun provideShouldSaveUserWalletsStore(@ApplicationContext context: Context): ShouldSaveUserWalletStore {
return DefaultShouldSaveUserWalletStore(
store = BooleanSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
)
}
}

View file

@ -5,6 +5,7 @@ package com.tangem.datasource.local
*
[REDACTED_AUTHOR]
*/
@Deprecated(message = "Use AppPreferencesStore", level = DeprecationLevel.WARNING)
interface AppPreferenceStorage {
/** Json config with feature toggles 'ToggleName: String - Availability: Boolean' */

View file

@ -1,13 +0,0 @@
package com.tangem.datasource.local.appcurrency
import com.tangem.domain.balancehiding.BalanceHidingSettings
import kotlinx.coroutines.flow.Flow
interface BalanceHidingSettingsStore {
fun get(): Flow<BalanceHidingSettings>
suspend fun getSyncOrDefault(): BalanceHidingSettings
suspend fun store(settings: BalanceHidingSettings)
}

View file

@ -1,15 +0,0 @@
package com.tangem.datasource.local.appcurrency
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import kotlinx.coroutines.flow.Flow
interface SelectedAppCurrencyStore {
fun get(): Flow<CurrenciesResponse.Currency>
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
suspend fun store(item: CurrenciesResponse.Currency)
suspend fun isEmpty(): Boolean
}

View file

@ -1,19 +0,0 @@
package com.tangem.datasource.local.appcurrency.implementation
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.balancehiding.BalanceHidingSettings
internal class BalanceStateHidingSettingsStore(
dataStore: StringKeyDataStore<BalanceHidingSettings>,
) : BalanceHidingSettingsStore, KeylessDataStoreDecorator<BalanceHidingSettings>(dataStore) {
override suspend fun getSyncOrDefault(): BalanceHidingSettings {
return getSyncOrNull() ?: BalanceHidingSettings(
isHidingEnabledInSettings = false,
isBalanceHidden = false,
isBalanceHidingNotificationEnabled = true,
)
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.datasource.local.appcurrency.implementation
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
internal class DefaultSelectedAppCurrencyStore(
dataStore: StringKeyDataStore<CurrenciesResponse.Currency>,
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore)

View file

@ -1,13 +0,0 @@
package com.tangem.datasource.local.apptheme
import com.tangem.domain.apptheme.model.AppThemeMode
import kotlinx.coroutines.flow.Flow
interface AppThemeModeStore {
fun get(): Flow<AppThemeMode>
suspend fun store(item: AppThemeMode)
suspend fun isEmpty(): Boolean
}

View file

@ -1,9 +0,0 @@
package com.tangem.datasource.local.apptheme
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.apptheme.model.AppThemeMode
internal class DefaultAppThemeModeStore(
dataStore: StringKeyDataStore<AppThemeMode>,
) : AppThemeModeStore, KeylessDataStoreDecorator<AppThemeMode>(dataStore)

View file

@ -1,21 +0,0 @@
package com.tangem.datasource.local.card
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import kotlinx.coroutines.flow.Flow
interface UsedCardsStore {
fun get(): Flow<List<UsedCardInfo>>
suspend fun getSyncOrNull(): List<UsedCardInfo>?
suspend fun store(item: List<UsedCardInfo>)
}
internal class DefaultUsedCardsStore(
store: SharedPreferencesDataStore<List<UsedCardInfo>>,
) : UsedCardsStore, KeylessDataStoreDecorator<List<UsedCardInfo>>(
wrappedDataStore = store,
key = "usedCardsInfo_v2",
)

View file

@ -1,16 +0,0 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import androidx.core.content.edit
internal class BooleanSharedPreferencesDataStore(
preferencesName: String,
context: Context,
) : SharedPreferencesDataStore<Boolean>(preferencesName, context) {
override fun getByKey(key: String): Boolean = sharedPreferences.getBoolean(key, false)
override fun storeByKey(key: String, value: Boolean) {
sharedPreferences.edit { putBoolean(key, value) }
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import androidx.core.content.edit
internal class IntSharedPreferencesDataStore(
preferencesName: String,
context: Context,
) : SharedPreferencesDataStore<Int>(preferencesName, context) {
override fun getByKey(key: String): Int = sharedPreferences.getInt(key, 0)
override fun storeByKey(key: String, value: Int) {
sharedPreferences.edit { putInt(key, value) }
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import androidx.core.content.edit
import com.squareup.moshi.JsonAdapter
internal class JsonSharedPreferencesDataStore<Value : Any>(
preferencesName: String,
context: Context,
private val adapter: JsonAdapter<Value>,
) : SharedPreferencesDataStore<Value>(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) }
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import androidx.core.content.edit
internal class LongSharedPreferencesDataStore(
preferencesName: String,
context: Context,
) : SharedPreferencesDataStore<Long>(preferencesName, context) {
override fun getByKey(key: String): Long = sharedPreferences.getLong(key, 0)
override fun storeByKey(key: String, value: Long) {
sharedPreferences.edit { putLong(key, value) }
}
}

View file

@ -1,98 +0,0 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.core.content.edit
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.mapNotNull
import timber.log.Timber
internal abstract class SharedPreferencesDataStore<Value : Any>(
preferencesName: String,
context: Context,
) : StringKeyDataStore<Value> {
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 suspend fun isEmpty(): Boolean {
return sharedPreferences.all.isEmpty()
}
override suspend fun contains(key: String): Boolean {
return sharedPreferences.contains(key)
}
override fun get(key: String): Flow<Value> {
return writeTrigger
.mapNotNull { getInternal(key) }
.distinctUntilChanged()
}
override fun getAll(): Flow<List<Value>> {
throw UnsupportedOperationException("Unknown key")
}
override suspend fun getSyncOrNull(key: String): Value? = getInternal(key)
override suspend fun getAllSyncOrNull(): List<Value> {
throw UnsupportedOperationException("Unknown key")
}
override suspend fun store(key: String, value: Value) {
try {
storeByKey(key, value)
writeTrigger.trigger()
} catch (e: Throwable) {
Timber.e(e, "Unable to edit preferences: $key")
}
}
override suspend fun store(values: Map<String, Value>) {
values.forEach { (key, item) ->
store(key, item)
}
}
override suspend fun remove(key: String) {
sharedPreferences.edit { remove(key) }
writeTrigger.trigger()
}
override suspend fun remove(keys: Collection<String>) {
sharedPreferences.edit {
keys.forEach { key ->
remove(key)
}
}
writeTrigger.trigger()
}
override suspend fun clear() {
sharedPreferences.edit { clear() }
writeTrigger.trigger()
}
private fun getInternal(key: String): Value? {
if (!sharedPreferences.contains(key)) return null
return try {
getByKey(key)
} catch (e: Throwable) {
Timber.e(e, "Unable to get value from preferences: $key")
null
}
}
}

View file

@ -4,11 +4,13 @@ import android.content.Context
import androidx.datastore.core.DataMigration
import androidx.datastore.core.DataStore
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.preferences.SharedPreferencesMigration
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.tangem.datasource.local.preferences.PreferencesDataStore.INSTANCE
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import timber.log.Timber
@ -23,6 +25,8 @@ import kotlin.coroutines.CoroutineContext
internal object PreferencesDataStore {
private const val PREFERENCES_FILE_NAME = "TAP_PREFS"
private const val LEGACY_TAP_PREFS_FILE_NAME = "tapPrefs"
private const val LEGACY_DEFAULT_KEY_NAME = "key"
private var INSTANCE: DataStore<Preferences>? = null
@ -33,7 +37,7 @@ internal object PreferencesDataStore {
private fun create(context: Context, dispatcher: CoroutineContext): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
corruptionHandler = createCorruptionHandler(),
migrations = createMigrations(),
migrations = createMigrations(context = context),
scope = CoroutineScope(context = dispatcher + SupervisorJob()),
produceFile = { context.preferencesDataStoreFile(name = PREFERENCES_FILE_NAME) },
)
@ -48,7 +52,31 @@ internal object PreferencesDataStore {
)
}
private fun createMigrations(): List<DataMigration<Preferences>> {
return listOf()
private fun createMigrations(context: Context): List<DataMigration<Preferences>> {
return listOf(
SharedPreferencesMigration(
context = context,
sharedPreferencesName = LEGACY_TAP_PREFS_FILE_NAME,
keysToMigrate = getTapPrefKeysToMigrate(),
),
SharedPreferencesKeyMigration(
context = context,
legacyPrefsName = "app_theme",
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
keyName = PreferencesKeys.APP_THEME_MODE_KEY.name,
),
SharedPreferencesKeyMigration(
context = context,
legacyPrefsName = "selected_app_currency",
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
keyName = PreferencesKeys.SELECTED_APP_CURRENCY_KEY.name,
),
SharedPreferencesKeyMigration(
context = context,
legacyPrefsName = "balance_hiding_settings",
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
),
)
}
}

View file

@ -1,13 +1,49 @@
package com.tangem.datasource.local.preferences
import androidx.datastore.preferences.core.*
import com.tangem.datasource.local.preferences.PreferencesKeys.APP_LAUNCH_COUNT_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.FUNDS_FOUND_DATE_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.SAVE_USER_WALLETS_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.USED_CARDS_INFO_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY
/**
* All preferences keys that DataStore<Preferences> is stored.
*
[REDACTED_AUTHOR]
*/
object PreferencesKeys
object PreferencesKeys {
val SAVE_USER_WALLETS_KEY by lazy { booleanPreferencesKey(name = "saveUserWallets") }
val APP_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "launchCount") }
val SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "showRatingDialogAtLaunchCount") }
val FUNDS_FOUND_DATE_KEY by lazy { longPreferencesKey(name = "fundsFoundDate") }
val USER_WAS_INTERACT_WITH_RATING_KEY by lazy { booleanPreferencesKey(name = "userWasInteractWithRating") }
val USED_CARDS_INFO_KEY by lazy { stringPreferencesKey(name = "usedCardsInfo_v2") }
val APP_THEME_MODE_KEY by lazy { stringPreferencesKey(name = "appThemeMode") }
val SELECTED_APP_CURRENCY_KEY by lazy { stringPreferencesKey(name = "selectedAppCurrency") }
val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") }
}
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
internal fun getTapPrefKeysToMigrate(): Set<String> {
return setOf()
return setOf(
SAVE_USER_WALLETS_KEY,
APP_LAUNCH_COUNT_KEY,
SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY,
FUNDS_FOUND_DATE_KEY,
USER_WAS_INTERACT_WITH_RATING_KEY,
USED_CARDS_INFO_KEY,
)
.map(Preferences.Key<*>::name)
.toSet()
}

View file

@ -30,6 +30,14 @@ internal class DefaultQuotesStore(
}
}
override suspend fun getSync(currenciesIds: Set<CryptoCurrency.ID>): Set<StoredQuote> {
return currenciesIds.mapNotNull { currencyId ->
currencyId.rawCurrencyId?.let {
dataStore.getSyncOrNull(it)
}
}.toSet()
}
override suspend fun store(response: QuotesResponse) {
val quotes = response.quotes.mapValues { (id, quote) ->
StoredQuote(id, quote)

View file

@ -9,5 +9,7 @@ interface QuotesStore {
fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>>
suspend fun getSync(currenciesIds: Set<CryptoCurrency.ID>): Set<StoredQuote>
suspend fun store(response: QuotesResponse)
}

View file

@ -1,21 +0,0 @@
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<Int>
suspend fun getSyncOrNull(): Int?
suspend fun store(item: Int)
}
internal class DefaultAppLaunchCountStore(
store: IntSharedPreferencesDataStore,
) : AppLaunchCountStore, KeylessDataStoreDecorator<Int>(
wrappedDataStore = store,
key = "launchCount",
)

View file

@ -1,21 +0,0 @@
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<Int>
suspend fun getSyncOrNull(): Int?
suspend fun store(item: Int)
}
internal class DefaultAppRatingShowingCountStore(
store: IntSharedPreferencesDataStore,
) : AppRatingShowingCountStore, KeylessDataStoreDecorator<Int>(
wrappedDataStore = store,
key = "showRatingDialogAtLaunchCount",
)

View file

@ -1,21 +0,0 @@
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<Long>
suspend fun getSyncOrNull(): Long?
suspend fun store(item: Long)
}
internal class DefaultFundsFoundDateInMillisStore(
store: LongSharedPreferencesDataStore,
) : FundsFoundDateInMillisStore, KeylessDataStoreDecorator<Long>(
wrappedDataStore = store,
key = "fundsFoundDate",
)

View file

@ -1,21 +0,0 @@
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<Boolean>
suspend fun getSyncOrNull(): Boolean?
suspend fun store(item: Boolean)
}
internal class DefaultUserInteractingStatusStore(
store: BooleanSharedPreferencesDataStore,
) : UserInteractingStatusStore, KeylessDataStoreDecorator<Boolean>(
wrappedDataStore = store,
key = "userWasInteractWithRating",
)

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.local.userwallet
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import kotlinx.coroutines.flow.Flow
/**
[REDACTED_AUTHOR]
*/
interface ShouldSaveUserWalletStore {
fun get(): Flow<Boolean>
suspend fun getSyncOrNull(): Boolean?
suspend fun store(item: Boolean)
}
internal class DefaultShouldSaveUserWalletStore(
store: BooleanSharedPreferencesDataStore,
) : ShouldSaveUserWalletStore, KeylessDataStoreDecorator<Boolean>(
wrappedDataStore = store,
key = "saveUserWallets",
)

View file

@ -23,12 +23,15 @@ dependencies {
/** Project - Utils */
implementation(projects.core.utils)
/** AndroidX */
implementation(deps.androidx.datastore)
/** DI */
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)
/** Other */
implementation(deps.jodatime)
implementation(deps.kotlin.coroutines)
implementation(deps.timber)
implementation(deps.jodatime)
}

View file

@ -6,12 +6,16 @@ import com.tangem.data.common.cache.CacheRegistry
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -19,23 +23,25 @@ import org.joda.time.Duration
internal class DefaultAppCurrencyRepository(
private val tangemTechApi: TangemTechApi,
private val appPreferencesStore: AppPreferencesStore,
private val availableAppCurrenciesStore: AvailableAppCurrenciesStore,
private val selectedAppCurrencyStore: SelectedAppCurrencyStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : AppCurrencyRepository {
private val appCurrencyConverter = AppCurrencyConverter()
override fun getSelectedAppCurrency(): Flow<AppCurrency> = channelFlow {
launch(dispatchers.io) {
selectedAppCurrencyStore.get()
.map(appCurrencyConverter::convert)
.collect(::send)
}
override fun getSelectedAppCurrency(): Flow<AppCurrency> {
return channelFlow {
launch {
appPreferencesStore
.getObject<CurrenciesResponse.Currency>(key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY)
.filterNotNull()
.map(appCurrencyConverter::convert)
.collect(::send)
}
withContext(dispatchers.io) {
if (selectedAppCurrencyStore.isEmpty()) {
withContext(dispatchers.io) {
fetchDefaultAppCurrency()
}
}
@ -61,7 +67,10 @@ internal class DefaultAppCurrencyRepository(
"Unable to find app currency with provided code: $currencyCode"
}
selectedAppCurrencyStore.store(currency)
appPreferencesStore.storeObject(
key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
value = currency,
)
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.data.appcurrency.DefaultAppCurrencyRepository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -21,17 +21,17 @@ internal object AppCurrencyDataModule {
@Singleton
fun provideAppCurrencyRepository(
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
availableAppCurrenciesStore: AvailableAppCurrenciesStore,
selectedAppCurrencyStore: SelectedAppCurrencyStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): AppCurrencyRepository {
return DefaultAppCurrencyRepository(
tangemTechApi,
availableAppCurrenciesStore,
selectedAppCurrencyStore,
cacheRegistry,
dispatchers,
tangemTechApi = tangemTechApi,
appPreferencesStore = appPreferencesStore,
availableAppCurrenciesStore = availableAppCurrenciesStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
}
}

View file

@ -27,4 +27,7 @@ dependencies {
/** Other */
implementation(deps.kotlin.coroutines)
/** Local storages */
implementation(deps.androidx.datastore)
}

View file

@ -1,35 +1,25 @@
package com.tangem.data.apptheme
import com.tangem.datasource.local.apptheme.AppThemeModeStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.flow.Flow
internal class DefaultAppThemeModeRepository(
private val appThemeModeStore: AppThemeModeStore,
private val dispatchers: CoroutineDispatcherProvider,
private val appPreferencesStore: AppPreferencesStore,
) : AppThemeModeRepository {
override fun getAppThemeMode(): Flow<AppThemeMode> {
return channelFlow {
launch(dispatchers.io) {
if (appThemeModeStore.isEmpty()) {
appThemeModeStore.store(AppThemeMode.DEFAULT)
}
}
launch(dispatchers.io) {
appThemeModeStore.get().collect(::send)
}
}
return appPreferencesStore.getObject(
key = PreferencesKeys.APP_THEME_MODE_KEY,
default = AppThemeMode.DEFAULT,
)
}
override suspend fun changeAppThemeMode(mode: AppThemeMode) {
withContext(dispatchers.io) {
appThemeModeStore.store(mode)
}
appPreferencesStore.storeObject(key = PreferencesKeys.APP_THEME_MODE_KEY, value = mode)
}
}

View file

@ -1,9 +1,8 @@
package com.tangem.data.apptheme.di
import com.tangem.data.apptheme.DefaultAppThemeModeRepository
import com.tangem.datasource.local.apptheme.AppThemeModeStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -16,10 +15,7 @@ internal object AppThemeModeDataModule {
@Provides
@Singleton
fun provideAppThemeModeRepository(
appThemeModeStore: AppThemeModeStore,
dispatchers: CoroutineDispatcherProvider,
): AppThemeModeRepository {
return DefaultAppThemeModeRepository(appThemeModeStore, dispatchers)
fun provideAppThemeModeRepository(appPreferencesStore: AppPreferencesStore): AppThemeModeRepository {
return DefaultAppThemeModeRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -11,10 +11,10 @@ android {
}
dependencies {
implementation(deps.androidx.datastore)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.kotlin.coroutines)

View file

@ -1,36 +1,41 @@
package com.tangem.data.balancehiding
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrDefault
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
internal class DefaultBalanceHidingRepository(
private val balanceHidingSettingsStore: BalanceHidingSettingsStore,
private val dispatchers: CoroutineDispatcherProvider,
private val appPreferencesStore: AppPreferencesStore,
) : BalanceHidingRepository {
override fun getBalanceHidingSettingsFlow(): Flow<BalanceHidingSettings> {
return balanceHidingSettingsStore.get()
.onStart { emit(getBalanceHidingSettings()) }
.flowOn(dispatchers.io)
.distinctUntilChanged()
return appPreferencesStore.getObject(
key = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY,
default = DEFAULT_HIDING_SETTINGS,
)
}
override suspend fun storeBalanceHidingSettings(balanceHidingSettings: BalanceHidingSettings) {
withContext(dispatchers.io) {
balanceHidingSettingsStore.store(balanceHidingSettings)
}
override suspend fun storeBalanceHidingSettings(isBalanceHidden: BalanceHidingSettings) {
appPreferencesStore.storeObject(key = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY, value = isBalanceHidden)
}
override suspend fun getBalanceHidingSettings(): BalanceHidingSettings {
return withContext(dispatchers.io) {
balanceHidingSettingsStore.getSyncOrDefault()
}
return appPreferencesStore.getObjectSyncOrDefault(
key = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY,
default = DEFAULT_HIDING_SETTINGS,
)
}
private companion object {
val DEFAULT_HIDING_SETTINGS = BalanceHidingSettings(
isHidingEnabledInSettings = false,
isBalanceHidden = false,
isBalanceHidingNotificationEnabled = true,
)
}
}

View file

@ -3,16 +3,15 @@ package com.tangem.data.balancehiding.di
import android.content.Context
import com.tangem.data.balancehiding.DefaultBalanceHidingRepository
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.balancehiding.DeviceFlipDetector
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
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
import dagger.hilt.android.qualifiers.ApplicationContext
@Module
@InstallIn(SingletonComponent::class)
@ -20,14 +19,8 @@ internal object BalanceHidingModule {
@Provides
@Singleton
fun provideBalanceHidingRepository(
balanceHidingSettingsStore: BalanceHidingSettingsStore,
dispatchers: CoroutineDispatcherProvider,
): BalanceHidingRepository {
return DefaultBalanceHidingRepository(
balanceHidingSettingsStore = balanceHidingSettingsStore,
dispatchers = dispatchers,
)
fun provideBalanceHidingRepository(appPreferencesStore: AppPreferencesStore): BalanceHidingRepository {
return DefaultBalanceHidingRepository(appPreferencesStore = appPreferencesStore)
}
@Provides

View file

@ -11,6 +11,8 @@ android {
}
dependencies {
implementation(deps.androidx.datastore)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)

View file

@ -1,43 +1,37 @@
package com.tangem.data.card
import com.tangem.datasource.local.card.UsedCardInfo
import com.tangem.datasource.local.card.UsedCardsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.domain.card.repository.CardRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.flow.map
internal class DefaultCardRepository(
private val usedCardsStore: UsedCardsStore,
private val dispatchers: CoroutineDispatcherProvider,
private val appPreferencesStore: AppPreferencesStore,
) : CardRepository {
override fun wasCardScanned(cardId: String): Flow<Boolean> {
return channelFlow {
launch(dispatchers.io) {
usedCardsStore.get()
.collect { savedCards ->
send(element = savedCards.any { it.cardId == cardId })
}
return appPreferencesStore.getObject<List<UsedCardInfo>>(key = PreferencesKeys.USED_CARDS_INFO_KEY)
.map { savedCards ->
savedCards?.any { it.cardId == cardId } ?: false
}
withContext(dispatchers.io) {
if (usedCardsStore.getSyncOrNull() == null) {
send(element = false)
}
}
}
}
override suspend fun setCardWasScanned(cardId: String) {
withContext(dispatchers.io) {
usedCardsStore.store(
item = usedCardsStore.getSyncOrNull()
?.updateCard(cardId)
?: listOf(UsedCardInfo(cardId = cardId, isScanned = true)),
appPreferencesStore.editData { mutablePreferences ->
val usedCards: List<UsedCardInfo>? = mutablePreferences.getObject(
key = PreferencesKeys.USED_CARDS_INFO_KEY,
)
val updatedUsedCards = usedCards?.updateCard(cardId)
?: listOf(UsedCardInfo(cardId = cardId, isScanned = true))
mutablePreferences.setObject(
key = PreferencesKeys.USED_CARDS_INFO_KEY,
value = updatedUsedCards,
)
}
}

View file

@ -4,10 +4,9 @@ import com.tangem.data.card.DefaultCardRepository
import com.tangem.data.card.DefaultCardSdkConfigRepository
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.local.card.UsedCardsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -32,10 +31,7 @@ internal object CardDataModule {
@Provides
@Singleton
fun provideCardRepository(
usedCardsStore: UsedCardsStore,
dispatchers: CoroutineDispatcherProvider,
): CardRepository {
return DefaultCardRepository(usedCardsStore = usedCardsStore, dispatchers = dispatchers)
fun provideCardRepository(appPreferencesStore: AppPreferencesStore): CardRepository {
return DefaultCardRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -12,18 +12,19 @@ android {
dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.data.source.preferences)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.settings)
implementation(deps.androidx.datastore)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.kotlin.coroutines)
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.domain.settings)
implementation(projects.domain.balanceHiding.models)
implementation(projects.data.source.preferences)
}

View file

@ -1,68 +1,42 @@
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 androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.edit
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.get
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,
private val appPreferencesStore: AppPreferencesStore,
) : 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
appPreferencesStore.edit { mutablePreferences ->
val foundDate = mutablePreferences[PreferencesKeys.FUNDS_FOUND_DATE_KEY]
if (foundDate != null && foundDate != FUNDS_FOUND_DATE_UNDEFINED) return@edit
fundsFoundDateInMillisStore.store(item = Calendar.getInstance().timeInMillis)
mutablePreferences[PreferencesKeys.FUNDS_FOUND_DATE_KEY] = 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)
val appLaunchCount = mutablePreferences[PreferencesKeys.APP_LAUNCH_COUNT_KEY] ?: DEFAULT_APP_LAUNCH_COUNT
mutablePreferences[PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY] =
appLaunchCount + FIRST_SHOWING_COUNT
}
}
override fun isReadyToShow(): Flow<Boolean> {
// TODO: [REDACTED_JIRA]
return combine(
userInteractingStatusStore.get(),
appRatingShowingCountStore.get(),
appLaunchCountStore.get(),
fundsFoundDateInMillisStore.get(),
appPreferencesStore.get(key = PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY, default = false),
appPreferencesStore.get(
key = PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY,
default = FIRST_SHOWING_COUNT,
),
appPreferencesStore.get(key = PreferencesKeys.APP_LAUNCH_COUNT_KEY, default = DEFAULT_APP_LAUNCH_COUNT),
appPreferencesStore.get(key = PreferencesKeys.FUNDS_FOUND_DATE_KEY, default = FUNDS_FOUND_DATE_UNDEFINED),
) { isInteracting, ratingShowingCount, appLaunchCount, fundsFoundDate ->
if (!isInteracting) {
val diff = Calendar.getInstance().timeInMillis - fundsFoundDate
@ -77,20 +51,22 @@ internal class DefaultAppRatingRepository(
}
override suspend fun remindLater() {
appLaunchCountStore.getSyncOrNull()?.let {
updateNextShowing(at = it + DEFER_SHOWING_COUNT)
appPreferencesStore.edit { mutablePreferences ->
mutablePreferences[PreferencesKeys.APP_LAUNCH_COUNT_KEY]?.let {
mutablePreferences.updateNextShowing(it + DEFER_SHOWING_COUNT)
}
}
}
override suspend fun setNeverToShow() {
updateNextShowing(at = Int.MAX_VALUE)
appPreferencesStore.edit { mutablePreferences ->
mutablePreferences.updateNextShowing(at = Int.MAX_VALUE)
}
}
private suspend fun updateNextShowing(at: Int) {
withContext(dispatchers.io) {
appRatingShowingCountStore.store(item = at)
userInteractingStatusStore.store(item = true)
}
private fun MutablePreferences.updateNextShowing(at: Int) {
this[PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY] = at
this[PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY] = true
}
private companion object {

View file

@ -3,10 +3,7 @@ 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.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -34,19 +31,7 @@ internal object SettingsDataModule {
@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,
)
fun provideAppRatingRepository(appPreferencesStore: AppPreferencesStore): AppRatingRepository {
return DefaultAppRatingRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -32,6 +32,9 @@ dependencies {
implementation(deps.tangem.blockchain)
implementation(deps.tangem.card.core)
/** AndroidX */
implementation(deps.androidx.datastore)
/** DI */
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)

View file

@ -7,8 +7,8 @@ import com.tangem.data.tokens.repository.DefaultNetworksRepository
import com.tangem.data.tokens.repository.DefaultQuotesRepository
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.UserTokensStore
@ -55,15 +55,15 @@ internal object TokensDataModule {
@Singleton
fun provideQuotesRepository(
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
quotesStore: QuotesStore,
selectedAppCurrencyStore: SelectedAppCurrencyStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): QuotesRepository {
return DefaultQuotesRepository(
tangemTechApi = tangemTechApi,
appPreferencesStore = appPreferencesStore,
quotesStore = quotesStore,
selectedAppCurrencyStore = selectedAppCurrencyStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)

View file

@ -5,7 +5,12 @@ import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.QuotesConverter
import com.tangem.data.tokens.utils.QuotesUnsupportedCurrenciesIdAdapter
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.*
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
@ -17,8 +22,8 @@ import kotlinx.coroutines.withContext
internal class DefaultQuotesRepository(
private val tangemTechApi: TangemTechApi,
private val appPreferencesStore: AppPreferencesStore,
private val quotesStore: QuotesStore,
private val selectedAppCurrencyStore: SelectedAppCurrencyStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : QuotesRepository {
@ -31,8 +36,11 @@ internal class DefaultQuotesRepository(
@OptIn(ExperimentalCoroutinesApi::class)
override fun getQuotesUpdates(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<Quote>> {
return selectedAppCurrencyStore.get()
return appPreferencesStore.getObject<CurrenciesResponse.Currency>(
key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
)
.distinctUntilChanged()
.filterNotNull()
.flatMapLatest { appCurrency ->
fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = false)
@ -44,13 +52,16 @@ internal class DefaultQuotesRepository(
override suspend fun getQuotesSync(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Set<Quote> {
return withContext(dispatchers.io) {
val selectedAppCurrency = requireNotNull(selectedAppCurrencyStore.getSyncOrNull()) {
"Unable to get selected application currency to update quotes"
}
val selectedAppCurrency = requireNotNull(
value = appPreferencesStore.getObjectSyncOrNull<CurrenciesResponse.Currency>(
key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
),
lazyMessage = { "Unable to get selected application currency to update quotes" },
)
fetchExpiredQuotes(currenciesIds, selectedAppCurrency.id, refresh)
val quotes = quotesStore.get(currenciesIds).first()
val quotes = quotesStore.getSync(currenciesIds)
quotesConverter.convertSet(quotes)
}

View file

@ -13,10 +13,12 @@ android {
dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.data.source.preferences)
implementation(projects.domain.wallets)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
/** Local storages */
implementation(deps.androidx.datastore)
}

View file

@ -1,29 +1,26 @@
package com.tangem.data.wallets
import com.tangem.datasource.local.userwallet.ShouldSaveUserWalletStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
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.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
internal class DefaultWalletsRepository(
private val shouldSaveUserWalletStore: ShouldSaveUserWalletStore,
private val dispatchers: CoroutineDispatcherProvider,
private val appPreferencesStore: AppPreferencesStore,
) : WalletsRepository {
override suspend fun initialize() {
withContext(dispatchers.io) {
shouldSaveUserWalletStore.getSyncOrNull() ?: shouldSaveUserWalletStore.store(item = false)
}
}
override suspend fun shouldSaveUserWalletsSync(): Boolean {
return withContext(dispatchers.io) { shouldSaveUserWalletStore.getSyncOrNull() ?: false }
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
}
override fun shouldSaveUserWallets(): Flow<Boolean> = shouldSaveUserWalletStore.get()
override fun shouldSaveUserWallets(): Flow<Boolean> {
return appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
}
override suspend fun saveShouldSaveUserWallets(item: Boolean) {
withContext(dispatchers.io) { shouldSaveUserWalletStore.store(item = item) }
appPreferencesStore.store(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, value = item)
}
}

View file

@ -1,9 +1,8 @@
package com.tangem.data.wallets.di
import com.tangem.data.wallets.DefaultWalletsRepository
import com.tangem.datasource.local.userwallet.ShouldSaveUserWalletStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -12,17 +11,11 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object WalletsDataModule {
internal object WalletsDataModule {
@Provides
@Singleton
fun providesWalletsRepository(
shouldSaveUserWalletStore: ShouldSaveUserWalletStore,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
): WalletsRepository {
return DefaultWalletsRepository(
shouldSaveUserWalletStore = shouldSaveUserWalletStore,
dispatchers = coroutineDispatcherProvider,
)
fun providesWalletsRepository(appPreferencesStore: AppPreferencesStore): WalletsRepository {
return DefaultWalletsRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -4,9 +4,6 @@ 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<Boolean>

View file

@ -4,24 +4,21 @@ import arrow.core.Either
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetCryptoCurrencyStatusUseCase(
class GetCryptoCurrencyStatusesSyncUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
@ -29,25 +26,7 @@ class GetCryptoCurrencyStatusUseCase(
networksRepository = networksRepository,
)
return operations.getCurrenciesStatusesFlow()
.map { maybeCurrenciesStatuses ->
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
}
}
@Suppress("UnusedPrivateMember")
private fun createTokenList(
userWalletId: UserWalletId,
tokens: List<CryptoCurrencyStatus>,
): Flow<Either<TokenListError, TokenList>> {
val operations = TokenListOperations(
userWalletId = userWalletId,
tokens = tokens,
currenciesRepository = currenciesRepository,
)
return operations.getTokenListFlow().map { maybeTokenList ->
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
}
return operations.getCurrenciesStatusesSync()
.mapLeft { error -> error.mapToTokenListError() }
}
}

View file

@ -67,6 +67,24 @@ internal class CurrenciesStatusesOperations(
}
}
suspend fun getCurrenciesStatusesSync(): Either<Error, List<CryptoCurrencyStatus>> {
return either {
catch(
block = {
val nonEmptyCurrencies =
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
?: return emptyList<CryptoCurrencyStatus>().right()
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
val quotes = quotesRepository.getQuotesSync(currenciesIds, false).right()
val networkStatuses =
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses)
},
catch = { raise(Error.DataError(it)) },
)
}
}
fun getCardCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return flow {
val nonEmptyCurrencies = recover(

View file

@ -4,8 +4,6 @@ import kotlinx.coroutines.flow.Flow
interface WalletsRepository {
suspend fun initialize()
suspend fun shouldSaveUserWalletsSync(): Boolean
fun shouldSaveUserWallets(): Flow<Boolean>

View file

@ -17,5 +17,6 @@ internal sealed class SendRecipientListContent {
data class Wallets(
val list: PersistentList<Item>,
val isWalletsOnly: Boolean,
) : SendRecipientListContent()
}

View file

@ -105,10 +105,15 @@ internal class SendStateFactory(
//endregion
//region recipient
fun onLoadedRecipientList(wallets: List<AvailableWallet?>, txHistory: PagingData<TxHistoryItem>) {
fun onLoadedRecipientList(
wallets: List<AvailableWallet?>,
txHistory: PagingData<TxHistoryItem>,
txHistoryCount: Int,
) {
recipientListStateConverter.convert(
wallets = wallets,
txHistory = txHistory,
txHistoryCount = txHistoryCount,
)
}

View file

@ -22,7 +22,7 @@ internal class SendRecipientListConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) {
fun convert(wallets: List<AvailableWallet?>, txHistory: PagingData<TxHistoryItem>) {
fun convert(wallets: List<AvailableWallet?>, txHistory: PagingData<TxHistoryItem>, txHistoryCount: Int) {
val filteredWallets = wallets.filterNotNull()
.groupBy { item -> item.name }
.values.flatten()
@ -32,49 +32,58 @@ internal class SendRecipientListConverter(
)
}
val walletsItem = getWalletItems(filteredWallets, txHistoryCount)
currentStateProvider().recipientList.update {
txHistory.filter { item ->
val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer
val isNotContract = item.interactionAddressType is TxHistoryItem.InteractionAddressType.User
val isSingleAddress = if (item.isOutgoing) {
item.destinationType is TxHistoryItem.DestinationType.Single
} else {
item.sourceType is TxHistoryItem.SourceType.Single
}
isTransfer && isSingleAddress && isNotContract
}.map<TxHistoryItem, SendRecipientListContent> { tx ->
SendRecipientListContent.Item(
id = tx.txHash,
title = tx.extractAddress(),
subtitle = TextReference.Str(tx.getAmount()),
info = tx.extractTimestamp(),
subtitleIconRes = tx.extractIconRes(),
)
}.insertWallets(filteredWallets)
if (txHistoryCount == 0) {
PagingData.from(listOf(walletsItem))
} else {
txHistory.filter { item ->
val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer
val isNotContract = item.interactionAddressType is TxHistoryItem.InteractionAddressType.User
val isSingleAddress = if (item.isOutgoing) {
item.destinationType is TxHistoryItem.DestinationType.Single
} else {
item.sourceType is TxHistoryItem.SourceType.Single
}
isTransfer && isSingleAddress && isNotContract
}.map<TxHistoryItem, SendRecipientListContent> { tx ->
SendRecipientListContent.Item(
id = tx.txHash,
title = tx.extractAddress(),
subtitle = TextReference.Str(tx.getAmount()),
info = tx.extractTimestamp(),
subtitleIconRes = tx.extractIconRes(),
)
}.insertWallets(walletsItem)
}
}
}
private fun PagingData<SendRecipientListContent>.insertWallets(
wallets: List<AvailableWallet>,
wallets: SendRecipientListContent.Wallets,
): PagingData<SendRecipientListContent> {
return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after ->
return@insertSeparators when {
before == null && after is SendRecipientListContent.Item -> {
SendRecipientListContent.Wallets(
wallets.map {
SendRecipientListContent.Item(
id = it.address,
title = TextReference.Str(it.address),
subtitle = TextReference.Str(it.name),
)
}.toPersistentList(),
)
}
before == null && after is SendRecipientListContent.Item -> wallets
else -> null
}
}
}
private fun getWalletItems(wallets: List<AvailableWallet>, txHistoryCount: Int): SendRecipientListContent.Wallets {
return SendRecipientListContent.Wallets(
wallets.map {
SendRecipientListContent.Item(
id = it.address,
title = TextReference.Str(it.address),
subtitle = TextReference.Str(it.name),
)
}.toPersistentList(),
isWalletsOnly = txHistoryCount == 0,
)
}
private fun TxHistoryItem.extractAddress(): TextReference = if (isOutgoing) {
when (val destination = destinationType) {
is TxHistoryItem.DestinationType.Multiple -> TextReference.Res(

View file

@ -80,6 +80,7 @@ internal fun SendRecipientContent(
}
}
@Suppress("LongMethod")
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.recipientListItem(
recipientList: LazyPagingItems<SendRecipientListContent>,
@ -106,10 +107,17 @@ private fun LazyListScope.recipientListItem(
.padding(top = TangemTheme.dimens.spacing20)
.then(
if (index == 0) {
val bottomRadius = if (item.isWalletsOnly) {
TangemTheme.dimens.radius12
} else {
TangemTheme.dimens.radius0
}
Modifier.clip(
RoundedCornerShape(
topEnd = TangemTheme.dimens.radius12,
topStart = TangemTheme.dimens.radius12,
bottomStart = bottomRadius,
bottomEnd = bottomRadius,
),
)
} else {
@ -182,18 +190,25 @@ private fun RecipientWalletListItem(
onClick = { clickIntents.onRecipientAddressValueChange(title) },
)
}
Text(
text = stringResource(R.string.send_recent_transactions),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.fillMaxWidth()
.padding(
top = TangemTheme.dimens.spacing8,
bottom = TangemTheme.dimens.spacing8,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
),
)
if (!item.isWalletsOnly) {
val topPadding = if (item.list.isNotEmpty()) {
TangemTheme.dimens.spacing8
} else {
TangemTheme.dimens.spacing0
}
Text(
text = stringResource(R.string.send_recent_transactions),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.fillMaxWidth()
.padding(
top = topPadding,
bottom = TangemTheme.dimens.spacing8,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
),
)
}
}
}

View file

@ -20,6 +20,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
@ -57,6 +58,7 @@ internal class SendViewModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val getFeeUseCase: GetFeeUseCase,
private val walletManagersFacade: WalletManagersFacade,
savedStateHandle: SavedStateHandle,
@ -97,7 +99,6 @@ internal class SendViewModel @Inject constructor(
override fun onCreate(owner: LifecycleOwner) {
getWalletAddresses()
subscribeOnCurrencyStatusUpdates(owner)
getWalletsAndRecent()
getFee()
}
@ -134,6 +135,7 @@ internal class SendViewModel @Inject constructor(
.onEach { either ->
either.onRight {
cryptoCurrencyStatus = it
getWalletsAndRecent()
uiState = stateFactory.getReadyState()
}
}
@ -158,10 +160,12 @@ internal class SendViewModel @Inject constructor(
combine(
flow = getUserWallets().conflate(),
flow2 = getTxHistory().conflate(),
) { wallets, txHistory ->
flow3 = getTxHistoryCount().conflate(),
) { wallets, txHistory, txHistoryCount ->
stateFactory.onLoadedRecipientList(
wallets = wallets,
txHistory = txHistory,
txHistoryCount = txHistoryCount,
)
}
.flowOn(dispatchers.io)
@ -213,6 +217,18 @@ internal class SendViewModel @Inject constructor(
}
}
private fun getTxHistoryCount(): Flow<Int> {
return flow {
txHistoryItemsCountUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
).fold(
ifRight = { emit(it) },
ifLeft = { emit(0) },
)
}
}
private fun getFee() {
viewModelScope.launch(dispatchers.main) {
uiState.currentState

View file

@ -294,7 +294,7 @@ internal class SwapRepositoryImpl @Inject constructor(
return ExchangeQuote(
toAmount = response.toAmount,
allowanceContract = response.allowanceContract
allowanceContract = response.allowanceContract,
)
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain
import arrow.core.getOrElse
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
@ -22,7 +22,6 @@ import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.toFiatString
import kotlinx.coroutines.flow.first
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
@ -39,7 +38,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val networksRepository: NetworksRepository,
private val walletFeatureToggles: WalletFeatureToggles,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
) : SwapInteractor {
// TODO: Move to DI
@ -58,10 +57,9 @@ internal class SwapInteractorImpl @Inject constructor(
ifRight = { it },
)
requireNotNull(selectedWallet)
requireNotNull(selectedWallet) { "No selected wallet" }
val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
.first()
.getOrElse { emptyList() }
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses.filter {
@ -112,12 +110,13 @@ internal class SwapInteractorImpl @Inject constructor(
status?.let { CryptoCurrencySwapInfo(it, pair.providers) }
}
val unavailableCryptoCurrencies =
(cryptoCurrenciesList - availableCryptoCurrencies.map { it.currencyStatus }.toSet())
val unavailableCryptoCurrencies = cryptoCurrenciesList - availableCryptoCurrencies
.map { it.currencyStatus }
.toSet()
return CurrenciesGroup(
available = availableCryptoCurrencies,
unavailable = unavailableCryptoCurrencies.map { CryptoCurrencySwapInfo(it, emptyList()) }
unavailable = unavailableCryptoCurrencies.map { CryptoCurrencySwapInfo(it, emptyList()) },
)
}
@ -138,14 +137,10 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private fun Currency.getContractAddress(): String {
return when (this) {
is Currency.NativeToken -> "0"
is Currency.NonNativeToken -> this.contractAddress
}
}
suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List<CryptoCurrency>): List<SwapPairLeast> {
private suspend fun getPairs(
initialCurrency: LeastTokenInfo,
currenciesList: List<CryptoCurrency>,
): List<SwapPairLeast> {
return repository.getPairs(initialCurrency, currenciesList)
}

View file

@ -65,6 +65,7 @@ interface SwapRepository {
amount: BigDecimal?,
): String
@Suppress("LongParameterList")
suspend fun getExchangeQuote(
fromContractAddress: String,
fromNetwork: String,

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.di
import com.tangem.domain.tokens.GetCardTokensListUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
@ -35,7 +35,7 @@ class SwapDomainModule {
networksRepository: NetworksRepository,
walletFeatureToggles: WalletFeatureToggles,
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -81,8 +81,8 @@ class SwapDomainModule {
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyStatusUseCase {
return GetCryptoCurrencyStatusUseCase(
): GetCryptoCurrencyStatusesSyncUseCase {
return GetCryptoCurrencyStatusesSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,

View file

@ -17,7 +17,7 @@ data class SwapPairLeast(
data class CryptoCurrencySwapInfo(
val currencyStatus: CryptoCurrencyStatus,
val providers: List<SwapProvider>
val providers: List<SwapProvider>,
)
/**

View file

@ -17,6 +17,8 @@ dependencies {
implementation(projects.common)
/** Domain modules **/
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
@ -28,6 +30,7 @@ dependencies {
implementation(deps.androidx.browser)
/** Compose */
implementation(deps.arrow.core)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
implementation(deps.compose.ui.tooling)

View file

@ -2,52 +2,108 @@ package com.tangem.feature.swap.converters
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.getTintForTokenIcon
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.ui.FoundTokensStateExpress
import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress
import com.tangem.feature.swap.models.Network
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.TokenBalanceData
import com.tangem.feature.swap.models.TokenToSelectState
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
class TokensDataConverter(
private val onSearchEntered: (String) -> Unit,
private val onTokenSelected: (String) -> Unit,
private val isBalanceHiddenProvider: Provider<Boolean>,
) {
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<CurrenciesGroup, SwapSelectTokenStateHolder> {
fun convertWithNetwork(value: FoundTokensStateExpress, network: NetworkInfo): SwapSelectTokenStateHolder {
override fun convert(value: CurrenciesGroup): SwapSelectTokenStateHolder {
val availableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
val unavailableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
return SwapSelectTokenStateHolder(
availableTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
unavailableTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
this.add(0, availableTitle)
}
.toImmutableList(),
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
.toMutableList()
.apply {
this.add(0, unavailableTitle)
}
.toImmutableList(),
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
network = Network(network.name, network.blockchainId),
)
}
private fun tokenWithBalanceToTokenToSelect(
tokenWithBalance: TokenWithBalanceExpress,
): TokenToSelectState.TokenToSelect {
cryptoCurrencySwapInfo: CryptoCurrencySwapInfo,
isAvailable: Boolean,
): TokenToSelectState {
val cryptoCurrencyStatus = cryptoCurrencySwapInfo.currencyStatus
return TokenToSelectState.TokenToSelect(
id = tokenWithBalance.token.id.value,
name = tokenWithBalance.token.name,
symbol = tokenWithBalance.token.symbol,
isNative = tokenWithBalance.token is CryptoCurrency.Coin,
// todo replace converting
tokenIcon = TokenIconState.CoinIcon(
url = "",
fallbackResId = 0,
isGrayscale = false,
showCustomBadge = false,
),
id = cryptoCurrencyStatus.currency.id.value,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
available = isAvailable,
tokenIcon = convertIcon(cryptoCurrencyStatus.currency, isAvailable),
addedTokenBalanceData = TokenBalanceData(
amount = tokenWithBalance.tokenBalanceData?.amount,
amountEquivalent = tokenWithBalance.tokenBalanceData?.amountEquivalent,
amount = formatCryptoAmount(cryptoCurrencyStatus),
amountEquivalent = formatFiatAmount(cryptoCurrencyStatus, appCurrencyProvider.invoke()),
isBalanceHidden = isBalanceHiddenProvider.invoke(),
),
)
}
private fun convertIcon(currency: CryptoCurrency, isAvailable: Boolean): TokenIconState {
return when (currency) {
is CryptoCurrency.Coin -> {
TokenIconState.CoinIcon(
url = currency.iconUrl,
fallbackResId = currency.networkIconResId,
isGrayscale = !isAvailable,
showCustomBadge = currency.isCustom,
)
}
is CryptoCurrency.Token -> {
val isGrayscale = currency.network.isTestnet
val background = currency.tryGetBackgroundForTokenIcon(isGrayscale)
val tint = getTintForTokenIcon(background)
TokenIconState.TokenIcon(
url = currency.iconUrl,
isGrayscale = !isAvailable,
showCustomBadge = currency.isCustom,
networkBadgeIconResId = currency.networkIconResId,
fallbackTint = tint,
fallbackBackground = background,
)
}
}
}
private fun formatCryptoAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): String {
return BigDecimalFormatter.formatCryptoAmount(
cryptoCurrencyStatus.value.amount,
cryptoCurrencyStatus.currency.symbol,
cryptoCurrencyStatus.currency.decimals,
)
}
private fun formatFiatAmount(cryptoCurrencyStatus: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = cryptoCurrencyStatus.value.fiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
}

View file

@ -1,30 +1,24 @@
package com.tangem.feature.swap.models
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
data class SwapSelectTokenStateHolder(
val availableTokens: ImmutableList<TokenToSelectState>,
val unavailableTokens: ImmutableList<TokenToSelectState>,
val network: Network,
val onSearchEntered: (String) -> Unit,
val onTokenSelected: (String) -> Unit,
)
data class Network(
val name: String,
val blockchainId: String,
)
sealed class TokenToSelectState {
data class Title(val title: String) : TokenToSelectState()
data class Title(val title: TextReference) : TokenToSelectState()
data class TokenToSelect(
val id: String,
val name: String,
val symbol: String,
val isNative: Boolean,
val tokenIcon: TokenIconState,
val available: Boolean = true,
val addedTokenBalanceData: TokenBalanceData? = null,

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.components.states.SelectableItemsState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
@ -24,12 +25,17 @@ import kotlinx.collections.immutable.toImmutableList
* State builder creates a specific states for SwapScreen
*/
@Suppress("LargeClass")
internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: Provider<Boolean>) {
internal class StateBuilder(
private val actions: UiActions,
private val isBalanceHiddenProvider: Provider<Boolean>,
appCurrencyProvider: Provider<AppCurrency>,
) {
private val tokensDataConverter = TokensDataConverter(
onSearchEntered = actions.onSearchEntered,
onTokenSelected = actions.onTokenSelected,
isBalanceHiddenProvider = isBalanceHiddenProvider,
appCurrencyProvider = appCurrencyProvider,
)
fun createInitialLoadingState(initialCurrency: CryptoCurrency, networkInfo: NetworkInfo): SwapStateHolder {
@ -247,15 +253,10 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
)
}
fun addTokensToState(
uiState: SwapStateHolder,
dataState: FoundTokensStateExpress,
networkInfo: NetworkInfo,
): SwapStateHolder {
fun addTokensToState(uiState: SwapStateHolder, tokensDataState: CurrenciesGroup): SwapStateHolder {
return uiState.copy(
selectTokenState = tokensDataConverter.convertWithNetwork(
value = dataState,
network = networkInfo,
selectTokenState = tokensDataConverter.convert(
value = tokensDataState,
),
)
}

View file

@ -11,7 +11,6 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.Strings
@ -20,7 +19,8 @@ import com.tangem.core.ui.components.appbar.ExpandableSearchView
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.presentation.R
@ -43,8 +43,7 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
placeholderSearchText = stringResource(id = R.string.common_search_tokens),
onSearchChange = state.onSearchEntered,
onSearchDisplayClose = { state.onSearchEntered("") },
subtitle = state.network.name,
icon = painterResource(id = getActiveIconRes(state.network.blockchainId)),
subtitle = "", // todo add title
)
},
)
@ -112,7 +111,7 @@ private fun TitleHeader(item: TokenToSelectState.Title, modifier: Modifier = Mod
.background(TangemTheme.colors.background.action),
) {
Text(
text = item.title,
text = item.title.resolveReference().uppercase(),
style = TangemTheme.typography.overline,
modifier = Modifier
.padding(
@ -134,7 +133,10 @@ private fun TokenItem(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size72)
.clickable(onClick = onTokenClick)
.clickable(
enabled = token.available,
onClick = onTokenClick,
)
.padding(
vertical = TangemTheme.dimens.spacing14,
horizontal = TangemTheme.dimens.spacing16,
@ -170,13 +172,7 @@ private fun TokenItem(
Spacer(modifier = Modifier.weight(1f))
if (!token.available) {
Text(
text = stringResource(id = R.string.swapping_token_not_available),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
} else if (token.addedTokenBalanceData != null) {
if (token.addedTokenBalanceData != null) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Center,
@ -191,7 +187,11 @@ private fun TokenItem(
token.addedTokenBalanceData.amountEquivalent.orEmpty()
},
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
color = if (token.available) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.tertiary
},
)
SpacerW2()
Text(
@ -220,7 +220,6 @@ private val token = TokenToSelectState.TokenToSelect(
id = "",
name = "USDC",
symbol = "USDC",
isNative = false,
addedTokenBalanceData = TokenBalanceData(
amount = "15 000 $",
amountEquivalent = "15 000 " +
@ -230,7 +229,7 @@ private val token = TokenToSelectState.TokenToSelect(
)
private val title = TokenToSelectState.Title(
title = "MY TOKENS",
title = stringReference("MY TOKENS"),
)
@Preview
@ -243,7 +242,6 @@ private fun TokenScreenPreview() {
unavailableTokens = listOf(title, token, token, token).toImmutableList(),
onSearchEntered = {},
onTokenSelected = {},
network = Network("Ethereum", "ETH"),
),
onBack = {},
)

View file

@ -4,6 +4,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
import com.tangem.feature.swap.domain.models.ui.SwapStateData
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
import com.tangem.feature.swap.domain.models.ui.TxFee
data class SwapProcessDataState(
@ -20,4 +21,5 @@ data class SwapProcessDataState(
val approveDataModel: RequestApproveStateData? = null,
val swapDataModel: SwapStateData? = null,
val selectedFee: TxFee? = null,
val tokensDataState: TokensDataStateExpress? = null,
)

View file

@ -4,9 +4,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.analytics.SwapEvents
@ -29,11 +32,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.runCatching
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import timber.log.Timber
import java.text.DecimalFormat
import java.text.NumberFormat
@ -49,6 +50,7 @@ internal class SwapViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
@ -57,9 +59,12 @@ internal class SwapViewModel @Inject constructor(
private var isBalanceHidden = true
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val stateBuilder = StateBuilder(
actions = createUiActions(),
isBalanceHiddenProvider = Provider { isBalanceHidden },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
)
private val inputNumberFormatter =
@ -89,7 +94,7 @@ internal class SwapViewModel @Inject constructor(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens(initialCryptoCurrency)
initTokens()
}
override fun onCreate(owner: LifecycleOwner) {
@ -128,7 +133,7 @@ internal class SwapViewModel @Inject constructor(
}
@Suppress("UnusedPrivateMember")
private fun initTokens(currency: CryptoCurrency) {
private fun initTokens() {
// new flow
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
@ -136,16 +141,18 @@ internal class SwapViewModel @Inject constructor(
}.onSuccess { state ->
dataState = dataState.copy(
fromCryptoCurrency = initialCryptoCurrency,
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency,
tokensDataState = state,
)
// updateTokensState(dataState = state.foundTokensState)
//updateTokensState(state)
val toToken = state.toGroup.available.first()
startLoadingQuotes(
fromToken = initialCryptoCurrency,
toToken = toToken.currencyStatus.currency,
toToken = state.toGroup.available.first().currencyStatus.currency,
amount = lastAmount.value,
toProvidersList = toToken.providers
toProvidersList = toToken.providers,
)
}.onFailure {
Timber.tag(loggingTag).e(it)
@ -175,11 +182,11 @@ internal class SwapViewModel @Inject constructor(
// }
}
private fun updateTokensState(dataState: FoundTokensStateExpress) {
private fun updateTokensState(dataState: TokensDataStateExpress) {
val tokensDataState = if (!isOrderReversed) dataState.toGroup else dataState.fromGroup
uiState = stateBuilder.addTokensToState(
uiState = uiState,
dataState = dataState,
networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId),
tokensDataState = tokensDataState,
)
}
@ -381,28 +388,31 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.searchTokens(dataState.networkId, searchQuery)
}
.onSuccess {
updateTokensState(it)
// updateTokensState(it)
}
.onFailure { }
}
}
private fun onTokenSelect(id: String) {
val foundToken = swapInteractor.findTokenById(id)
val tokens = dataState.tokensDataState ?: return
val foundToken = tokens.toGroup.available.firstOrNull {
it.currencyStatus.currency.id.value == id
}
analyticsEventHandler.send(
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.symbol),
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.currencyStatus?.currency?.symbol),
)
if (foundToken != null) {
val fromToken: CryptoCurrency
val toToken: CryptoCurrency
if (isOrderReversed) {
fromToken = foundToken
fromToken = foundToken.currencyStatus.currency
toToken = initialCryptoCurrency
} else {
fromToken = initialCryptoCurrency
toToken = foundToken
toToken = foundToken.currencyStatus.currency
}
dataState = dataState.copy(
fromCryptoCurrency = fromToken,
@ -544,6 +554,18 @@ internal class SwapViewModel @Inject constructor(
)
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
companion object {
private const val loggingTag = "SwapViewModel"
private const val INITIAL_AMOUNT = ""