diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 02a97fd3e3..41bba10c2e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,13 +22,13 @@ configurations { dependencies { implementation(files("libs/walletconnect-1.5.6.aar")) - implementation(project(":domain:legacy")) - implementation(project(":domain:models")) - implementation(project(":domain:core")) + implementation(projects.domain.legacy) + implementation(projects.domain.models) + implementation(projects.domain.core) implementation(projects.domain.card) implementation(projects.domain.demo) - implementation(project(":domain:wallets")) - implementation(project(":domain:wallets:models")) + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) implementation(projects.domain.settings) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) @@ -37,21 +37,24 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.appTheme) implementation(projects.domain.appTheme.models) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) - implementation(project(":common")) - implementation(project(":core:analytics")) + implementation(projects.common) + implementation(projects.core.analytics) implementation(projects.core.analytics.models) - implementation(project(":core:navigation")) - implementation(project(":core:featuretoggles")) - implementation(project(":core:res")) - implementation(project(":core:ui")) - implementation(project(":core:datasource")) - implementation(project(":core:utils")) - implementation(project(":libs:crypto")) - implementation(project(":libs:auth")) + implementation(projects.core.navigation) + implementation(projects.core.featuretoggles) + implementation(projects.core.res) + implementation(projects.core.ui) + implementation(projects.core.datasource) + implementation(projects.core.utils) + implementation(projects.libs.crypto) + implementation(projects.libs.auth) implementation(projects.data.appCurrency) implementation(projects.data.appTheme) + implementation(projects.data.balanceHiding) implementation(projects.data.card) implementation(projects.data.common) implementation(projects.data.settings) @@ -61,20 +64,20 @@ dependencies { implementation(projects.data.wallets) /** Features */ - implementation(project(":features:onboarding")) - implementation(project(":features:learn2earn:api")) - implementation(project(":features:learn2earn:impl")) - implementation(project(":features:referral:presentation")) - implementation(project(":features:referral:domain")) - implementation(project(":features:referral:data")) - implementation(project(":features:swap:api")) - implementation(project(":features:swap:presentation")) - implementation(project(":features:swap:domain")) - implementation(project(":features:swap:data")) - implementation(project(":features:tester:api")) - implementation(project(":features:tester:impl")) - implementation(project(":features:wallet:api")) - implementation(project(":features:wallet:impl")) + implementation(projects.features.onboarding) + implementation(projects.features.learn2earn.api) + implementation(projects.features.learn2earn.impl) + implementation(projects.features.referral.presentation) + implementation(projects.features.referral.domain) + implementation(projects.features.referral.data) + implementation(projects.features.swap.api) + implementation(projects.features.swap.presentation) + implementation(projects.features.swap.domain) + implementation(projects.features.swap.data) + implementation(projects.features.tester.api) + implementation(projects.features.tester.impl) + implementation(projects.features.wallet.api) + implementation(projects.features.wallet.impl) implementation(projects.features.tokendetails.api) implementation(projects.features.tokendetails.impl) diff --git a/app/src/main/java/com/tangem/tap/DeviceFlipDetector.kt b/app/src/main/java/com/tangem/tap/DeviceFlipDetector.kt deleted file mode 100644 index fe0dc2f8fc..0000000000 --- a/app/src/main/java/com/tangem/tap/DeviceFlipDetector.kt +++ /dev/null @@ -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 = 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) } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 445d082721..b02a66920f 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -24,6 +24,7 @@ import com.tangem.datasource.config.models.Config import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository +import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.LogConfig import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -178,6 +179,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var appThemeModeRepository: AppThemeModeRepository + @Inject + lateinit var balanceHidingRepository: BalanceHidingRepository + @Inject lateinit var detailsFeatureToggles: DetailsFeatureToggles @@ -207,6 +211,7 @@ class TapApplication : Application(), ImageLoaderFactory { appStateHolder = appStateHolder, currenciesRepository = currenciesRepository, appThemeModeRepository = appThemeModeRepository, + balanceHidingRepository = balanceHidingRepository, detailsFeatureToggles = detailsFeatureToggles, ), ), diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index eaf0da329c..a25d200f68 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -1,8 +1,10 @@ package com.tangem.tap.di.domain -import com.tangem.domain.settings.CanUseBiometryUseCase -import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase -import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase +import com.tangem.domain.balancehiding.DeviceFlipDetector +import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase +import com.tangem.domain.balancehiding.ListenToFlipsUseCase +import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository +import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository @@ -37,4 +39,24 @@ internal object SettingsDomainModule { 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, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index b9be23ef7e..4bef087c68 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -86,6 +86,10 @@ sealed class DetailsAction : Action { val appThemeMode: AppThemeMode, ) : AppSettings() + data class ChangeBalanceHiding( + val hideBalance: Boolean, + ) : AppSettings() + data class ChangeAppCurrency( val fiatCurrency: FiatCurrency, ) : AppSettings() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 11c2dd536a..1d0b13e4e3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -15,6 +15,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference 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.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse @@ -207,6 +208,9 @@ class DetailsMiddleware { is DetailsAction.AppSettings.ChangeAppThemeMode -> { changeAppThemeMode(action.appThemeMode) } + is DetailsAction.AppSettings.ChangeBalanceHiding -> { + changeBalanceHiding(action.hideBalance) + } is DetailsAction.AppSettings.ChangeAppCurrency -> { store.dispatch(GlobalAction.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 { // Nothing to change if (preferencesStorage.shouldSaveUserWallets == enable) { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index ce36eb806f..6c464d6275 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -8,6 +8,7 @@ import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.runBlocking import org.rekotlin.Action import java.util.EnumSet @@ -73,6 +74,10 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, selectedFiatCurrency = store.state.globalState.appCurrency, selectedThemeMode = store.state.globalState.appThemeMode, + isHidingEnabled = runBlocking { + store.state.daggerGraphState + .get { balanceHidingRepository }.getBalanceHidingSettings().isHidingEnabledInSettings + }, darkThemeSwitchEnabled = action.darkThemeSwitchEnabled, ), ) @@ -222,6 +227,11 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail selectedFiatCurrency = action.fiatCurrency, ), ) + is DetailsAction.AppSettings.ChangeBalanceHiding -> state.copy( + appSettingsState = state.appSettingsState.copy( + isHidingEnabled = action.hideBalance, + ), + ) is DetailsAction.AppSettings.EnrollBiometrics, is DetailsAction.AppSettings.CheckBiometricsStatus, -> state diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index dc93a0e653..68c663534e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -57,6 +57,7 @@ data class AppSettingsState( val saveAccessCodes: Boolean = false, val isBiometricsAvailable: Boolean = false, val needEnrollBiometrics: Boolean = false, + val isHidingEnabled: Boolean = false, val isInProgress: Boolean = false, val selectedFiatCurrency: FiatCurrency = FiatCurrency.Default, val selectedThemeMode: AppThemeMode = AppThemeMode.DEFAULT, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsItemsFactory.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsItemsFactory.kt index 783325f318..0d1f66f8bb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsItemsFactory.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsItemsFactory.kt @@ -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 { return Item.Button( id = "select_app_currency_button", diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 314d4634b3..7c99ed8b24 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -97,6 +97,7 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide itemsFactory.createSelectAppCurrencyButton(currentAppCurrencyName = "US Dollar") {}, itemsFactory.createSaveWalletsSwitch(isChecked = true, isEnabled = true, { _ -> }), itemsFactory.createSaveAccessCodeSwitch(isChecked = false, isEnabled = true) { _ -> }, + itemsFactory.createFlipToHideBalanceSwitch(isChecked = false, isEnabled = true) { _ -> }, itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}), ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt index ae0bc13cb3..c76eaafa65 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt @@ -99,6 +99,16 @@ internal class AppSettingsViewModel( ).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) { itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) { showThemeModeSelector(state.selectedThemeMode) @@ -178,6 +188,10 @@ internal class AppSettingsViewModel( store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting)) } + private fun onFlipToHideBalanceToggled(enable: Boolean) { + store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(hideBalance = enable)) + } + private fun dismissDialog() { updateContentState { copy(dialog = null) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 4c9eaf36a5..094136bb80 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -102,6 +102,7 @@ sealed class AmountAction : SendScreenAction { data class SetAmount(val amountCrypto: BigDecimal, val isUserInput: Boolean) : AmountAction() data class SetAmountError(val error: TapError?) : AmountAction() data class SetDecimalSeparator(val separator: String) : AmountAction() + data class HideBalance(val hide: Boolean) : AmountAction() } // Fee diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt index 9221d5ef60..fd1fe275e7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.send.redux.reducers +import com.tangem.common.Strings.STARS import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.features.send.redux.AmountAction @@ -38,8 +39,8 @@ class AmountReducer : SendInternalReducer { val viewValue = state.restoreDecimalSeparator(fiatToSend.stripZeroPlainString()) state.copy( viewAmountValue = InputViewValue(viewValue), - viewBalanceValue = rescaledBalance.stripZeroPlainString(), - mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), + viewBalanceValue = if (state.hideBalance) STARS else rescaledBalance.stripZeroPlainString(), + mainCurrency = state.createMainCurrency(currency, true), maxLengthOfAmount = sendState.getDecimals(currency), cursorAtTheSamePosition = false, ) @@ -80,6 +81,14 @@ class AmountReducer : SendInternalReducer { } is AmountAction.SetAmountError -> state.copy(error = action.error) 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) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 8400f07c87..fa9aa1d60e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -130,6 +130,7 @@ data class AmountState( val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, FiatCurrency.Default.code), val amountToSendCrypto: BigDecimal = BigDecimal.ZERO, val balanceCrypto: BigDecimal = BigDecimal.ZERO, + val hideBalance: Boolean = false, val cursorAtTheSamePosition: Boolean = true, val maxLengthOfAmount: Int = 2, val decimalSeparator: String = ".", diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 0e963d3ac5..5da7c2fe6a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -11,6 +11,7 @@ import android.view.inputmethod.EditorInfo import android.widget.EditText import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding @@ -45,6 +46,7 @@ import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.* @@ -56,8 +58,11 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L [REDACTED_AUTHOR] */ @OptIn(FlowPreview::class) +@AndroidEntryPoint class SendFragment : BaseStoreFragment(R.layout.fragment_send) { + private val viewModel by viewModels() + lateinit var sendBtn: ViewStateWidget private lateinit var etAmountToSend: TextInputEditText @@ -70,6 +75,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + lifecycle.addObserver(viewModel) Analytics.send(Token.Send.ScreenOpened()) } @@ -348,6 +354,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { override fun onDestroy() { store.dispatch(ReleaseSendState) + lifecycle.removeObserver(viewModel) super.onDestroy() } } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt new file mode 100644 index 0000000000..1ac28bda46 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 215e6e22de..cd68dae6e3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository 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.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -41,6 +42,7 @@ data class DaggerGraphState( val walletManagersFacade: WalletManagersFacade? = null, val appStateHolder: AppStateHolder? = null, val appThemeModeRepository: AppThemeModeRepository? = null, + val balanceHidingRepository: BalanceHidingRepository? = null, val detailsFeatureToggles: DetailsFeatureToggles? = null, // FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList diff --git a/common/src/main/java/com/tangem/common/Strings.kt b/common/src/main/java/com/tangem/common/Strings.kt new file mode 100644 index 0000000000..402f3daa07 --- /dev/null +++ b/common/src/main/java/com/tangem/common/Strings.kt @@ -0,0 +1,6 @@ +package com.tangem.common + +object Strings { + + const val STARS = "***" +} \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 3ab87d91ac..79b6816470 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(projects.domain.appTheme.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.balanceHiding.models) /** Tangem libraries */ implementation(deps.tangem.blockchain) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt new file mode 100644 index 0000000000..3cb897a49f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/HiddenBalanceDataModule.kt @@ -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), + ), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/BalanceHidingSettingsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/BalanceHidingSettingsStore.kt new file mode 100644 index 0000000000..a29714251b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/BalanceHidingSettingsStore.kt @@ -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 + + suspend fun getSyncOrDefault(): BalanceHidingSettings + + suspend fun store(settings: BalanceHidingSettings) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/BalanceStateHidingSettingsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/BalanceStateHidingSettingsStore.kt new file mode 100644 index 0000000000..740005d56b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/BalanceStateHidingSettingsStore.kt @@ -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, +) : BalanceHidingSettingsStore, KeylessDataStoreDecorator(dataStore) { + + override suspend fun getSyncOrDefault(): BalanceHidingSettings { + return getSyncOrNull() ?: BalanceHidingSettings( + isHidingEnabledInSettings = false, + isBalanceHidden = false, + ) + } +} \ No newline at end of file diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6e8c21c2a4..e869ddb5a2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -159,6 +159,8 @@ Card ID Link More Cards App Currency + Flip-to-Hide Balances + Flip your device screen down to quickly hide and show balances Issuer Send Feedback Signed diff --git a/core/ui/src/main/res/drawable/ic_eye_off_24.xml b/core/ui/src/main/res/drawable/ic_eye_off_24.xml deleted file mode 100644 index 7454717d84..0000000000 --- a/core/ui/src/main/res/drawable/ic_eye_off_24.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/data/balance-hiding/.gitignore b/data/balance-hiding/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/data/balance-hiding/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/balance-hiding/build.gradle.kts b/data/balance-hiding/build.gradle.kts new file mode 100644 index 0000000000..434d3e0a34 --- /dev/null +++ b/data/balance-hiding/build.gradle.kts @@ -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) +} diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt new file mode 100644 index 0000000000..879bef5f17 --- /dev/null +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt @@ -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 { + 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() + } + } +} \ No newline at end of file diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt new file mode 100644 index 0000000000..30b2e97726 --- /dev/null +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt @@ -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 = callbackFlow { + val listener = FlipListener { trySend(Unit) } + + gravitySensor?.let { + sensorManager.registerListener(listener, it, SensorManager.SENSOR_DELAY_NORMAL) + } + + awaitClose { sensorManager.unregisterListener(listener) } + } +} \ No newline at end of file diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/FlipListener.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/FlipListener.kt new file mode 100644 index 0000000000..b535797121 --- /dev/null +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/FlipListener.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt new file mode 100644 index 0000000000..86b293b9fe --- /dev/null +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/data/settings/build.gradle.kts b/data/settings/build.gradle.kts index 1413053d65..e59302acd9 100644 --- a/data/settings/build.gradle.kts +++ b/data/settings/build.gradle.kts @@ -14,13 +14,16 @@ 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.settings) + implementation(projects.domain.balanceHiding.models) implementation(projects.data.source.preferences) } diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index 6ef7e9a280..6d0736bdf1 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -20,6 +20,9 @@ internal object SettingsDataModule { preferencesDataSource: PreferencesDataSource, dispatchers: CoroutineDispatcherProvider, ): SettingsRepository { - return DefaultSettingsRepository(preferencesDataSource = preferencesDataSource, dispatchers = dispatchers) + return DefaultSettingsRepository( + preferencesDataSource = preferencesDataSource, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/domain/balance-hiding/.gitignore b/domain/balance-hiding/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/domain/balance-hiding/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/balance-hiding/build.gradle.kts b/domain/balance-hiding/build.gradle.kts new file mode 100644 index 0000000000..d9554a9615 --- /dev/null +++ b/domain/balance-hiding/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/domain/balance-hiding/models/.gitignore b/domain/balance-hiding/models/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/balance-hiding/models/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/balance-hiding/models/build.gradle.kts b/domain/balance-hiding/models/build.gradle.kts new file mode 100644 index 0000000000..7ff7fb7522 --- /dev/null +++ b/domain/balance-hiding/models/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} \ No newline at end of file diff --git a/domain/balance-hiding/models/src/main/kotlin/com/tangem/domain/balancehiding/BalanceHidingSettings.kt b/domain/balance-hiding/models/src/main/kotlin/com/tangem/domain/balancehiding/BalanceHidingSettings.kt new file mode 100644 index 0000000000..4aac92cad2 --- /dev/null +++ b/domain/balance-hiding/models/src/main/kotlin/com/tangem/domain/balancehiding/BalanceHidingSettings.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.balancehiding + +data class BalanceHidingSettings( + val isHidingEnabledInSettings: Boolean, + val isBalanceHidden: Boolean, +) \ No newline at end of file diff --git a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/DeviceFlipDetector.kt b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/DeviceFlipDetector.kt new file mode 100644 index 0000000000..588e4a06e3 --- /dev/null +++ b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/DeviceFlipDetector.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.balancehiding + +import kotlinx.coroutines.flow.Flow + +interface DeviceFlipDetector { + + fun getDeviceFlipFlow(): Flow +} \ No newline at end of file diff --git a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/IsBalanceHiddenUseCase.kt b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/IsBalanceHiddenUseCase.kt new file mode 100644 index 0000000000..9d22b8d762 --- /dev/null +++ b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/IsBalanceHiddenUseCase.kt @@ -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 { + return balanceHidingRepository.getBalanceHidingSettingsFlow().map { + it.isBalanceHidden + } + } +} \ No newline at end of file diff --git a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/ListenToFlipsUseCase.kt b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/ListenToFlipsUseCase.kt new file mode 100644 index 0000000000..f4c4eb1562 --- /dev/null +++ b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/ListenToFlipsUseCase.kt @@ -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 { + return if (balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings) { + flipDetector.getDeviceFlipFlow().onEach { + val balanceHidingSettings = balanceHidingRepository.getBalanceHidingSettings() + + balanceHidingRepository.storeBalanceHidingSettings( + balanceHidingSettings.copy( + isBalanceHidden = !balanceHidingSettings.isBalanceHidden, + ), + ) + } + } else { + emptyFlow() + } + } +} \ No newline at end of file diff --git a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/repositories/BalanceHidingRepository.kt b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/repositories/BalanceHidingRepository.kt new file mode 100644 index 0000000000..84161a07d9 --- /dev/null +++ b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/repositories/BalanceHidingRepository.kt @@ -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 + + suspend fun storeBalanceHidingSettings(isBalanceHidden: BalanceHidingSettings) + + suspend fun getBalanceHidingSettings(): BalanceHidingSettings +} \ No newline at end of file diff --git a/domain/settings/build.gradle.kts b/domain/settings/build.gradle.kts index 7ff7fb7522..00cd0a439e 100644 --- a/domain/settings/build.gradle.kts +++ b/domain/settings/build.gradle.kts @@ -1,4 +1,9 @@ plugins { alias(deps.plugins.kotlin.jvm) id("configuration") +} + +dependencies { + implementation(deps.kotlin.coroutines) + implementation(projects.domain.balanceHiding.models) } \ No newline at end of file diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 8248b21113..65614c399d 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -9,11 +9,15 @@ plugins { dependencies { /** Core modules */ - implementation(project(":core:analytics")) + implementation(projects.core.analytics) implementation(projects.core.analytics.models) - implementation(project(":core:featuretoggles")) - implementation(project(":core:utils")) - implementation(project(":core:ui")) + implementation(projects.core.featuretoggles) + implementation(projects.core.utils) + implementation(projects.core.ui) + implementation(projects.common) + + /** Domain modules **/ + implementation(projects.domain.balanceHiding) /** AndroidX */ implementation(deps.androidx.activity.compose) @@ -30,10 +34,11 @@ dependencies { implementation(deps.compose.constraintLayout) /** Api */ - implementation(project(":features:swap:api")) + implementation(projects.features.swap.api) /** Domain */ - implementation(project(":features:swap:domain")) + implementation(projects.features.swap.domain) + implementation(projects.domain.settings) /** Other libraries */ implementation(deps.compose.shimmer) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index a7ea53771e..73ec9cd110 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -41,6 +41,7 @@ data class SwapCardData( val tokenIconUrl: String, val tokenCurrency: String, val balance: String, + val isBalanceHidden: Boolean, val isNotNativeToken: Boolean, val canSelectAnotherToken: Boolean = false, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index d2d2622426..cc92e13246 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -24,6 +24,11 @@ class SwapFragment : Fragment() { private val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycle.addObserver(viewModel) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { viewModel.setRouter( SwapRouter( @@ -70,6 +75,11 @@ class SwapFragment : Fragment() { } } + override fun onDestroy() { + lifecycle.removeObserver(viewModel) + super.onDestroy() + } + companion object { const val CURRENCY_BUNDLE_KEY = "swap_currency" const val DERIVATION_PATH = "DERIVATION_STYLE" diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 00e8323623..9324dc4d15 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -2,6 +2,7 @@ package com.tangem.feature.swap.ui import androidx.compose.ui.text.TextRange 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.SelectableItemsState import com.tangem.core.ui.extensions.TextReference @@ -21,7 +22,7 @@ import kotlinx.collections.immutable.toImmutableList * State builder creates a specific states for SwapScreen */ @Suppress("LargeClass") -internal class StateBuilder(val actions: UiActions) { +internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: Provider) { private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected) @@ -39,6 +40,7 @@ internal class StateBuilder(val actions: UiActions) { canSelectAnotherToken = false, isNotNativeToken = initialCurrency.isNonNative(), balance = "", + isBalanceHidden = true, ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -50,6 +52,7 @@ internal class StateBuilder(val actions: UiActions) { balance = "", isNotNativeToken = false, coinId = null, + isBalanceHidden = true, ), fee = FeeState.Loading, networkCurrency = networkInfo.blockchainCurrency, @@ -84,6 +87,7 @@ internal class StateBuilder(val actions: UiActions) { isNotNativeToken = fromToken.isNonNative(), canSelectAnotherToken = canSelectSendToken, balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "", + isBalanceHidden = isBalanceHiddenProvider(), ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -95,6 +99,7 @@ internal class StateBuilder(val actions: UiActions) { isNotNativeToken = toToken.isNonNative(), canSelectAnotherToken = canSelectReceiveToken, balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "", + isBalanceHidden = isBalanceHiddenProvider(), ), fee = FeeState.Loading, swapButton = SwapButton(enabled = false, loading = true, onClick = {}), @@ -144,6 +149,7 @@ internal class StateBuilder(val actions: UiActions) { tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, balance = quoteModel.fromTokenInfo.tokenWalletBalance, + isBalanceHidden = isBalanceHiddenProvider(), ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -155,6 +161,7 @@ internal class StateBuilder(val actions: UiActions) { tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken, balance = quoteModel.toTokenInfo.tokenWalletBalance, + isBalanceHidden = isBalanceHiddenProvider(), ), networkCurrency = quoteModel.networkCurrency, warnings = warnings, @@ -192,6 +199,7 @@ internal class StateBuilder(val actions: UiActions) { tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, balance = emptyAmountState.fromTokenWalletBalance, + isBalanceHidden = isBalanceHiddenProvider(), ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -203,6 +211,7 @@ internal class StateBuilder(val actions: UiActions) { tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken, balance = emptyAmountState.toTokenWalletBalance, + isBalanceHidden = isBalanceHiddenProvider(), ), warnings = emptyList(), 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 { return if (uiState.permissionState is SwapPermissionState.ReadyForRequest) { uiState.copy( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 4059a1157d..c18c6a8889 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout +import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.states.Item @@ -143,7 +144,11 @@ private fun MainInfo(state: SwapStateHolder) { val priceImpactWarning = state.warnings.filterIsInstance().firstOrNull() TransactionCard( type = state.sendCardData.type, - balance = state.sendCardData.balance, + balance = if (state.sendCardData.isBalanceHidden) { + STARS + } else { + state.sendCardData.balance + }, textFieldValue = state.sendCardData.amountTextFieldValue, amountEquivalent = state.sendCardData.amountEquivalent, tokenIconUrl = state.sendCardData.tokenIconUrl, @@ -161,7 +166,7 @@ private fun MainInfo(state: SwapStateHolder) { val marginCard = TangemTheme.dimens.spacing16 TransactionCard( type = state.receiveCardData.type, - balance = state.receiveCardData.balance, + balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance, textFieldValue = state.receiveCardData.amountTextFieldValue, amountEquivalent = state.receiveCardData.amountEquivalent, tokenIconUrl = state.receiveCardData.tokenIconUrl, @@ -371,6 +376,7 @@ private val sendCard = SwapCardData( canSelectAnotherToken = false, balance = "123", coinId = "", + isBalanceHidden = false, ) private val receiveCard = SwapCardData( @@ -383,6 +389,7 @@ private val receiveCard = SwapCardData( canSelectAnotherToken = true, balance = "33333", coinId = "", + isBalanceHidden = false, ) val stateSelectable = SelectableItemsState( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index e7a96cac0c..2379a8438d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -3,11 +3,12 @@ package com.tangem.feature.swap.viewmodels import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* +import com.tangem.common.Provider import com.tangem.core.analytics.api.AnalyticsEventHandler 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.domain.BlockchainInteractor 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.runCatching import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import kotlinx.serialization.decodeFromString +import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import timber.log.Timber import java.text.DecimalFormat @@ -37,15 +39,17 @@ import java.util.* import javax.inject.Inject import kotlin.properties.Delegates -@Suppress("LargeClass") +@Suppress("LargeClass", "LongParameterList") @HiltViewModel internal class SwapViewModel @Inject constructor( private val swapInteractor: SwapInteractor, private val blockchainInteractor: BlockchainInteractor, private val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, + private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase, + private val listenToFlipsUseCase: ListenToFlipsUseCase, savedStateHandle: SavedStateHandle, -) : ViewModel() { +) : ViewModel(), DefaultLifecycleObserver { private val currency = Json.decodeFromString( savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] @@ -53,15 +57,20 @@ internal class SwapViewModel @Inject constructor( ) private val derivationPath = savedStateHandle.get(SwapFragment.DERIVATION_PATH) + private var isBalanceHidden = true + private val stateBuilder = StateBuilder( actions = createUiActions(), + isBalanceHiddenProvider = Provider { isBalanceHidden }, ) + private val inputNumberFormatter = InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat) private val amountDebouncer = Debouncer() private val singleTaskScheduler = SingleTaskScheduler() private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId)) + var uiState: SwapStateHolder by mutableStateOf( stateBuilder.createInitialLoadingState( initialCurrency = currency, @@ -82,6 +91,24 @@ internal class SwapViewModel @Inject constructor( 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() { singleTaskScheduler.cancelTask() super.onCleared() diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 7f3fcd1357..704e1f8bc9 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -53,12 +53,14 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.legacy) implementation(projects.domain.models) + implementation(projects.domain.settings) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory) implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.balanceHiding) /** Feature Apis */ implementation(projects.features.tokendetails.api) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 14e909c9c5..2ff8ce19a2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -67,6 +67,7 @@ internal object TokenDetailsPreviewData { actionButtons = actionButtons, fiatBalance = "123,00$", cryptoBalance = "866,96 USDT", + isBalanceHidden = false, ) val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = actionButtons) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt index 2f066dae01..9bcf4d84c3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -15,6 +15,7 @@ internal sealed class TokenDetailsBalanceBlockState { override val actionButtons: ImmutableList, val fiatBalance: String, val cryptoBalance: String, + val isBalanceHidden: Boolean, ) : TokenDetailsBalanceBlockState() data class Error( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 2199b485c3..95fa7c04c5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -18,6 +18,7 @@ import java.math.BigDecimal internal class TokenDetailsLoadedBalanceConverter( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, + private val isBalanceHiddenProvider: Provider, private val symbol: String, private val decimals: Int, ) : Converter, TokenDetailsState> { @@ -57,6 +58,7 @@ internal class TokenDetailsLoadedBalanceConverter( actionButtons = currentState.actionButtons, fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()), cryptoBalance = formatCryptoAmount(status), + isBalanceHidden = isBalanceHiddenProvider(), ) } is CryptoCurrencyStatus.Loading -> { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 7d29d64f91..c381e21449 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -15,6 +15,7 @@ import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError 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.components.TokenDetailsDialogConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter @@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.Flow internal class TokenDetailsStateFactory( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, + private val isBalanceHiddenProvider: Provider, private val clickIntents: TokenDetailsClickIntents, symbol: String, decimals: Int, @@ -38,6 +40,7 @@ internal class TokenDetailsStateFactory( TokenDetailsLoadedBalanceConverter( currentStateProvider = currentStateProvider, appCurrencyProvider = appCurrencyProvider, + isBalanceHiddenProvider = isBalanceHiddenProvider, symbol = symbol, decimals = decimals, ) @@ -162,4 +165,16 @@ internal class TokenDetailsStateFactory( 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 + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index b1b9ac832a..9e2b59dc9e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -72,7 +72,7 @@ private fun FiatBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier ) is TokenDetailsBalanceBlockState.Content -> Text( modifier = modifier, - text = state.fiatBalance, + text = if (state.isBalanceHidden) DOTS else state.fiatBalance, style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, ) @@ -96,7 +96,7 @@ private fun CryptoBalance(state: TokenDetailsBalanceBlockState, modifier: Modifi ) is TokenDetailsBalanceBlockState.Content -> Text( modifier = modifier, - text = state.cryptoBalance, + text = if (state.isBalanceHidden) DOTS else state.cryptoBalance, style = TangemTheme.typography.caption, color = TangemTheme.colors.text.primary1, ) @@ -135,4 +135,6 @@ private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameter TokenDetailsPreviewData.balanceContent, TokenDetailsPreviewData.balanceError, ), -) \ No newline at end of file +) + +const val DOTS = "***" \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index d6256f8a67..e08a08f68c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -9,6 +9,8 @@ import arrow.core.getOrElse import com.tangem.common.Provider import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase 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.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction @@ -49,6 +51,8 @@ internal class TokenDetailsViewModel @Inject constructor( private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, + private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase, + private val listenToFlipsUseCase: ListenToFlipsUseCase, private val walletManagersFacade: WalletManagersFacade, private val reduxStateHolder: ReduxStateHolder, savedStateHandle: SavedStateHandle, @@ -65,19 +69,24 @@ internal class TokenDetailsViewModel @Inject constructor( private var wallet by Delegates.notNull() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() + private var isBalanceHidden = true + private val stateFactory = TokenDetailsStateFactory( currentStateProvider = Provider { uiState }, appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), + isBalanceHiddenProvider = Provider { isBalanceHidden }, clickIntents = this, symbol = cryptoCurrency.symbol, decimals = cryptoCurrency.decimals, ) + var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency)) private set override fun onCreate(owner: LifecycleOwner) { getWallet() updateContent(selectedWallet = wallet) + handleBalanceHiding(owner) } private fun getWallet() { @@ -93,6 +102,22 @@ internal class TokenDetailsViewModel @Inject constructor( 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) { getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, cryptoCurrencyStatus = currencyStatus) .distinctUntilChanged() diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 3f75bd4fc9..8de129fa16 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { implementation(projects.domain.wallets.models) implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) + implementation(projects.domain.balanceHiding) /** Feature Apis */ implementation(projects.features.wallet.api) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index 550d23bc4a..27df264a07 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -63,6 +63,8 @@ internal object WalletPreviewData { imageResId = R.drawable.ill_businessman_3d, onRenameClick = { _, _ -> }, onDeleteClick = {}, + balance = "8923,05 $", + additionalInfo = TextReference.Str("3 cards • Seed phrase"), ) } @@ -125,12 +127,13 @@ internal object WalletPreviewData { name = "Polygon", amount = "5,412 MATIC", hasPending = true, - tokenOptions = TokenOptionsState.Visible( + tokenOptions = TokenOptionsState( fiatAmount = "321 $", config = PriceChangeConfig( valueInPercent = "2%", type = PriceChangeConfig.Type.UP, ), + isBalanceHidden = false, ), onItemClick = {}, onItemLongClick = {}, @@ -151,11 +154,14 @@ internal object WalletPreviewData { name = "Polygon", amount = "5,412 MATIC", hasPending = true, - tokenOptions = TokenOptionsState.Hidden( + tokenOptions = TokenOptionsState( config = PriceChangeConfig( valueInPercent = "2%", type = PriceChangeConfig.Type.UP, ), + fiatAmount = "321 $", + isBalanceHidden = false, + ), onItemClick = {}, onItemLongClick = {}, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt index f7dd28c4b6..f20a71a201 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt @@ -9,14 +9,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.res.painterResource +import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTypography 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.Companion.DOTS -import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState @Composable internal fun TokenCryptoInfoBlock(state: TokenItemState, modifier: Modifier = Modifier) { @@ -40,7 +39,7 @@ private fun ContentBlock(state: TokenItemState.ContentState, modifier: Modifier AmountText( 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.Unreachable -> null }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenFiatInfoBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenFiatInfoBlock.kt index d245ea512d..8c9f6db9fa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenFiatInfoBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenFiatInfoBlock.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.res.painterResource 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.SpacerW4 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.feature.wallet.impl.R 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 org.burnoutcrew.reorderable.ReorderableLazyListState import org.burnoutcrew.reorderable.detectReorder @@ -56,9 +56,10 @@ private fun ContentBlock(state: TokenOptionsState, modifier: Modifier = Modifier modifier = Modifier.align(Alignment.End), ) { Text( - text = when (it) { - is TokenOptionsState.Visible -> it.fiatAmount - is TokenOptionsState.Hidden -> DOTS + text = if (it.isBalanceHidden) { + STARS + } else { + it.fiatAmount }, style = TangemTypography.body2, color = TangemTheme.colors.text.primary1, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index 0d7f76e222..70e14a9b86 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -146,30 +146,9 @@ internal sealed interface TokenItemState { /** Token options state */ @Immutable - sealed interface TokenOptionsState { - - val config: PriceChangeConfig - - /** - * 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 = "•••" - } + data class TokenOptionsState( + val config: PriceChangeConfig, + val fiatAmount: String, + val isBalanceHidden: Boolean, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/TokenItemHiddenStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/TokenItemHiddenStateConverter.kt new file mode 100644 index 0000000000..91e8124c43 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/TokenItemHiddenStateConverter.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt index 6c4f9e181e..c9830c2a7b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt @@ -49,11 +49,13 @@ internal sealed interface WalletCardState { /** * Wallet card hidden content state * - * @property id wallet id - * @property title wallet name - * @property imageResId wallet image resource id - * @property onRenameClick lambda be invoked when Rename button is clicked - * @property onDeleteClick lambda be invoked when Delete button is clicked + * @property id wallet id + * @property title wallet name + * @property imageResId wallet image resource id + * @property onRenameClick lambda be invoked when Rename 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( override val id: UserWalletId, @@ -61,6 +63,8 @@ internal sealed interface WalletCardState { override val imageResId: Int?, override val onRenameClick: (UserWalletId, String) -> Unit, override val onDeleteClick: (UserWalletId) -> Unit, + val additionalInfo: TextReference, + val balance: String, ) : WalletCardState /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt index 4571d10d19..1f6d2ceff0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt @@ -24,11 +24,13 @@ import com.tangem.utils.converter.Converter * [REDACTED_AUTHOR] */ +@Suppress("LongParameterList") internal class WalletLoadedTokensListConverter( private val currentStateProvider: Provider, private val tokenListErrorConverter: TokenListErrorConverter, appCurrencyProvider: Provider, currentWalletProvider: Provider, + isBalanceHiddenProvider: Provider, clickIntents: WalletClickIntents, ) : Converter, WalletState> { @@ -36,7 +38,7 @@ internal class WalletLoadedTokensListConverter( currentStateProvider = currentStateProvider, currentWalletProvider = currentWalletProvider, appCurrencyProvider = appCurrencyProvider, - isWalletContentHidden = false, // TODO: [REDACTED_JIRA] + isBalanceHiddenProvider = isBalanceHiddenProvider, clickIntents = clickIntents, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt index 22a3e6872f..7d909ffbde 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -18,11 +18,16 @@ import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError import com.tangem.domain.txhistory.models.TxHistoryStateError 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.components.WalletNotification 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.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.viewmodels.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver @@ -44,6 +49,7 @@ internal class WalletStateFactory( private val currentCardTypeResolverProvider: Provider, private val currentWalletProvider: Provider, private val appCurrencyProvider: Provider, + private val isBalanceHiddenProvider: Provider, private val clickIntents: WalletClickIntents, ) { @@ -57,6 +63,8 @@ internal class WalletStateFactory( private val walletDeleteStateConverter by lazy { WalletDeleteStateConverter(currentStateProvider) } + private val hiddenStateConverter by lazy { HiddenStateConverter(currentStateProvider) } + private val tokenListErrorConverter by lazy { TokenListErrorConverter(currentStateProvider) } @@ -69,6 +77,7 @@ internal class WalletStateFactory( tokenListErrorConverter = tokenListErrorConverter, appCurrencyProvider = appCurrencyProvider, currentWalletProvider = currentWalletProvider, + isBalanceHiddenProvider = isBalanceHiddenProvider, clickIntents = clickIntents, ) } @@ -242,6 +251,10 @@ internal class WalletStateFactory( return currencyStatusErrorConverter.convert(error) } + fun getHiddenBalanceState(isBalanceHidden: Boolean): WalletState { + return hiddenStateConverter.convert(isBalanceHidden) + } + fun getStateAndTriggerEvent( state: WalletState, event: WalletEvent, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 86273d595e..d1c6cfeb73 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -241,15 +241,6 @@ private fun Title(state: WalletCardState, modifier: Modifier = Modifier) { horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), ) { 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, - ) - } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt index ebd6ad5e7e..5a7fc06b24 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt @@ -13,7 +13,7 @@ import java.math.BigDecimal internal class CryptoCurrencyStatusToTokenItemConverter( private val appCurrencyProvider: Provider, - private val isWalletContentHidden: Boolean, + private val isBalanceHiddenProvider: Provider, private val clickIntents: WalletClickIntents, ) : Converter { @@ -42,14 +42,11 @@ internal class CryptoCurrencyStatusToTokenItemConverter( icon = iconStateConverter.convert(value = this), amount = getFormattedAmount(), hasPending = value.hasCurrentNetworkTransactions, - tokenOptions = if (isWalletContentHidden) { - TokenItemState.TokenOptionsState.Hidden(getPriceChangeConfig()) - } else { - TokenItemState.TokenOptionsState.Visible( - fiatAmount = getFormattedFiatAmount(), - config = getPriceChangeConfig(), - ) - }, + tokenOptions = TokenItemState.TokenOptionsState( + fiatAmount = getFormattedFiatAmount(), + config = getPriceChangeConfig(), + isBalanceHidden = isBalanceHiddenProvider(), + ), onItemClick = { clickIntents.onTokenItemClick(currency) }, onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt index d72d10693d..89e3ff8be5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt @@ -13,7 +13,7 @@ internal class FiatBalanceToWalletCardConverter( private val currentState: WalletCardState, private val appCurrencyProvider: Provider, private val currentWalletProvider: Provider, - private val isWalletContentHidden: Boolean, + private val isBalanceHiddenProvider: Provider, ) : Converter { override fun convert(value: FiatBalance): WalletCardState { @@ -39,17 +39,23 @@ internal class FiatBalanceToWalletCardConverter( } private fun FiatBalance.Loaded.convertToWalletCardState(): WalletCardState { - return if (isWalletContentHidden) { + val appCurrency = appCurrencyProvider() + + return if (isBalanceHiddenProvider()) { WalletCardState.HiddenContent( id = currentState.id, title = currentState.title, imageResId = currentState.imageResId, onRenameClick = currentState.onRenameClick, onDeleteClick = currentState.onDeleteClick, + balance = formatFiatAmount( + fiatAmount = this.amount, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()), ) } else { - val appCurrency = appCurrencyProvider() - WalletCardState.Content( id = currentState.id, title = currentState.title, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt new file mode 100644 index 0000000000..a5f5ea9808 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt @@ -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, +) : Converter { + + 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() + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt index 8fb50ac24e..24d808bd72 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -17,12 +17,12 @@ import kotlinx.collections.immutable.persistentListOf internal class TokenListToContentItemsConverter( appCurrencyProvider: Provider, - isWalletContentHidden: Boolean, + isBalanceHiddenProvider: Provider, private val clickIntents: WalletClickIntents, ) : Converter { private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter( - isWalletContentHidden = isWalletContentHidden, + isBalanceHiddenProvider = isBalanceHiddenProvider, appCurrencyProvider = appCurrencyProvider, clickIntents = clickIntents, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt index bf97fcb508..eabeaf9611 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -17,12 +17,12 @@ internal class TokenListToWalletStateConverter( private val currentStateProvider: Provider, private val currentWalletProvider: Provider, private val appCurrencyProvider: Provider, - private val isWalletContentHidden: Boolean, + private val isBalanceHiddenProvider: Provider, clickIntents: WalletClickIntents, ) : Converter { private val tokenListToContentConverter = TokenListToContentItemsConverter( - isWalletContentHidden = isWalletContentHidden, + isBalanceHiddenProvider = isBalanceHiddenProvider, appCurrencyProvider = appCurrencyProvider, clickIntents = clickIntents, ) @@ -48,9 +48,9 @@ internal class TokenListToWalletStateConverter( val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex] val converter = FiatBalanceToWalletCardConverter( currentState = selectedWalletCard, - appCurrencyProvider = appCurrencyProvider, currentWalletProvider = currentWalletProvider, - isWalletContentHidden = isWalletContentHidden, + appCurrencyProvider = appCurrencyProvider, + isBalanceHiddenProvider = isBalanceHiddenProvider, ) return walletsListConfig.copy( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/WalletHiddenBalanceStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/WalletHiddenBalanceStateConverter.kt new file mode 100644 index 0000000000..7f29843cea --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/WalletHiddenBalanceStateConverter.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index d185f34bb4..e8ed3975b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -14,14 +14,14 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase 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.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.settings.CanUseBiometryUseCase -import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase -import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase +import com.tangem.domain.settings.* import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -93,6 +93,8 @@ internal class WalletViewModel @Inject constructor( private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, + private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase, + private val listenToFlipsUseCase: ListenToFlipsUseCase, private val walletManagersFacade: WalletManagersFacade, private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, @@ -104,6 +106,7 @@ internal class WalletViewModel @Inject constructor( var router: InnerWalletRouter by Delegates.notNull() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() + private var isBalanceHidden = true private val notificationsListFactory = WalletNotificationsListFactory( wasCardScannedCallback = getCardWasScannedUseCase::invoke, @@ -123,6 +126,7 @@ internal class WalletViewModel @Inject constructor( wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex] }, appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), + isBalanceHiddenProvider = Provider { isBalanceHidden }, clickIntents = this, ) @@ -161,6 +165,20 @@ internal class WalletViewModel @Inject constructor( .onEach(::updateWallets) .flowOn(dispatchers.io) .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) { diff --git a/settings.gradle.kts b/settings.gradle.kts index b2c3126321..00b22db5d1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -112,11 +112,15 @@ include(":domain:app-currency") include(":domain:app-currency:models") include(":domain:app-theme") include(":domain:app-theme:models") +include(":domain:balance-hiding") +include(":domain:balance-hiding:models") + // endregion Domain modules // region Data modules include(":data:app-currency") include(":data:app-theme") +include(":data:balance-hiding") include(":data:common") include(":data:card") include(":data:tokens")