Updated on 2026-08-14
This commit is contained in:
parent
3c59214c76
commit
4c903fc654
73 changed files with 886 additions and 190 deletions
|
|
@ -22,13 +22,13 @@ configurations {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||||
implementation(project(":domain:legacy"))
|
implementation(projects.domain.legacy)
|
||||||
implementation(project(":domain:models"))
|
implementation(projects.domain.models)
|
||||||
implementation(project(":domain:core"))
|
implementation(projects.domain.core)
|
||||||
implementation(projects.domain.card)
|
implementation(projects.domain.card)
|
||||||
implementation(projects.domain.demo)
|
implementation(projects.domain.demo)
|
||||||
implementation(project(":domain:wallets"))
|
implementation(projects.domain.wallets)
|
||||||
implementation(project(":domain:wallets:models"))
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.settings)
|
implementation(projects.domain.settings)
|
||||||
implementation(projects.domain.tokens)
|
implementation(projects.domain.tokens)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
|
|
@ -37,21 +37,24 @@ dependencies {
|
||||||
implementation(projects.domain.appCurrency.models)
|
implementation(projects.domain.appCurrency.models)
|
||||||
implementation(projects.domain.appTheme)
|
implementation(projects.domain.appTheme)
|
||||||
implementation(projects.domain.appTheme.models)
|
implementation(projects.domain.appTheme.models)
|
||||||
|
implementation(projects.domain.balanceHiding)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
|
||||||
implementation(project(":common"))
|
implementation(projects.common)
|
||||||
implementation(project(":core:analytics"))
|
implementation(projects.core.analytics)
|
||||||
implementation(projects.core.analytics.models)
|
implementation(projects.core.analytics.models)
|
||||||
implementation(project(":core:navigation"))
|
implementation(projects.core.navigation)
|
||||||
implementation(project(":core:featuretoggles"))
|
implementation(projects.core.featuretoggles)
|
||||||
implementation(project(":core:res"))
|
implementation(projects.core.res)
|
||||||
implementation(project(":core:ui"))
|
implementation(projects.core.ui)
|
||||||
implementation(project(":core:datasource"))
|
implementation(projects.core.datasource)
|
||||||
implementation(project(":core:utils"))
|
implementation(projects.core.utils)
|
||||||
implementation(project(":libs:crypto"))
|
implementation(projects.libs.crypto)
|
||||||
implementation(project(":libs:auth"))
|
implementation(projects.libs.auth)
|
||||||
|
|
||||||
implementation(projects.data.appCurrency)
|
implementation(projects.data.appCurrency)
|
||||||
implementation(projects.data.appTheme)
|
implementation(projects.data.appTheme)
|
||||||
|
implementation(projects.data.balanceHiding)
|
||||||
implementation(projects.data.card)
|
implementation(projects.data.card)
|
||||||
implementation(projects.data.common)
|
implementation(projects.data.common)
|
||||||
implementation(projects.data.settings)
|
implementation(projects.data.settings)
|
||||||
|
|
@ -61,20 +64,20 @@ dependencies {
|
||||||
implementation(projects.data.wallets)
|
implementation(projects.data.wallets)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(project(":features:onboarding"))
|
implementation(projects.features.onboarding)
|
||||||
implementation(project(":features:learn2earn:api"))
|
implementation(projects.features.learn2earn.api)
|
||||||
implementation(project(":features:learn2earn:impl"))
|
implementation(projects.features.learn2earn.impl)
|
||||||
implementation(project(":features:referral:presentation"))
|
implementation(projects.features.referral.presentation)
|
||||||
implementation(project(":features:referral:domain"))
|
implementation(projects.features.referral.domain)
|
||||||
implementation(project(":features:referral:data"))
|
implementation(projects.features.referral.data)
|
||||||
implementation(project(":features:swap:api"))
|
implementation(projects.features.swap.api)
|
||||||
implementation(project(":features:swap:presentation"))
|
implementation(projects.features.swap.presentation)
|
||||||
implementation(project(":features:swap:domain"))
|
implementation(projects.features.swap.domain)
|
||||||
implementation(project(":features:swap:data"))
|
implementation(projects.features.swap.data)
|
||||||
implementation(project(":features:tester:api"))
|
implementation(projects.features.tester.api)
|
||||||
implementation(project(":features:tester:impl"))
|
implementation(projects.features.tester.impl)
|
||||||
implementation(project(":features:wallet:api"))
|
implementation(projects.features.wallet.api)
|
||||||
implementation(project(":features:wallet:impl"))
|
implementation(projects.features.wallet.impl)
|
||||||
implementation(projects.features.tokendetails.api)
|
implementation(projects.features.tokendetails.api)
|
||||||
implementation(projects.features.tokendetails.impl)
|
implementation(projects.features.tokendetails.impl)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package com.tangem.tap
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.hardware.Sensor
|
|
||||||
import android.hardware.SensorEvent
|
|
||||||
import android.hardware.SensorEventListener
|
|
||||||
import android.hardware.SensorManager
|
|
||||||
import android.os.SystemClock
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
|
||||||
class DeviceFlipDetector(context: Context) {
|
|
||||||
|
|
||||||
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
|
||||||
private var gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
|
|
||||||
|
|
||||||
private val zAxisThreshold = -6
|
|
||||||
private val throttleTimeMs = 3000
|
|
||||||
private var lastTriggerTime = 0L
|
|
||||||
private var isScreenDown = false
|
|
||||||
|
|
||||||
fun deviceFlipEvents(): Flow<Unit> = callbackFlow {
|
|
||||||
val listener = object : SensorEventListener {
|
|
||||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
|
|
||||||
/* no-op */
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSensorChanged(event: SensorEvent?) {
|
|
||||||
event?.let {
|
|
||||||
val currentTime = SystemClock.elapsedRealtime()
|
|
||||||
val zAxisValue = it.values[2]
|
|
||||||
|
|
||||||
if (zAxisValue < zAxisThreshold && !isScreenDown) {
|
|
||||||
isScreenDown = true
|
|
||||||
lastTriggerTime = currentTime
|
|
||||||
} else if (zAxisValue >= zAxisThreshold) {
|
|
||||||
if (isScreenDown && currentTime - lastTriggerTime <= throttleTimeMs) {
|
|
||||||
lastTriggerTime = currentTime
|
|
||||||
trySend(Unit)
|
|
||||||
}
|
|
||||||
isScreenDown = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gravitySensor?.let {
|
|
||||||
sensorManager.registerListener(listener, it, SensorManager.SENSOR_DELAY_NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
awaitClose { sensorManager.unregisterListener(listener) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.tangem.datasource.config.models.Config
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.common.LogConfig
|
import com.tangem.domain.common.LogConfig
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
|
@ -178,6 +179,9 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appThemeModeRepository: AppThemeModeRepository
|
lateinit var appThemeModeRepository: AppThemeModeRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var balanceHidingRepository: BalanceHidingRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var detailsFeatureToggles: DetailsFeatureToggles
|
lateinit var detailsFeatureToggles: DetailsFeatureToggles
|
||||||
|
|
||||||
|
|
@ -207,6 +211,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
appStateHolder = appStateHolder,
|
appStateHolder = appStateHolder,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
appThemeModeRepository = appThemeModeRepository,
|
appThemeModeRepository = appThemeModeRepository,
|
||||||
|
balanceHidingRepository = balanceHidingRepository,
|
||||||
detailsFeatureToggles = detailsFeatureToggles,
|
detailsFeatureToggles = detailsFeatureToggles,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
import com.tangem.domain.balancehiding.DeviceFlipDetector
|
||||||
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
|
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
|
||||||
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
|
import com.tangem.domain.settings.*
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.tap.domain.TangemSdkManager
|
import com.tangem.tap.domain.TangemSdkManager
|
||||||
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
||||||
|
|
@ -37,4 +39,24 @@ internal object SettingsDomainModule {
|
||||||
legacySettingsRepository = DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager),
|
legacySettingsRepository = DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun providesIsBalanceHiddenUseCase(balanceHidingRepository: BalanceHidingRepository): IsBalanceHiddenUseCase {
|
||||||
|
return IsBalanceHiddenUseCase(
|
||||||
|
balanceHidingRepository = balanceHidingRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun providesListenUseCase(
|
||||||
|
flipDetector: DeviceFlipDetector,
|
||||||
|
balanceHidingRepository: BalanceHidingRepository,
|
||||||
|
): ListenToFlipsUseCase {
|
||||||
|
return ListenToFlipsUseCase(
|
||||||
|
flipDetector = flipDetector,
|
||||||
|
balanceHidingRepository = balanceHidingRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +86,10 @@ sealed class DetailsAction : Action {
|
||||||
val appThemeMode: AppThemeMode,
|
val appThemeMode: AppThemeMode,
|
||||||
) : AppSettings()
|
) : AppSettings()
|
||||||
|
|
||||||
|
data class ChangeBalanceHiding(
|
||||||
|
val hideBalance: Boolean,
|
||||||
|
) : AppSettings()
|
||||||
|
|
||||||
data class ChangeAppCurrency(
|
data class ChangeAppCurrency(
|
||||||
val fiatCurrency: FiatCurrency,
|
val fiatCurrency: FiatCurrency,
|
||||||
) : AppSettings()
|
) : AppSettings()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||||
|
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
@ -207,6 +208,9 @@ class DetailsMiddleware {
|
||||||
is DetailsAction.AppSettings.ChangeAppThemeMode -> {
|
is DetailsAction.AppSettings.ChangeAppThemeMode -> {
|
||||||
changeAppThemeMode(action.appThemeMode)
|
changeAppThemeMode(action.appThemeMode)
|
||||||
}
|
}
|
||||||
|
is DetailsAction.AppSettings.ChangeBalanceHiding -> {
|
||||||
|
changeBalanceHiding(action.hideBalance)
|
||||||
|
}
|
||||||
is DetailsAction.AppSettings.ChangeAppCurrency -> {
|
is DetailsAction.AppSettings.ChangeAppCurrency -> {
|
||||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
|
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
|
||||||
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
|
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
|
||||||
|
|
@ -257,6 +261,19 @@ class DetailsMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun changeBalanceHiding(hideBalance: Boolean) {
|
||||||
|
val repository = store.state.daggerGraphState.get(DaggerGraphState::balanceHidingRepository)
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
val newState = BalanceHidingSettings(
|
||||||
|
isHidingEnabledInSettings = hideBalance,
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
repository.storeBalanceHidingSettings(newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
|
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
|
||||||
// Nothing to change
|
// Nothing to change
|
||||||
if (preferencesStorage.shouldSaveUserWallets == enable) {
|
if (preferencesStorage.shouldSaveUserWallets == enable) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.tap.domain.extensions.signedHashesCount
|
||||||
import com.tangem.tap.preferencesStorage
|
import com.tangem.tap.preferencesStorage
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
|
||||||
|
|
@ -73,6 +74,10 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
||||||
saveAccessCodes = preferencesStorage.shouldSaveAccessCodes,
|
saveAccessCodes = preferencesStorage.shouldSaveAccessCodes,
|
||||||
selectedFiatCurrency = store.state.globalState.appCurrency,
|
selectedFiatCurrency = store.state.globalState.appCurrency,
|
||||||
selectedThemeMode = store.state.globalState.appThemeMode,
|
selectedThemeMode = store.state.globalState.appThemeMode,
|
||||||
|
isHidingEnabled = runBlocking {
|
||||||
|
store.state.daggerGraphState
|
||||||
|
.get { balanceHidingRepository }.getBalanceHidingSettings().isHidingEnabledInSettings
|
||||||
|
},
|
||||||
darkThemeSwitchEnabled = action.darkThemeSwitchEnabled,
|
darkThemeSwitchEnabled = action.darkThemeSwitchEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -222,6 +227,11 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
||||||
selectedFiatCurrency = action.fiatCurrency,
|
selectedFiatCurrency = action.fiatCurrency,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
is DetailsAction.AppSettings.ChangeBalanceHiding -> state.copy(
|
||||||
|
appSettingsState = state.appSettingsState.copy(
|
||||||
|
isHidingEnabled = action.hideBalance,
|
||||||
|
),
|
||||||
|
)
|
||||||
is DetailsAction.AppSettings.EnrollBiometrics,
|
is DetailsAction.AppSettings.EnrollBiometrics,
|
||||||
is DetailsAction.AppSettings.CheckBiometricsStatus,
|
is DetailsAction.AppSettings.CheckBiometricsStatus,
|
||||||
-> state
|
-> state
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ data class AppSettingsState(
|
||||||
val saveAccessCodes: Boolean = false,
|
val saveAccessCodes: Boolean = false,
|
||||||
val isBiometricsAvailable: Boolean = false,
|
val isBiometricsAvailable: Boolean = false,
|
||||||
val needEnrollBiometrics: Boolean = false,
|
val needEnrollBiometrics: Boolean = false,
|
||||||
|
val isHidingEnabled: Boolean = false,
|
||||||
val isInProgress: Boolean = false,
|
val isInProgress: Boolean = false,
|
||||||
val selectedFiatCurrency: FiatCurrency = FiatCurrency.Default,
|
val selectedFiatCurrency: FiatCurrency = FiatCurrency.Default,
|
||||||
val selectedThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
val selectedThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,21 @@ internal class AppSettingsItemsFactory {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFlipToHideBalanceSwitch(
|
||||||
|
isChecked: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
): Item.Switch {
|
||||||
|
return Item.Switch(
|
||||||
|
id = "flip_to_hide_balance_switch",
|
||||||
|
title = resourceReference(R.string.details_row_title_flip_to_hide),
|
||||||
|
description = resourceReference(R.string.details_row_description_flip_to_hide),
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
isChecked = isChecked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createSelectAppCurrencyButton(currentAppCurrencyName: String, onClick: () -> Unit): Item.Button {
|
fun createSelectAppCurrencyButton(currentAppCurrencyName: String, onClick: () -> Unit): Item.Button {
|
||||||
return Item.Button(
|
return Item.Button(
|
||||||
id = "select_app_currency_button",
|
id = "select_app_currency_button",
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide
|
||||||
itemsFactory.createSelectAppCurrencyButton(currentAppCurrencyName = "US Dollar") {},
|
itemsFactory.createSelectAppCurrencyButton(currentAppCurrencyName = "US Dollar") {},
|
||||||
itemsFactory.createSaveWalletsSwitch(isChecked = true, isEnabled = true, { _ -> }),
|
itemsFactory.createSaveWalletsSwitch(isChecked = true, isEnabled = true, { _ -> }),
|
||||||
itemsFactory.createSaveAccessCodeSwitch(isChecked = false, isEnabled = true) { _ -> },
|
itemsFactory.createSaveAccessCodeSwitch(isChecked = false, isEnabled = true) { _ -> },
|
||||||
|
itemsFactory.createFlipToHideBalanceSwitch(isChecked = false, isEnabled = true) { _ -> },
|
||||||
itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}),
|
itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,16 @@ internal class AppSettingsViewModel(
|
||||||
).let(::add)
|
).let(::add)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemsFactory.createFlipToHideBalanceSwitch(
|
||||||
|
isChecked = state.isHidingEnabled,
|
||||||
|
isEnabled = true,
|
||||||
|
onCheckedChange = ::onFlipToHideBalanceToggled,
|
||||||
|
).let(::add)
|
||||||
|
|
||||||
|
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
||||||
|
showThemeModeSelector(state.selectedThemeMode)
|
||||||
|
}.let(::add)
|
||||||
|
|
||||||
if (state.darkThemeSwitchEnabled) {
|
if (state.darkThemeSwitchEnabled) {
|
||||||
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
||||||
showThemeModeSelector(state.selectedThemeMode)
|
showThemeModeSelector(state.selectedThemeMode)
|
||||||
|
|
@ -178,6 +188,10 @@ internal class AppSettingsViewModel(
|
||||||
store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
|
store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onFlipToHideBalanceToggled(enable: Boolean) {
|
||||||
|
store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(hideBalance = enable))
|
||||||
|
}
|
||||||
|
|
||||||
private fun dismissDialog() {
|
private fun dismissDialog() {
|
||||||
updateContentState { copy(dialog = null) }
|
updateContentState { copy(dialog = null) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ sealed class AmountAction : SendScreenAction {
|
||||||
data class SetAmount(val amountCrypto: BigDecimal, val isUserInput: Boolean) : AmountAction()
|
data class SetAmount(val amountCrypto: BigDecimal, val isUserInput: Boolean) : AmountAction()
|
||||||
data class SetAmountError(val error: TapError?) : AmountAction()
|
data class SetAmountError(val error: TapError?) : AmountAction()
|
||||||
data class SetDecimalSeparator(val separator: String) : AmountAction()
|
data class SetDecimalSeparator(val separator: String) : AmountAction()
|
||||||
|
data class HideBalance(val hide: Boolean) : AmountAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fee
|
// Fee
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.features.send.redux.reducers
|
package com.tangem.tap.features.send.redux.reducers
|
||||||
|
|
||||||
|
import com.tangem.common.Strings.STARS
|
||||||
import com.tangem.common.extensions.isZero
|
import com.tangem.common.extensions.isZero
|
||||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||||
import com.tangem.tap.features.send.redux.AmountAction
|
import com.tangem.tap.features.send.redux.AmountAction
|
||||||
|
|
@ -38,8 +39,8 @@ class AmountReducer : SendInternalReducer {
|
||||||
val viewValue = state.restoreDecimalSeparator(fiatToSend.stripZeroPlainString())
|
val viewValue = state.restoreDecimalSeparator(fiatToSend.stripZeroPlainString())
|
||||||
state.copy(
|
state.copy(
|
||||||
viewAmountValue = InputViewValue(viewValue),
|
viewAmountValue = InputViewValue(viewValue),
|
||||||
viewBalanceValue = rescaledBalance.stripZeroPlainString(),
|
viewBalanceValue = if (state.hideBalance) STARS else rescaledBalance.stripZeroPlainString(),
|
||||||
mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched),
|
mainCurrency = state.createMainCurrency(currency, true),
|
||||||
maxLengthOfAmount = sendState.getDecimals(currency),
|
maxLengthOfAmount = sendState.getDecimals(currency),
|
||||||
cursorAtTheSamePosition = false,
|
cursorAtTheSamePosition = false,
|
||||||
)
|
)
|
||||||
|
|
@ -80,6 +81,14 @@ class AmountReducer : SendInternalReducer {
|
||||||
}
|
}
|
||||||
is AmountAction.SetAmountError -> state.copy(error = action.error)
|
is AmountAction.SetAmountError -> state.copy(error = action.error)
|
||||||
is AmountAction.SetDecimalSeparator -> state.copy(decimalSeparator = action.separator)
|
is AmountAction.SetDecimalSeparator -> state.copy(decimalSeparator = action.separator)
|
||||||
|
is AmountAction.HideBalance -> {
|
||||||
|
val rescaledBalance = sendState.convertExtractCryptoToFiat(state.balanceCrypto, true)
|
||||||
|
|
||||||
|
state.copy(
|
||||||
|
hideBalance = action.hide,
|
||||||
|
viewBalanceValue = if (action.hide) STARS else rescaledBalance.stripZeroPlainString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return updateLastState(sendState.copy(amountState = result), result)
|
return updateLastState(sendState.copy(amountState = result), result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ data class AmountState(
|
||||||
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, FiatCurrency.Default.code),
|
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, FiatCurrency.Default.code),
|
||||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||||
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
||||||
|
val hideBalance: Boolean = false,
|
||||||
val cursorAtTheSamePosition: Boolean = true,
|
val cursorAtTheSamePosition: Boolean = true,
|
||||||
val maxLengthOfAmount: Int = 2,
|
val maxLengthOfAmount: Int = 2,
|
||||||
val decimalSeparator: String = ".",
|
val decimalSeparator: String = ".",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.core.view.postDelayed
|
import androidx.core.view.postDelayed
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
|
|
@ -45,6 +46,7 @@ import com.tangem.tap.mainScope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import com.tangem.wallet.databinding.FragmentSendBinding
|
import com.tangem.wallet.databinding.FragmentSendBinding
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
@ -56,8 +58,11 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
|
@AndroidEntryPoint
|
||||||
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
|
|
||||||
|
private val viewModel by viewModels<SendViewModel>()
|
||||||
|
|
||||||
lateinit var sendBtn: ViewStateWidget
|
lateinit var sendBtn: ViewStateWidget
|
||||||
|
|
||||||
private lateinit var etAmountToSend: TextInputEditText
|
private lateinit var etAmountToSend: TextInputEditText
|
||||||
|
|
@ -70,6 +75,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
lifecycle.addObserver(viewModel)
|
||||||
Analytics.send(Token.Send.ScreenOpened())
|
Analytics.send(Token.Send.ScreenOpened())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,6 +354,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
store.dispatch(ReleaseSendState)
|
store.dispatch(ReleaseSendState)
|
||||||
|
lifecycle.removeObserver(viewModel)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.tangem.tap.features.send.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.*
|
||||||
|
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
|
||||||
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
|
import com.tangem.tap.features.send.redux.AmountAction
|
||||||
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
internal class SendViewModel @Inject constructor(
|
||||||
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val appStateHolder: AppStateHolder,
|
||||||
|
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
|
||||||
|
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||||
|
) : ViewModel(), DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
isBalanceHiddenUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.onEach { isBalanceHidden ->
|
||||||
|
withContext(dispatchers.main) {
|
||||||
|
appStateHolder.mainStore?.dispatch(AmountAction.HideBalance(isBalanceHidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
listenToFlipsUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.card.ScanCardUseCase
|
import com.tangem.domain.card.ScanCardUseCase
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
|
|
@ -41,6 +42,7 @@ data class DaggerGraphState(
|
||||||
val walletManagersFacade: WalletManagersFacade? = null,
|
val walletManagersFacade: WalletManagersFacade? = null,
|
||||||
val appStateHolder: AppStateHolder? = null,
|
val appStateHolder: AppStateHolder? = null,
|
||||||
val appThemeModeRepository: AppThemeModeRepository? = null,
|
val appThemeModeRepository: AppThemeModeRepository? = null,
|
||||||
|
val balanceHidingRepository: BalanceHidingRepository? = null,
|
||||||
val detailsFeatureToggles: DetailsFeatureToggles? = null,
|
val detailsFeatureToggles: DetailsFeatureToggles? = null,
|
||||||
|
|
||||||
// FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList
|
// FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList
|
||||||
|
|
|
||||||
6
common/src/main/java/com/tangem/common/Strings.kt
Normal file
6
common/src/main/java/com/tangem/common/Strings.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.common
|
||||||
|
|
||||||
|
object Strings {
|
||||||
|
|
||||||
|
const val STARS = "***"
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ dependencies {
|
||||||
implementation(projects.domain.appTheme.models)
|
implementation(projects.domain.appTheme.models)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
|
||||||
/** Tangem libraries */
|
/** Tangem libraries */
|
||||||
implementation(deps.tangem.blockchain)
|
implementation(deps.tangem.blockchain)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
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.SharedPreferencesDataStore
|
||||||
|
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 = SharedPreferencesDataStore(
|
||||||
|
preferencesName = "balance_hiding_settings",
|
||||||
|
context = context,
|
||||||
|
adapter = moshi.adapter(BalanceHidingSettings::class.java),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -159,6 +159,8 @@
|
||||||
<string name="details_row_title_cid">Card ID</string>
|
<string name="details_row_title_cid">Card ID</string>
|
||||||
<string name="details_row_title_create_backup">Link More Cards</string>
|
<string name="details_row_title_create_backup">Link More Cards</string>
|
||||||
<string name="details_row_title_currency">App Currency</string>
|
<string name="details_row_title_currency">App Currency</string>
|
||||||
|
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
|
||||||
|
<string name="details_row_description_flip_to_hide">Flip your device screen down to quickly hide and show balances</string>
|
||||||
<string name="details_row_title_issuer">Issuer</string>
|
<string name="details_row_title_issuer">Issuer</string>
|
||||||
<string name="details_row_title_send_feedback">Send Feedback</string>
|
<string name="details_row_title_send_feedback">Send Feedback</string>
|
||||||
<string name="details_row_title_signed_hashes">Signed</string>
|
<string name="details_row_title_signed_hashes">Signed</string>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:pathData="M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27ZM12,9C12.796,9 13.559,9.316 14.121,9.879C14.684,10.441 15,11.204 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9ZM12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5ZM3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z"
|
|
||||||
android:fillColor="#000000"/>
|
|
||||||
</vector>
|
|
||||||
1
data/balance-hiding/.gitignore
vendored
Normal file
1
data/balance-hiding/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
27
data/balance-hiding/build.gradle.kts
Normal file
27
data/balance-hiding/build.gradle.kts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
alias(deps.plugins.kotlin.kapt)
|
||||||
|
alias(deps.plugins.hilt.android)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.data.balancehiding"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
/** DI */
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
|
||||||
|
implementation(projects.core.utils)
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
|
implementation(projects.domain.balanceHiding)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.tangem.data.balancehiding
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
|
||||||
|
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,
|
||||||
|
) : BalanceHidingRepository {
|
||||||
|
|
||||||
|
override fun getBalanceHidingSettingsFlow(): Flow<BalanceHidingSettings> {
|
||||||
|
return balanceHidingSettingsStore.get()
|
||||||
|
.onStart { emit(getBalanceHidingSettings()) }
|
||||||
|
.flowOn(dispatchers.io)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeBalanceHidingSettings(balanceHidingSettings: BalanceHidingSettings) {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
balanceHidingSettingsStore.store(balanceHidingSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getBalanceHidingSettings(): BalanceHidingSettings {
|
||||||
|
return withContext(dispatchers.io) {
|
||||||
|
balanceHidingSettingsStore.getSyncOrDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.tangem.data.balancehiding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.hardware.Sensor
|
||||||
|
import android.hardware.SensorManager
|
||||||
|
import com.tangem.domain.balancehiding.DeviceFlipDetector
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
|
||||||
|
internal class DefaultDeviceFlipDetector(context: Context) : DeviceFlipDetector {
|
||||||
|
|
||||||
|
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||||
|
private var gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
|
||||||
|
|
||||||
|
override fun getDeviceFlipFlow(): Flow<Unit> = callbackFlow {
|
||||||
|
val listener = FlipListener { trySend(Unit) }
|
||||||
|
|
||||||
|
gravitySensor?.let {
|
||||||
|
sensorManager.registerListener(listener, it, SensorManager.SENSOR_DELAY_NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitClose { sensorManager.unregisterListener(listener) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.tangem.data.balancehiding
|
||||||
|
|
||||||
|
import android.hardware.Sensor
|
||||||
|
import android.hardware.SensorEvent
|
||||||
|
import android.hardware.SensorEventListener
|
||||||
|
import android.os.SystemClock
|
||||||
|
|
||||||
|
internal class FlipListener(private val action: () -> Unit) : SensorEventListener {
|
||||||
|
|
||||||
|
private val zAxisThreshold = -6
|
||||||
|
private val throttleTimeMs = 3000
|
||||||
|
private var lastTriggerTime = 0L
|
||||||
|
private var isScreenDown = false
|
||||||
|
|
||||||
|
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSensorChanged(event: SensorEvent?) {
|
||||||
|
event?.let {
|
||||||
|
val currentTime = SystemClock.elapsedRealtime()
|
||||||
|
val zAxisValue = it.values[2]
|
||||||
|
|
||||||
|
if (zAxisValue < zAxisThreshold && !isScreenDown) {
|
||||||
|
isScreenDown = true
|
||||||
|
lastTriggerTime = currentTime
|
||||||
|
// TODO add module logging
|
||||||
|
// Timber.tag("onSensorChanged").d("screen down")
|
||||||
|
} else if (zAxisValue >= zAxisThreshold) {
|
||||||
|
if (isScreenDown && currentTime - lastTriggerTime <= throttleTimeMs) {
|
||||||
|
// Timber.tag("onSensorChanged").d("screen up!")
|
||||||
|
lastTriggerTime = currentTime
|
||||||
|
action.invoke()
|
||||||
|
}
|
||||||
|
isScreenDown = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.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.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object BalanceHidingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideBalanceHidingRepository(
|
||||||
|
balanceHidingSettingsStore: BalanceHidingSettingsStore,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): BalanceHidingRepository {
|
||||||
|
return DefaultBalanceHidingRepository(
|
||||||
|
balanceHidingSettingsStore = balanceHidingSettingsStore,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFlipDetector(@ApplicationContext context: Context): DeviceFlipDetector {
|
||||||
|
return DefaultDeviceFlipDetector(context = context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,13 +14,16 @@ dependencies {
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
|
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
implementation(deps.kotlin.coroutines)
|
implementation(deps.kotlin.coroutines)
|
||||||
|
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
implementation(projects.domain.settings)
|
implementation(projects.domain.settings)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
|
||||||
implementation(projects.data.source.preferences)
|
implementation(projects.data.source.preferences)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ internal object SettingsDataModule {
|
||||||
preferencesDataSource: PreferencesDataSource,
|
preferencesDataSource: PreferencesDataSource,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): SettingsRepository {
|
): SettingsRepository {
|
||||||
return DefaultSettingsRepository(preferencesDataSource = preferencesDataSource, dispatchers = dispatchers)
|
return DefaultSettingsRepository(
|
||||||
|
preferencesDataSource = preferencesDataSource,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
domain/balance-hiding/.gitignore
vendored
Normal file
1
domain/balance-hiding/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
10
domain/balance-hiding/build.gradle.kts
Normal file
10
domain/balance-hiding/build.gradle.kts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.kotlin.jvm)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(projects.domain.settings)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
}
|
||||||
1
domain/balance-hiding/models/.gitignore
vendored
Normal file
1
domain/balance-hiding/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
4
domain/balance-hiding/models/build.gradle.kts
Normal file
4
domain/balance-hiding/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.kotlin.jvm)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.domain.balancehiding
|
||||||
|
|
||||||
|
data class BalanceHidingSettings(
|
||||||
|
val isHidingEnabledInSettings: Boolean,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.domain.balancehiding
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface DeviceFlipDetector {
|
||||||
|
|
||||||
|
fun getDeviceFlipFlow(): Flow<Unit>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.domain.balancehiding
|
||||||
|
|
||||||
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class IsBalanceHiddenUseCase(
|
||||||
|
private val balanceHidingRepository: BalanceHidingRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(): Flow<Boolean> {
|
||||||
|
return balanceHidingRepository.getBalanceHidingSettingsFlow().map {
|
||||||
|
it.isBalanceHidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tangem.domain.balancehiding
|
||||||
|
|
||||||
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
class ListenToFlipsUseCase(
|
||||||
|
private val flipDetector: DeviceFlipDetector,
|
||||||
|
private val balanceHidingRepository: BalanceHidingRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): Flow<Unit> {
|
||||||
|
return if (balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings) {
|
||||||
|
flipDetector.getDeviceFlipFlow().onEach {
|
||||||
|
val balanceHidingSettings = balanceHidingRepository.getBalanceHidingSettings()
|
||||||
|
|
||||||
|
balanceHidingRepository.storeBalanceHidingSettings(
|
||||||
|
balanceHidingSettings.copy(
|
||||||
|
isBalanceHidden = !balanceHidingSettings.isBalanceHidden,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyFlow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.domain.balancehiding.repositories
|
||||||
|
|
||||||
|
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface BalanceHidingRepository {
|
||||||
|
|
||||||
|
fun getBalanceHidingSettingsFlow(): Flow<BalanceHidingSettings>
|
||||||
|
|
||||||
|
suspend fun storeBalanceHidingSettings(isBalanceHidden: BalanceHidingSettings)
|
||||||
|
|
||||||
|
suspend fun getBalanceHidingSettings(): BalanceHidingSettings
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.kotlin.jvm)
|
alias(deps.plugins.kotlin.jvm)
|
||||||
id("configuration")
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(projects.domain.balanceHiding.models)
|
||||||
}
|
}
|
||||||
|
|
@ -9,11 +9,15 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
/** Core modules */
|
/** Core modules */
|
||||||
implementation(project(":core:analytics"))
|
implementation(projects.core.analytics)
|
||||||
implementation(projects.core.analytics.models)
|
implementation(projects.core.analytics.models)
|
||||||
implementation(project(":core:featuretoggles"))
|
implementation(projects.core.featuretoggles)
|
||||||
implementation(project(":core:utils"))
|
implementation(projects.core.utils)
|
||||||
implementation(project(":core:ui"))
|
implementation(projects.core.ui)
|
||||||
|
implementation(projects.common)
|
||||||
|
|
||||||
|
/** Domain modules **/
|
||||||
|
implementation(projects.domain.balanceHiding)
|
||||||
|
|
||||||
/** AndroidX */
|
/** AndroidX */
|
||||||
implementation(deps.androidx.activity.compose)
|
implementation(deps.androidx.activity.compose)
|
||||||
|
|
@ -30,10 +34,11 @@ dependencies {
|
||||||
implementation(deps.compose.constraintLayout)
|
implementation(deps.compose.constraintLayout)
|
||||||
|
|
||||||
/** Api */
|
/** Api */
|
||||||
implementation(project(":features:swap:api"))
|
implementation(projects.features.swap.api)
|
||||||
|
|
||||||
/** Domain */
|
/** Domain */
|
||||||
implementation(project(":features:swap:domain"))
|
implementation(projects.features.swap.domain)
|
||||||
|
implementation(projects.domain.settings)
|
||||||
|
|
||||||
/** Other libraries */
|
/** Other libraries */
|
||||||
implementation(deps.compose.shimmer)
|
implementation(deps.compose.shimmer)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ data class SwapCardData(
|
||||||
val tokenIconUrl: String,
|
val tokenIconUrl: String,
|
||||||
val tokenCurrency: String,
|
val tokenCurrency: String,
|
||||||
val balance: String,
|
val balance: String,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
val isNotNativeToken: Boolean,
|
val isNotNativeToken: Boolean,
|
||||||
val canSelectAnotherToken: Boolean = false,
|
val canSelectAnotherToken: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ class SwapFragment : Fragment() {
|
||||||
|
|
||||||
private val viewModel by viewModels<SwapViewModel>()
|
private val viewModel by viewModels<SwapViewModel>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
lifecycle.addObserver(viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
viewModel.setRouter(
|
viewModel.setRouter(
|
||||||
SwapRouter(
|
SwapRouter(
|
||||||
|
|
@ -70,6 +75,11 @@ class SwapFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
lifecycle.removeObserver(viewModel)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CURRENCY_BUNDLE_KEY = "swap_currency"
|
const val CURRENCY_BUNDLE_KEY = "swap_currency"
|
||||||
const val DERIVATION_PATH = "DERIVATION_STYLE"
|
const val DERIVATION_PATH = "DERIVATION_STYLE"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.swap.ui
|
||||||
|
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import com.tangem.common.Provider
|
||||||
import com.tangem.core.ui.components.states.Item
|
import com.tangem.core.ui.components.states.Item
|
||||||
import com.tangem.core.ui.components.states.SelectableItemsState
|
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
@ -21,7 +22,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
* State builder creates a specific states for SwapScreen
|
* State builder creates a specific states for SwapScreen
|
||||||
*/
|
*/
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
internal class StateBuilder(val actions: UiActions) {
|
internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: Provider<Boolean>) {
|
||||||
|
|
||||||
private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected)
|
private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected)
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
canSelectAnotherToken = false,
|
canSelectAnotherToken = false,
|
||||||
isNotNativeToken = initialCurrency.isNonNative(),
|
isNotNativeToken = initialCurrency.isNonNative(),
|
||||||
balance = "",
|
balance = "",
|
||||||
|
isBalanceHidden = true,
|
||||||
),
|
),
|
||||||
receiveCardData = SwapCardData(
|
receiveCardData = SwapCardData(
|
||||||
type = TransactionCardType.ReceiveCard(),
|
type = TransactionCardType.ReceiveCard(),
|
||||||
|
|
@ -50,6 +52,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
balance = "",
|
balance = "",
|
||||||
isNotNativeToken = false,
|
isNotNativeToken = false,
|
||||||
coinId = null,
|
coinId = null,
|
||||||
|
isBalanceHidden = true,
|
||||||
),
|
),
|
||||||
fee = FeeState.Loading,
|
fee = FeeState.Loading,
|
||||||
networkCurrency = networkInfo.blockchainCurrency,
|
networkCurrency = networkInfo.blockchainCurrency,
|
||||||
|
|
@ -84,6 +87,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
isNotNativeToken = fromToken.isNonNative(),
|
isNotNativeToken = fromToken.isNonNative(),
|
||||||
canSelectAnotherToken = canSelectSendToken,
|
canSelectAnotherToken = canSelectSendToken,
|
||||||
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
|
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
receiveCardData = SwapCardData(
|
receiveCardData = SwapCardData(
|
||||||
type = TransactionCardType.ReceiveCard(),
|
type = TransactionCardType.ReceiveCard(),
|
||||||
|
|
@ -95,6 +99,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
isNotNativeToken = toToken.isNonNative(),
|
isNotNativeToken = toToken.isNonNative(),
|
||||||
canSelectAnotherToken = canSelectReceiveToken,
|
canSelectAnotherToken = canSelectReceiveToken,
|
||||||
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
|
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
fee = FeeState.Loading,
|
fee = FeeState.Loading,
|
||||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||||
|
|
@ -144,6 +149,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||||
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
|
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
receiveCardData = SwapCardData(
|
receiveCardData = SwapCardData(
|
||||||
type = TransactionCardType.ReceiveCard(),
|
type = TransactionCardType.ReceiveCard(),
|
||||||
|
|
@ -155,6 +161,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||||
balance = quoteModel.toTokenInfo.tokenWalletBalance,
|
balance = quoteModel.toTokenInfo.tokenWalletBalance,
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
networkCurrency = quoteModel.networkCurrency,
|
networkCurrency = quoteModel.networkCurrency,
|
||||||
warnings = warnings,
|
warnings = warnings,
|
||||||
|
|
@ -192,6 +199,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||||
balance = emptyAmountState.fromTokenWalletBalance,
|
balance = emptyAmountState.fromTokenWalletBalance,
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
receiveCardData = SwapCardData(
|
receiveCardData = SwapCardData(
|
||||||
type = TransactionCardType.ReceiveCard(),
|
type = TransactionCardType.ReceiveCard(),
|
||||||
|
|
@ -203,6 +211,7 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||||
balance = emptyAmountState.toTokenWalletBalance,
|
balance = emptyAmountState.toTokenWalletBalance,
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
warnings = emptyList(),
|
warnings = emptyList(),
|
||||||
fee = FeeState.Empty,
|
fee = FeeState.Empty,
|
||||||
|
|
@ -254,6 +263,19 @@ internal class StateBuilder(val actions: UiActions) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder {
|
||||||
|
val patchedSendCardData = uiState.sendCardData.copy(
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
)
|
||||||
|
val patchedReceiveCardData = uiState.receiveCardData.copy(
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
)
|
||||||
|
return uiState.copy(
|
||||||
|
sendCardData = patchedSendCardData,
|
||||||
|
receiveCardData = patchedReceiveCardData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun updateApproveType(uiState: SwapStateHolder, approveType: ApproveType): SwapStateHolder {
|
fun updateApproveType(uiState: SwapStateHolder, approveType: ApproveType): SwapStateHolder {
|
||||||
return if (uiState.permissionState is SwapPermissionState.ReadyForRequest) {
|
return if (uiState.permissionState is SwapPermissionState.ReadyForRequest) {
|
||||||
uiState.copy(
|
uiState.copy(
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
|
import com.tangem.common.Strings.STARS
|
||||||
import com.tangem.core.ui.components.*
|
import com.tangem.core.ui.components.*
|
||||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||||
import com.tangem.core.ui.components.states.Item
|
import com.tangem.core.ui.components.states.Item
|
||||||
|
|
@ -143,7 +144,11 @@ private fun MainInfo(state: SwapStateHolder) {
|
||||||
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
|
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
|
||||||
TransactionCard(
|
TransactionCard(
|
||||||
type = state.sendCardData.type,
|
type = state.sendCardData.type,
|
||||||
balance = state.sendCardData.balance,
|
balance = if (state.sendCardData.isBalanceHidden) {
|
||||||
|
STARS
|
||||||
|
} else {
|
||||||
|
state.sendCardData.balance
|
||||||
|
},
|
||||||
textFieldValue = state.sendCardData.amountTextFieldValue,
|
textFieldValue = state.sendCardData.amountTextFieldValue,
|
||||||
amountEquivalent = state.sendCardData.amountEquivalent,
|
amountEquivalent = state.sendCardData.amountEquivalent,
|
||||||
tokenIconUrl = state.sendCardData.tokenIconUrl,
|
tokenIconUrl = state.sendCardData.tokenIconUrl,
|
||||||
|
|
@ -161,7 +166,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
||||||
val marginCard = TangemTheme.dimens.spacing16
|
val marginCard = TangemTheme.dimens.spacing16
|
||||||
TransactionCard(
|
TransactionCard(
|
||||||
type = state.receiveCardData.type,
|
type = state.receiveCardData.type,
|
||||||
balance = state.receiveCardData.balance,
|
balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance,
|
||||||
textFieldValue = state.receiveCardData.amountTextFieldValue,
|
textFieldValue = state.receiveCardData.amountTextFieldValue,
|
||||||
amountEquivalent = state.receiveCardData.amountEquivalent,
|
amountEquivalent = state.receiveCardData.amountEquivalent,
|
||||||
tokenIconUrl = state.receiveCardData.tokenIconUrl,
|
tokenIconUrl = state.receiveCardData.tokenIconUrl,
|
||||||
|
|
@ -371,6 +376,7 @@ private val sendCard = SwapCardData(
|
||||||
canSelectAnotherToken = false,
|
canSelectAnotherToken = false,
|
||||||
balance = "123",
|
balance = "123",
|
||||||
coinId = "",
|
coinId = "",
|
||||||
|
isBalanceHidden = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val receiveCard = SwapCardData(
|
private val receiveCard = SwapCardData(
|
||||||
|
|
@ -383,6 +389,7 @@ private val receiveCard = SwapCardData(
|
||||||
canSelectAnotherToken = true,
|
canSelectAnotherToken = true,
|
||||||
balance = "33333",
|
balance = "33333",
|
||||||
coinId = "",
|
coinId = "",
|
||||||
|
isBalanceHidden = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val stateSelectable = SelectableItemsState(
|
val stateSelectable = SelectableItemsState(
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ package com.tangem.feature.swap.viewmodels
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.*
|
||||||
import androidx.lifecycle.ViewModel
|
import com.tangem.common.Provider
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.ui.utils.InputNumberFormatter
|
import com.tangem.core.ui.utils.InputNumberFormatter
|
||||||
|
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
|
||||||
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.feature.swap.analytics.SwapEvents
|
import com.tangem.feature.swap.analytics.SwapEvents
|
||||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||||
import com.tangem.feature.swap.domain.SwapInteractor
|
import com.tangem.feature.swap.domain.SwapInteractor
|
||||||
|
|
@ -27,8 +28,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.Debouncer
|
import com.tangem.utils.coroutines.Debouncer
|
||||||
import com.tangem.utils.coroutines.runCatching
|
import com.tangem.utils.coroutines.runCatching
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
@ -37,15 +39,17 @@ import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass", "LongParameterList")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class SwapViewModel @Inject constructor(
|
internal class SwapViewModel @Inject constructor(
|
||||||
private val swapInteractor: SwapInteractor,
|
private val swapInteractor: SwapInteractor,
|
||||||
private val blockchainInteractor: BlockchainInteractor,
|
private val blockchainInteractor: BlockchainInteractor,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
|
||||||
|
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
) : ViewModel() {
|
) : ViewModel(), DefaultLifecycleObserver {
|
||||||
|
|
||||||
private val currency = Json.decodeFromString<Currency>(
|
private val currency = Json.decodeFromString<Currency>(
|
||||||
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
|
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
|
||||||
|
|
@ -53,15 +57,20 @@ internal class SwapViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
private val derivationPath = savedStateHandle.get<String>(SwapFragment.DERIVATION_PATH)
|
private val derivationPath = savedStateHandle.get<String>(SwapFragment.DERIVATION_PATH)
|
||||||
|
|
||||||
|
private var isBalanceHidden = true
|
||||||
|
|
||||||
private val stateBuilder = StateBuilder(
|
private val stateBuilder = StateBuilder(
|
||||||
actions = createUiActions(),
|
actions = createUiActions(),
|
||||||
|
isBalanceHiddenProvider = Provider { isBalanceHidden },
|
||||||
)
|
)
|
||||||
|
|
||||||
private val inputNumberFormatter =
|
private val inputNumberFormatter =
|
||||||
InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat)
|
InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat)
|
||||||
private val amountDebouncer = Debouncer()
|
private val amountDebouncer = Debouncer()
|
||||||
private val singleTaskScheduler = SingleTaskScheduler<SwapState>()
|
private val singleTaskScheduler = SingleTaskScheduler<SwapState>()
|
||||||
|
|
||||||
private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId))
|
private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId))
|
||||||
|
|
||||||
var uiState: SwapStateHolder by mutableStateOf(
|
var uiState: SwapStateHolder by mutableStateOf(
|
||||||
stateBuilder.createInitialLoadingState(
|
stateBuilder.createInitialLoadingState(
|
||||||
initialCurrency = currency,
|
initialCurrency = currency,
|
||||||
|
|
@ -82,6 +91,24 @@ internal class SwapViewModel @Inject constructor(
|
||||||
initTokens(currency)
|
initTokens(currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
isBalanceHiddenUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.onEach { hidden ->
|
||||||
|
isBalanceHidden = hidden
|
||||||
|
withContext(dispatchers.main) {
|
||||||
|
uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
listenToFlipsUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
singleTaskScheduler.cancelTask()
|
singleTaskScheduler.cancelTask()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,14 @@ dependencies {
|
||||||
implementation(projects.domain.appCurrency.models)
|
implementation(projects.domain.appCurrency.models)
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
|
implementation(projects.domain.settings)
|
||||||
implementation(projects.domain.tokens)
|
implementation(projects.domain.tokens)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.txhistory)
|
implementation(projects.domain.txhistory)
|
||||||
implementation(projects.domain.txhistory.models)
|
implementation(projects.domain.txhistory.models)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.domain.balanceHiding)
|
||||||
|
|
||||||
/** Feature Apis */
|
/** Feature Apis */
|
||||||
implementation(projects.features.tokendetails.api)
|
implementation(projects.features.tokendetails.api)
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ internal object TokenDetailsPreviewData {
|
||||||
actionButtons = actionButtons,
|
actionButtons = actionButtons,
|
||||||
fiatBalance = "123,00$",
|
fiatBalance = "123,00$",
|
||||||
cryptoBalance = "866,96 USDT",
|
cryptoBalance = "866,96 USDT",
|
||||||
|
isBalanceHidden = false,
|
||||||
)
|
)
|
||||||
val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = actionButtons)
|
val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = actionButtons)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ internal sealed class TokenDetailsBalanceBlockState {
|
||||||
override val actionButtons: ImmutableList<TokenDetailsActionButton>,
|
override val actionButtons: ImmutableList<TokenDetailsActionButton>,
|
||||||
val fiatBalance: String,
|
val fiatBalance: String,
|
||||||
val cryptoBalance: String,
|
val cryptoBalance: String,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
) : TokenDetailsBalanceBlockState()
|
) : TokenDetailsBalanceBlockState()
|
||||||
|
|
||||||
data class Error(
|
data class Error(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import java.math.BigDecimal
|
||||||
internal class TokenDetailsLoadedBalanceConverter(
|
internal class TokenDetailsLoadedBalanceConverter(
|
||||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
private val symbol: String,
|
private val symbol: String,
|
||||||
private val decimals: Int,
|
private val decimals: Int,
|
||||||
) : Converter<Either<CurrencyStatusError, CryptoCurrencyStatus>, TokenDetailsState> {
|
) : Converter<Either<CurrencyStatusError, CryptoCurrencyStatus>, TokenDetailsState> {
|
||||||
|
|
@ -57,6 +58,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
||||||
actionButtons = currentState.actionButtons,
|
actionButtons = currentState.actionButtons,
|
||||||
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
||||||
cryptoBalance = formatCryptoAmount(status),
|
cryptoBalance = formatCryptoAmount(status),
|
||||||
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CryptoCurrencyStatus.Loading -> {
|
is CryptoCurrencyStatus.Loading -> {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.tokens.models.CryptoCurrency
|
||||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||||
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
|
||||||
|
|
@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
internal class TokenDetailsStateFactory(
|
internal class TokenDetailsStateFactory(
|
||||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
private val clickIntents: TokenDetailsClickIntents,
|
private val clickIntents: TokenDetailsClickIntents,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
decimals: Int,
|
decimals: Int,
|
||||||
|
|
@ -38,6 +40,7 @@ internal class TokenDetailsStateFactory(
|
||||||
TokenDetailsLoadedBalanceConverter(
|
TokenDetailsLoadedBalanceConverter(
|
||||||
currentStateProvider = currentStateProvider,
|
currentStateProvider = currentStateProvider,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
symbol = symbol,
|
symbol = symbol,
|
||||||
decimals = decimals,
|
decimals = decimals,
|
||||||
)
|
)
|
||||||
|
|
@ -162,4 +165,16 @@ internal class TokenDetailsStateFactory(
|
||||||
bottomSheetConfig = state.bottomSheetConfig?.copy(isShow = false),
|
bottomSheetConfig = state.bottomSheetConfig?.copy(isShow = false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getStateWithUpdatedHidden(isBalanceHidden: Boolean): TokenDetailsState {
|
||||||
|
val currentState = currentStateProvider()
|
||||||
|
val possibleTokenBalanceBlockState = currentState.tokenBalanceBlockState as?
|
||||||
|
TokenDetailsBalanceBlockState.Content
|
||||||
|
|
||||||
|
possibleTokenBalanceBlockState?.let {
|
||||||
|
return currentState.copy(
|
||||||
|
tokenBalanceBlockState = possibleTokenBalanceBlockState.copy(isBalanceHidden = isBalanceHidden),
|
||||||
|
)
|
||||||
|
} ?: return currentState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ private fun FiatBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier
|
||||||
)
|
)
|
||||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
text = state.fiatBalance,
|
text = if (state.isBalanceHidden) DOTS else state.fiatBalance,
|
||||||
style = TangemTheme.typography.h2,
|
style = TangemTheme.typography.h2,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
)
|
)
|
||||||
|
|
@ -96,7 +96,7 @@ private fun CryptoBalance(state: TokenDetailsBalanceBlockState, modifier: Modifi
|
||||||
)
|
)
|
||||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
text = state.cryptoBalance,
|
text = if (state.isBalanceHidden) DOTS else state.cryptoBalance,
|
||||||
style = TangemTheme.typography.caption,
|
style = TangemTheme.typography.caption,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
)
|
)
|
||||||
|
|
@ -135,4 +135,6 @@ private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameter
|
||||||
TokenDetailsPreviewData.balanceContent,
|
TokenDetailsPreviewData.balanceContent,
|
||||||
TokenDetailsPreviewData.balanceError,
|
TokenDetailsPreviewData.balanceError,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const val DOTS = "***"
|
||||||
|
|
@ -9,6 +9,8 @@ import arrow.core.getOrElse
|
||||||
import com.tangem.common.Provider
|
import com.tangem.common.Provider
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
|
||||||
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.tokens.*
|
import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
|
|
@ -49,6 +51,8 @@ internal class TokenDetailsViewModel @Inject constructor(
|
||||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||||
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
|
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
|
||||||
|
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
|
||||||
|
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val reduxStateHolder: ReduxStateHolder,
|
private val reduxStateHolder: ReduxStateHolder,
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
|
|
@ -65,19 +69,24 @@ internal class TokenDetailsViewModel @Inject constructor(
|
||||||
private var wallet by Delegates.notNull<UserWallet>()
|
private var wallet by Delegates.notNull<UserWallet>()
|
||||||
|
|
||||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||||
|
private var isBalanceHidden = true
|
||||||
|
|
||||||
private val stateFactory = TokenDetailsStateFactory(
|
private val stateFactory = TokenDetailsStateFactory(
|
||||||
currentStateProvider = Provider { uiState },
|
currentStateProvider = Provider { uiState },
|
||||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||||
|
isBalanceHiddenProvider = Provider { isBalanceHidden },
|
||||||
clickIntents = this,
|
clickIntents = this,
|
||||||
symbol = cryptoCurrency.symbol,
|
symbol = cryptoCurrency.symbol,
|
||||||
decimals = cryptoCurrency.decimals,
|
decimals = cryptoCurrency.decimals,
|
||||||
)
|
)
|
||||||
|
|
||||||
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
|
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
getWallet()
|
getWallet()
|
||||||
updateContent(selectedWallet = wallet)
|
updateContent(selectedWallet = wallet)
|
||||||
|
handleBalanceHiding(owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWallet() {
|
private fun getWallet() {
|
||||||
|
|
@ -93,6 +102,22 @@ internal class TokenDetailsViewModel @Inject constructor(
|
||||||
updateTxHistory()
|
updateTxHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleBalanceHiding(owner: LifecycleOwner) {
|
||||||
|
isBalanceHiddenUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.onEach { hidden ->
|
||||||
|
isBalanceHidden = hidden
|
||||||
|
uiState = stateFactory.getStateWithUpdatedHidden(isBalanceHidden = hidden)
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
listenToFlipsUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateButtons(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
private fun updateButtons(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||||
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, cryptoCurrencyStatus = currencyStatus)
|
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, cryptoCurrencyStatus = currencyStatus)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ dependencies {
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.appCurrency)
|
implementation(projects.domain.appCurrency)
|
||||||
implementation(projects.domain.appCurrency.models)
|
implementation(projects.domain.appCurrency.models)
|
||||||
|
implementation(projects.domain.balanceHiding)
|
||||||
|
|
||||||
/** Feature Apis */
|
/** Feature Apis */
|
||||||
implementation(projects.features.wallet.api)
|
implementation(projects.features.wallet.api)
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ internal object WalletPreviewData {
|
||||||
imageResId = R.drawable.ill_businessman_3d,
|
imageResId = R.drawable.ill_businessman_3d,
|
||||||
onRenameClick = { _, _ -> },
|
onRenameClick = { _, _ -> },
|
||||||
onDeleteClick = {},
|
onDeleteClick = {},
|
||||||
|
balance = "8923,05 $",
|
||||||
|
additionalInfo = TextReference.Str("3 cards • Seed phrase"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,12 +127,13 @@ internal object WalletPreviewData {
|
||||||
name = "Polygon",
|
name = "Polygon",
|
||||||
amount = "5,412 MATIC",
|
amount = "5,412 MATIC",
|
||||||
hasPending = true,
|
hasPending = true,
|
||||||
tokenOptions = TokenOptionsState.Visible(
|
tokenOptions = TokenOptionsState(
|
||||||
fiatAmount = "321 $",
|
fiatAmount = "321 $",
|
||||||
config = PriceChangeConfig(
|
config = PriceChangeConfig(
|
||||||
valueInPercent = "2%",
|
valueInPercent = "2%",
|
||||||
type = PriceChangeConfig.Type.UP,
|
type = PriceChangeConfig.Type.UP,
|
||||||
),
|
),
|
||||||
|
isBalanceHidden = false,
|
||||||
),
|
),
|
||||||
onItemClick = {},
|
onItemClick = {},
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
|
|
@ -151,11 +154,14 @@ internal object WalletPreviewData {
|
||||||
name = "Polygon",
|
name = "Polygon",
|
||||||
amount = "5,412 MATIC",
|
amount = "5,412 MATIC",
|
||||||
hasPending = true,
|
hasPending = true,
|
||||||
tokenOptions = TokenOptionsState.Hidden(
|
tokenOptions = TokenOptionsState(
|
||||||
config = PriceChangeConfig(
|
config = PriceChangeConfig(
|
||||||
valueInPercent = "2%",
|
valueInPercent = "2%",
|
||||||
type = PriceChangeConfig.Type.UP,
|
type = PriceChangeConfig.Type.UP,
|
||||||
),
|
),
|
||||||
|
fiatAmount = "321 $",
|
||||||
|
isBalanceHidden = false,
|
||||||
|
|
||||||
),
|
),
|
||||||
onItemClick = {},
|
onItemClick = {},
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,13 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import com.tangem.common.Strings.STARS
|
||||||
import com.tangem.core.ui.components.RectangleShimmer
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemTypography
|
import com.tangem.core.ui.res.TangemTypography
|
||||||
import com.tangem.feature.wallet.impl.R
|
import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
|
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TokenCryptoInfoBlock(state: TokenItemState, modifier: Modifier = Modifier) {
|
internal fun TokenCryptoInfoBlock(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||||
|
|
@ -40,7 +39,7 @@ private fun ContentBlock(state: TokenItemState.ContentState, modifier: Modifier
|
||||||
|
|
||||||
AmountText(
|
AmountText(
|
||||||
amount = when (state) {
|
amount = when (state) {
|
||||||
is TokenItemState.Content -> if (state.tokenOptions is TokenOptionsState.Hidden) DOTS else state.amount
|
is TokenItemState.Content -> if (state.tokenOptions.isBalanceHidden) STARS else state.amount
|
||||||
is TokenItemState.Draggable -> state.info.resolveReference()
|
is TokenItemState.Draggable -> state.info.resolveReference()
|
||||||
is TokenItemState.Unreachable -> null
|
is TokenItemState.Unreachable -> null
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.tangem.common.Strings.STARS
|
||||||
import com.tangem.core.ui.components.RectangleShimmer
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
import com.tangem.core.ui.components.SpacerW4
|
import com.tangem.core.ui.components.SpacerW4
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
||||||
|
|
@ -18,7 +19,6 @@ import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemTypography
|
import com.tangem.core.ui.res.TangemTypography
|
||||||
import com.tangem.feature.wallet.impl.R
|
import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
|
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||||
import org.burnoutcrew.reorderable.detectReorder
|
import org.burnoutcrew.reorderable.detectReorder
|
||||||
|
|
@ -56,9 +56,10 @@ private fun ContentBlock(state: TokenOptionsState, modifier: Modifier = Modifier
|
||||||
modifier = Modifier.align(Alignment.End),
|
modifier = Modifier.align(Alignment.End),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = when (it) {
|
text = if (it.isBalanceHidden) {
|
||||||
is TokenOptionsState.Visible -> it.fiatAmount
|
STARS
|
||||||
is TokenOptionsState.Hidden -> DOTS
|
} else {
|
||||||
|
it.fiatAmount
|
||||||
},
|
},
|
||||||
style = TangemTypography.body2,
|
style = TangemTypography.body2,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
|
|
||||||
|
|
@ -146,30 +146,9 @@ internal sealed interface TokenItemState {
|
||||||
|
|
||||||
/** Token options state */
|
/** Token options state */
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface TokenOptionsState {
|
data class TokenOptionsState(
|
||||||
|
val config: PriceChangeConfig,
|
||||||
val config: PriceChangeConfig
|
val fiatAmount: String,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
/**
|
)
|
||||||
* Visible token options state
|
|
||||||
*
|
|
||||||
* @property fiatAmount fiat amount of token
|
|
||||||
* @property config value of price changing
|
|
||||||
*/
|
|
||||||
data class Visible(
|
|
||||||
override val config: PriceChangeConfig,
|
|
||||||
val fiatAmount: String,
|
|
||||||
) : TokenOptionsState
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hidden token options state
|
|
||||||
*
|
|
||||||
* @property config value of price changing
|
|
||||||
*/
|
|
||||||
data class Hidden(override val config: PriceChangeConfig) : TokenOptionsState
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DOTS = "•••"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter
|
||||||
|
|
||||||
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
|
|
||||||
|
internal class TokenItemHiddenStateConverter {
|
||||||
|
|
||||||
|
fun updateHiddenState(
|
||||||
|
optionsState: TokenItemState.TokenOptionsState,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
): TokenItemState.TokenOptionsState {
|
||||||
|
return when {
|
||||||
|
!optionsState.isBalanceHidden && isBalanceHidden -> {
|
||||||
|
optionsState.copy(isBalanceHidden = true)
|
||||||
|
}
|
||||||
|
optionsState.isBalanceHidden && !isBalanceHidden -> {
|
||||||
|
optionsState.copy(isBalanceHidden = false)
|
||||||
|
}
|
||||||
|
else -> optionsState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,11 +49,13 @@ internal sealed interface WalletCardState {
|
||||||
/**
|
/**
|
||||||
* Wallet card hidden content state
|
* Wallet card hidden content state
|
||||||
*
|
*
|
||||||
* @property id wallet id
|
* @property id wallet id
|
||||||
* @property title wallet name
|
* @property title wallet name
|
||||||
* @property imageResId wallet image resource id
|
* @property imageResId wallet image resource id
|
||||||
* @property onRenameClick lambda be invoked when Rename button is clicked
|
* @property onRenameClick lambda be invoked when Rename button is clicked
|
||||||
* @property onDeleteClick lambda be invoked when Delete button is clicked
|
* @property onDeleteClick lambda be invoked when Delete button is clicked
|
||||||
|
* @property additionalInfo wallet additional info
|
||||||
|
* @property balance wallet balance
|
||||||
*/
|
*/
|
||||||
data class HiddenContent(
|
data class HiddenContent(
|
||||||
override val id: UserWalletId,
|
override val id: UserWalletId,
|
||||||
|
|
@ -61,6 +63,8 @@ internal sealed interface WalletCardState {
|
||||||
override val imageResId: Int?,
|
override val imageResId: Int?,
|
||||||
override val onRenameClick: (UserWalletId, String) -> Unit,
|
override val onRenameClick: (UserWalletId, String) -> Unit,
|
||||||
override val onDeleteClick: (UserWalletId) -> Unit,
|
override val onDeleteClick: (UserWalletId) -> Unit,
|
||||||
|
val additionalInfo: TextReference,
|
||||||
|
val balance: String,
|
||||||
) : WalletCardState
|
) : WalletCardState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,13 @@ import com.tangem.utils.converter.Converter
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal class WalletLoadedTokensListConverter(
|
internal class WalletLoadedTokensListConverter(
|
||||||
private val currentStateProvider: Provider<WalletState>,
|
private val currentStateProvider: Provider<WalletState>,
|
||||||
private val tokenListErrorConverter: TokenListErrorConverter,
|
private val tokenListErrorConverter: TokenListErrorConverter,
|
||||||
appCurrencyProvider: Provider<AppCurrency>,
|
appCurrencyProvider: Provider<AppCurrency>,
|
||||||
currentWalletProvider: Provider<UserWallet>,
|
currentWalletProvider: Provider<UserWallet>,
|
||||||
|
isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) : Converter<Either<TokenListError, TokenList>, WalletState> {
|
) : Converter<Either<TokenListError, TokenList>, WalletState> {
|
||||||
|
|
||||||
|
|
@ -36,7 +38,7 @@ internal class WalletLoadedTokensListConverter(
|
||||||
currentStateProvider = currentStateProvider,
|
currentStateProvider = currentStateProvider,
|
||||||
currentWalletProvider = currentWalletProvider,
|
currentWalletProvider = currentWalletProvider,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,16 @@ import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.*
|
import com.tangem.feature.wallet.presentation.wallet.state.*
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter
|
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter
|
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.utils.CurrencyStatusErrorConverter
|
import com.tangem.feature.wallet.presentation.wallet.utils.CurrencyStatusErrorConverter
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.utils.HiddenStateConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorConverter
|
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
||||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver
|
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver
|
||||||
|
|
@ -44,6 +49,7 @@ internal class WalletStateFactory(
|
||||||
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
||||||
private val currentWalletProvider: Provider<UserWallet>,
|
private val currentWalletProvider: Provider<UserWallet>,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -57,6 +63,8 @@ internal class WalletStateFactory(
|
||||||
|
|
||||||
private val walletDeleteStateConverter by lazy { WalletDeleteStateConverter(currentStateProvider) }
|
private val walletDeleteStateConverter by lazy { WalletDeleteStateConverter(currentStateProvider) }
|
||||||
|
|
||||||
|
private val hiddenStateConverter by lazy { HiddenStateConverter(currentStateProvider) }
|
||||||
|
|
||||||
private val tokenListErrorConverter by lazy {
|
private val tokenListErrorConverter by lazy {
|
||||||
TokenListErrorConverter(currentStateProvider)
|
TokenListErrorConverter(currentStateProvider)
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +77,7 @@ internal class WalletStateFactory(
|
||||||
tokenListErrorConverter = tokenListErrorConverter,
|
tokenListErrorConverter = tokenListErrorConverter,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
currentWalletProvider = currentWalletProvider,
|
currentWalletProvider = currentWalletProvider,
|
||||||
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +251,10 @@ internal class WalletStateFactory(
|
||||||
return currencyStatusErrorConverter.convert(error)
|
return currencyStatusErrorConverter.convert(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getHiddenBalanceState(isBalanceHidden: Boolean): WalletState {
|
||||||
|
return hiddenStateConverter.convert(isBalanceHidden)
|
||||||
|
}
|
||||||
|
|
||||||
fun getStateAndTriggerEvent(
|
fun getStateAndTriggerEvent(
|
||||||
state: WalletState,
|
state: WalletState,
|
||||||
event: WalletEvent,
|
event: WalletEvent,
|
||||||
|
|
|
||||||
|
|
@ -241,15 +241,6 @@ private fun Title(state: WalletCardState, modifier: Modifier = Modifier) {
|
||||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||||
) {
|
) {
|
||||||
TitleText(title = state.title)
|
TitleText(title = state.title)
|
||||||
|
|
||||||
AnimatedVisibility(visible = state is WalletCardState.HiddenContent, label = "Update the hidden icon") {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
|
||||||
painter = painterResource(id = R.drawable.ic_eye_off_24),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = TangemTheme.colors.icon.informative,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
internal class CryptoCurrencyStatusToTokenItemConverter(
|
internal class CryptoCurrencyStatusToTokenItemConverter(
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val isWalletContentHidden: Boolean,
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||||
|
|
||||||
|
|
@ -42,14 +42,11 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
|
||||||
icon = iconStateConverter.convert(value = this),
|
icon = iconStateConverter.convert(value = this),
|
||||||
amount = getFormattedAmount(),
|
amount = getFormattedAmount(),
|
||||||
hasPending = value.hasCurrentNetworkTransactions,
|
hasPending = value.hasCurrentNetworkTransactions,
|
||||||
tokenOptions = if (isWalletContentHidden) {
|
tokenOptions = TokenItemState.TokenOptionsState(
|
||||||
TokenItemState.TokenOptionsState.Hidden(getPriceChangeConfig())
|
fiatAmount = getFormattedFiatAmount(),
|
||||||
} else {
|
config = getPriceChangeConfig(),
|
||||||
TokenItemState.TokenOptionsState.Visible(
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
fiatAmount = getFormattedFiatAmount(),
|
),
|
||||||
config = getPriceChangeConfig(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onItemClick = { clickIntents.onTokenItemClick(currency) },
|
onItemClick = { clickIntents.onTokenItemClick(currency) },
|
||||||
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
|
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ internal class FiatBalanceToWalletCardConverter(
|
||||||
private val currentState: WalletCardState,
|
private val currentState: WalletCardState,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val currentWalletProvider: Provider<UserWallet>,
|
private val currentWalletProvider: Provider<UserWallet>,
|
||||||
private val isWalletContentHidden: Boolean,
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
) : Converter<FiatBalance, WalletCardState> {
|
) : Converter<FiatBalance, WalletCardState> {
|
||||||
|
|
||||||
override fun convert(value: FiatBalance): WalletCardState {
|
override fun convert(value: FiatBalance): WalletCardState {
|
||||||
|
|
@ -39,17 +39,23 @@ internal class FiatBalanceToWalletCardConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FiatBalance.Loaded.convertToWalletCardState(): WalletCardState {
|
private fun FiatBalance.Loaded.convertToWalletCardState(): WalletCardState {
|
||||||
return if (isWalletContentHidden) {
|
val appCurrency = appCurrencyProvider()
|
||||||
|
|
||||||
|
return if (isBalanceHiddenProvider()) {
|
||||||
WalletCardState.HiddenContent(
|
WalletCardState.HiddenContent(
|
||||||
id = currentState.id,
|
id = currentState.id,
|
||||||
title = currentState.title,
|
title = currentState.title,
|
||||||
imageResId = currentState.imageResId,
|
imageResId = currentState.imageResId,
|
||||||
onRenameClick = currentState.onRenameClick,
|
onRenameClick = currentState.onRenameClick,
|
||||||
onDeleteClick = currentState.onDeleteClick,
|
onDeleteClick = currentState.onDeleteClick,
|
||||||
|
balance = formatFiatAmount(
|
||||||
|
fiatAmount = this.amount,
|
||||||
|
fiatCurrencyCode = appCurrency.code,
|
||||||
|
fiatCurrencySymbol = appCurrency.symbol,
|
||||||
|
),
|
||||||
|
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val appCurrency = appCurrencyProvider()
|
|
||||||
|
|
||||||
WalletCardState.Content(
|
WalletCardState.Content(
|
||||||
id = currentState.id,
|
id = currentState.id,
|
||||||
title = currentState.title,
|
title = currentState.title,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.common.Converter
|
||||||
|
import com.tangem.common.Provider
|
||||||
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
|
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.TokenItemHiddenStateConverter
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
internal class HiddenStateConverter(
|
||||||
|
private val currentStateProvider: Provider<WalletState>,
|
||||||
|
) : Converter<Boolean, WalletState> {
|
||||||
|
|
||||||
|
private val walletHiddenBalanceStateConverter by lazy { WalletHiddenBalanceStateConverter() }
|
||||||
|
|
||||||
|
private val tokenItemHiddenStateConverter by lazy { TokenItemHiddenStateConverter() }
|
||||||
|
|
||||||
|
override fun convert(value: Boolean): WalletState {
|
||||||
|
return when (val state = currentStateProvider() as? WalletState.ContentState) {
|
||||||
|
is WalletMultiCurrencyState.Content -> {
|
||||||
|
val updatedTokensList = (state.tokensListState as? WalletTokensListState.Content)?.let { content ->
|
||||||
|
content.copy(
|
||||||
|
items = content.items.map { tokenListItemState ->
|
||||||
|
if (tokenListItemState is WalletTokensListState.TokensListItemState.Token) {
|
||||||
|
if (tokenListItemState.state is TokenItemState.Content) {
|
||||||
|
tokenListItemState.copy(
|
||||||
|
state = tokenListItemState.state.copy(
|
||||||
|
tokenOptions = tokenItemHiddenStateConverter.updateHiddenState(
|
||||||
|
optionsState = tokenListItemState.state.tokenOptions,
|
||||||
|
isBalanceHidden = value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
tokenListItemState
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokenListItemState
|
||||||
|
}
|
||||||
|
}.toImmutableList(),
|
||||||
|
)
|
||||||
|
} ?: state.tokensListState
|
||||||
|
|
||||||
|
state.copy(
|
||||||
|
walletsListConfig = state.walletsListConfig.copy(
|
||||||
|
wallets = state.walletsListConfig.wallets.map {
|
||||||
|
walletHiddenBalanceStateConverter.updateHiddenState(it, value)
|
||||||
|
}.toImmutableList(),
|
||||||
|
),
|
||||||
|
tokensListState = updatedTokensList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is WalletSingleCurrencyState.Content -> {
|
||||||
|
state.copy(
|
||||||
|
walletsListConfig = state.walletsListConfig.copy(
|
||||||
|
wallets = state.walletsListConfig.wallets.map {
|
||||||
|
walletHiddenBalanceStateConverter.updateHiddenState(it, value)
|
||||||
|
}.toImmutableList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> currentStateProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,12 +17,12 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
internal class TokenListToContentItemsConverter(
|
internal class TokenListToContentItemsConverter(
|
||||||
appCurrencyProvider: Provider<AppCurrency>,
|
appCurrencyProvider: Provider<AppCurrency>,
|
||||||
isWalletContentHidden: Boolean,
|
isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
) : Converter<TokenList, WalletTokensListState> {
|
) : Converter<TokenList, WalletTokensListState> {
|
||||||
|
|
||||||
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
|
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
|
||||||
isWalletContentHidden = isWalletContentHidden,
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ internal class TokenListToWalletStateConverter(
|
||||||
private val currentStateProvider: Provider<WalletState>,
|
private val currentStateProvider: Provider<WalletState>,
|
||||||
private val currentWalletProvider: Provider<UserWallet>,
|
private val currentWalletProvider: Provider<UserWallet>,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val isWalletContentHidden: Boolean,
|
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) : Converter<TokenList, WalletState> {
|
) : Converter<TokenList, WalletState> {
|
||||||
|
|
||||||
private val tokenListToContentConverter = TokenListToContentItemsConverter(
|
private val tokenListToContentConverter = TokenListToContentItemsConverter(
|
||||||
isWalletContentHidden = isWalletContentHidden,
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
)
|
)
|
||||||
|
|
@ -48,9 +48,9 @@ internal class TokenListToWalletStateConverter(
|
||||||
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
|
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
|
||||||
val converter = FiatBalanceToWalletCardConverter(
|
val converter = FiatBalanceToWalletCardConverter(
|
||||||
currentState = selectedWalletCard,
|
currentState = selectedWalletCard,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
|
||||||
currentWalletProvider = currentWalletProvider,
|
currentWalletProvider = currentWalletProvider,
|
||||||
isWalletContentHidden = isWalletContentHidden,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
|
isBalanceHiddenProvider = isBalanceHiddenProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
return walletsListConfig.copy(
|
return walletsListConfig.copy(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||||
|
|
||||||
|
internal class WalletHiddenBalanceStateConverter {
|
||||||
|
|
||||||
|
fun updateHiddenState(walletCardState: WalletCardState, hiddenBalance: Boolean): WalletCardState {
|
||||||
|
return when {
|
||||||
|
walletCardState is WalletCardState.Content && hiddenBalance -> {
|
||||||
|
contentToHidden(walletCardState)
|
||||||
|
}
|
||||||
|
walletCardState is WalletCardState.HiddenContent && !hiddenBalance -> {
|
||||||
|
hiddenToContent(walletCardState)
|
||||||
|
}
|
||||||
|
else -> walletCardState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun contentToHidden(content: WalletCardState.Content): WalletCardState.HiddenContent {
|
||||||
|
return WalletCardState.HiddenContent(
|
||||||
|
id = content.id,
|
||||||
|
title = content.title,
|
||||||
|
additionalInfo = content.additionalInfo,
|
||||||
|
imageResId = content.imageResId,
|
||||||
|
onRenameClick = content.onRenameClick,
|
||||||
|
onDeleteClick = content.onDeleteClick,
|
||||||
|
balance = content.balance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hiddenToContent(hiddenContent: WalletCardState.HiddenContent): WalletCardState.Content {
|
||||||
|
return WalletCardState.Content(
|
||||||
|
id = hiddenContent.id,
|
||||||
|
title = hiddenContent.title,
|
||||||
|
additionalInfo = hiddenContent.additionalInfo,
|
||||||
|
imageResId = hiddenContent.imageResId,
|
||||||
|
onRenameClick = hiddenContent.onRenameClick,
|
||||||
|
onDeleteClick = hiddenContent.onDeleteClick,
|
||||||
|
balance = hiddenContent.balance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,14 +14,14 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
|
||||||
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.domain.card.*
|
import com.tangem.domain.card.*
|
||||||
import com.tangem.domain.common.CardTypesResolver
|
import com.tangem.domain.common.CardTypesResolver
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
import com.tangem.domain.settings.*
|
||||||
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
|
|
||||||
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
|
|
||||||
import com.tangem.domain.tokens.*
|
import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
|
|
@ -93,6 +93,8 @@ internal class WalletViewModel @Inject constructor(
|
||||||
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
|
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
|
||||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||||
|
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
|
||||||
|
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val reduxStateHolder: ReduxStateHolder,
|
private val reduxStateHolder: ReduxStateHolder,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
|
@ -104,6 +106,7 @@ internal class WalletViewModel @Inject constructor(
|
||||||
var router: InnerWalletRouter by Delegates.notNull()
|
var router: InnerWalletRouter by Delegates.notNull()
|
||||||
|
|
||||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||||
|
private var isBalanceHidden = true
|
||||||
|
|
||||||
private val notificationsListFactory = WalletNotificationsListFactory(
|
private val notificationsListFactory = WalletNotificationsListFactory(
|
||||||
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
|
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
|
||||||
|
|
@ -123,6 +126,7 @@ internal class WalletViewModel @Inject constructor(
|
||||||
wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex]
|
wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex]
|
||||||
},
|
},
|
||||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||||
|
isBalanceHiddenProvider = Provider { isBalanceHidden },
|
||||||
clickIntents = this,
|
clickIntents = this,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -161,6 +165,20 @@ internal class WalletViewModel @Inject constructor(
|
||||||
.onEach(::updateWallets)
|
.onEach(::updateWallets)
|
||||||
.flowOn(dispatchers.io)
|
.flowOn(dispatchers.io)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
isBalanceHiddenUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.onEach { hidden ->
|
||||||
|
isBalanceHidden = hidden
|
||||||
|
uiState = stateFactory.getHiddenBalanceState(isBalanceHidden = hidden)
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
listenToFlipsUseCase()
|
||||||
|
.flowWithLifecycle(owner.lifecycle)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWallets(sourceList: List<UserWallet>) {
|
private fun updateWallets(sourceList: List<UserWallet>) {
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,15 @@ include(":domain:app-currency")
|
||||||
include(":domain:app-currency:models")
|
include(":domain:app-currency:models")
|
||||||
include(":domain:app-theme")
|
include(":domain:app-theme")
|
||||||
include(":domain:app-theme:models")
|
include(":domain:app-theme:models")
|
||||||
|
include(":domain:balance-hiding")
|
||||||
|
include(":domain:balance-hiding:models")
|
||||||
|
|
||||||
// endregion Domain modules
|
// endregion Domain modules
|
||||||
|
|
||||||
// region Data modules
|
// region Data modules
|
||||||
include(":data:app-currency")
|
include(":data:app-currency")
|
||||||
include(":data:app-theme")
|
include(":data:app-theme")
|
||||||
|
include(":data:balance-hiding")
|
||||||
include(":data:common")
|
include(":data:common")
|
||||||
include(":data:card")
|
include(":data:card")
|
||||||
include(":data:tokens")
|
include(":data:tokens")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue