Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-20 18:48:25 +03:00
parent 3c59214c76
commit 4c903fc654
73 changed files with 886 additions and 190 deletions

View file

@ -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)

View file

@ -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) }
}
}

View file

@ -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,
), ),
), ),

View file

@ -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,
)
}
} }

View file

@ -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()

View file

@ -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) {

View file

@ -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

View file

@ -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,

View file

@ -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",

View file

@ -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, {}),
) )

View file

@ -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) }
} }

View file

@ -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

View file

@ -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)
} }

View file

@ -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 = ".",

View file

@ -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()
} }
} }

View file

@ -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()
}
}
}

View file

@ -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

View file

@ -0,0 +1,6 @@
package com.tangem.common
object Strings {
const val STARS = "***"
}

View file

@ -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)

View file

@ -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),
),
)
}
}

View file

@ -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)
}

View file

@ -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,
)
}
}

View file

@ -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>

View file

@ -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
View file

@ -0,0 +1 @@
/build

View 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)
}

View file

@ -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()
}
}
}

View file

@ -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) }
}
}

View file

@ -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
}
}
}
}

View file

@ -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)
}
}

View file

@ -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)
} }

View file

@ -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
View file

@ -0,0 +1 @@
/build

View 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)
}

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,4 @@
plugins {
alias(deps.plugins.kotlin.jvm)
id("configuration")
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.balancehiding
data class BalanceHidingSettings(
val isHidingEnabledInSettings: Boolean,
val isBalanceHidden: Boolean,
)

View file

@ -0,0 +1,8 @@
package com.tangem.domain.balancehiding
import kotlinx.coroutines.flow.Flow
interface DeviceFlipDetector {
fun getDeviceFlipFlow(): Flow<Unit>
}

View file

@ -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
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
}

View file

@ -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)
} }

View file

@ -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)

View file

@ -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,
) )

View file

@ -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"

View file

@ -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(

View file

@ -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(

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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 -> {

View file

@ -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
}
} }

View file

@ -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 = "***"

View file

@ -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()

View file

@ -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)

View file

@ -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 = {},

View file

@ -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
}, },

View file

@ -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,

View file

@ -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 = "•••"
}
} }

View file

@ -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
}
}
}

View file

@ -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
/** /**

View file

@ -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,
) )

View file

@ -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,

View file

@ -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,
)
}
} }
} }

View file

@ -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) },
) )

View file

@ -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,

View file

@ -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()
}
}
}

View file

@ -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,
) )

View file

@ -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(

View file

@ -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,
)
}
}

View file

@ -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>) {

View file

@ -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")