From 72534e42f408c6534d25e683575cb0b13560545d Mon Sep 17 00:00:00 2001 From: Milan Cerovsky Date: Thu, 15 May 2025 11:49:18 +0200 Subject: [PATCH] Shielded address rotation --- .../cash/z/ecc/sdk/extension/ZatoshiExt.kt | 3 + .../co/electriccoin/zcash/di/UseCaseModule.kt | 4 + .../ui/common/datasource/AccountDataSource.kt | 252 ++++++++---------- .../usecase/NavigateToReceiveUseCase.kt | 22 ++ .../NavigateToRequestShieldedUseCase.kt | 18 ++ .../zcash/ui/screen/home/HomeViewModel.kt | 12 +- .../ui/screen/receive/view/ReceiveView.kt | 30 +-- .../receive/viewmodel/ReceiveViewModel.kt | 7 +- .../TransactionHistoryWidgetViewModel.kt | 7 +- 9 files changed, 184 insertions(+), 171 deletions(-) create mode 100644 ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToReceiveUseCase.kt create mode 100644 ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToRequestShieldedUseCase.kt diff --git a/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt index e5e1c9a84..c48a26299 100644 --- a/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt +++ b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt @@ -11,6 +11,9 @@ private const val DECIMALS_SHORT = 3 private const val MIN_ZATOSHI_FOR_DOTS_SHORT = Zatoshi.ZATOSHI_PER_ZEC / 1000 +val Zatoshi.Companion.ZERO: Zatoshi + get() = Zatoshi(0) + fun Zatoshi.toZecStringFull() = convertZatoshiToZecString( maxDecimals = DECIMALS_MAX_LONG, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt index 0683d4b01..0a69050c6 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt @@ -44,6 +44,8 @@ import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase +import co.electriccoin.zcash.ui.common.usecase.NavigateToReceiveUseCase +import co.electriccoin.zcash.ui.common.usecase.NavigateToRequestShieldedUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToWalletBackupUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase @@ -192,4 +194,6 @@ val useCaseModule = singleOf(::NavigateToErrorUseCase) factoryOf(::RescanQrUseCase) factoryOf(::ShieldFundsMessageUseCase) + factoryOf(::NavigateToReceiveUseCase) + factoryOf(::NavigateToRequestShieldedUseCase) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/datasource/AccountDataSource.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/datasource/AccountDataSource.kt index ad004bf0d..7f6cd5f74 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/datasource/AccountDataSource.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/datasource/AccountDataSource.kt @@ -1,14 +1,18 @@ package co.electriccoin.zcash.ui.common.datasource import android.content.Context +import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.AccountImportSetup import cash.z.ecc.android.sdk.model.AccountPurpose +import cash.z.ecc.android.sdk.model.AccountUuid +import cash.z.ecc.android.sdk.model.UnifiedAddressRequest import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.WalletAddress import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zip32AccountIndex +import cash.z.ecc.sdk.extension.ZERO import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.model.KeystoneAccount import co.electriccoin.zcash.ui.common.model.SaplingInfo @@ -18,12 +22,14 @@ import co.electriccoin.zcash.ui.common.model.WalletAccount import co.electriccoin.zcash.ui.common.model.ZashiAccount import co.electriccoin.zcash.ui.common.provider.SelectedAccountUUIDProvider import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider +import co.electriccoin.zcash.ui.design.util.combineToFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -34,8 +40,10 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.time.Duration.Companion.seconds @@ -61,6 +69,8 @@ interface AccountDataSource { seedFingerprint: String, index: Long ): Account + + suspend fun requestNextShieldedAddress() } class AccountDataSourceImpl( @@ -70,119 +80,54 @@ class AccountDataSourceImpl( ) : AccountDataSource { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val requestNextShieldedAddressPipeline = MutableSharedFlow() + @OptIn(ExperimentalCoroutinesApi::class) - private val internalAccounts: Flow?> = + override val allAccounts: StateFlow?> = synchronizerProvider .synchronizer .flatMapLatest { synchronizer -> synchronizer ?.accountsFlow - ?.map { accounts -> - accounts?.map { account -> - if (account.keySource == KEYSTONE_KEYSOURCE) { - InternalAccountWithAddresses( - sdkAccount = account, - unifiedAddress = - WalletAddress.Unified.new(synchronizer.getUnifiedAddress(account)), - saplingAddress = null, - transparentAddress = - WalletAddress.Transparent.new(synchronizer.getTransparentAddress(account)), - ) - } else { - InternalAccountWithAddresses( - sdkAccount = account, - unifiedAddress = - WalletAddress.Unified.new(synchronizer.getUnifiedAddress(account)), - saplingAddress = - WalletAddress.Sapling.new(synchronizer.getSaplingAddress(account)), - transparentAddress = - WalletAddress.Transparent.new(synchronizer.getTransparentAddress(account)), - ) - } - } - }?.flatMapLatest { accountsWithAddresses -> - if (accountsWithAddresses == null) { - flowOf(null) - } else { - synchronizer.walletBalances.map { walletBalances -> - if (walletBalances == null) { - null - } else { - accountsWithAddresses.map { accountWithAddresses -> - val balance = - walletBalances[accountWithAddresses.sdkAccount.accountUuid] + ?.flatMapLatest { allSdkAccounts -> + allSdkAccounts + ?.map { sdkAccount -> + combine( + observeUnified(synchronizer, sdkAccount), + observeTransparent(synchronizer, sdkAccount), + observeSapling(synchronizer, sdkAccount), + observeIsSelected(sdkAccount, allSdkAccounts), + ) { unified, transparent, sapling, isSelected -> + when (sdkAccount.keySource?.lowercase()) { + KEYSTONE_KEYSOURCE -> + KeystoneAccount( + sdkAccount = sdkAccount, + unified = unified, + transparent = transparent, + isSelected = isSelected, + ) - InternalAccountWithBalances( - sdkAccount = accountWithAddresses.sdkAccount, - unifiedAddress = accountWithAddresses.unifiedAddress, - saplingAddress = accountWithAddresses.saplingAddress, - transparentAddress = accountWithAddresses.transparentAddress, - orchardBalance = balance?.orchard ?: createEmptyWalletBalance(), - transparentBalance = balance?.unshielded ?: Zatoshi.ZERO, - saplingBalance = balance?.sapling, - ) + else -> + ZashiAccount( + sdkAccount = sdkAccount, + unified = unified, + transparent = transparent, + sapling = sapling!!, + isSelected = isSelected, + ) } } } - } - }?.retryWhen { _, attempt -> + ?.combineToFlow() ?: flowOf(null) + } + ?.retryWhen { _, attempt -> emit(null) delay(attempt.coerceAtMost(RETRY_DELAY).seconds) true } ?: flowOf(null) - }.flowOn(Dispatchers.IO) - - override val allAccounts: StateFlow?> = - combine( - internalAccounts, - selectedAccountUUIDProvider.uuid, - ) { accounts, uuid -> - accounts - ?.map { account -> - when (account.sdkAccount.keySource?.lowercase()) { - KEYSTONE_KEYSOURCE -> - KeystoneAccount( - sdkAccount = account.sdkAccount, - unified = - UnifiedInfo( - address = account.unifiedAddress, - balance = account.orchardBalance - ), - transparent = - TransparentInfo( - address = account.transparentAddress, - balance = account.transparentBalance - ), - isSelected = account.sdkAccount.accountUuid == uuid || accounts.size == 1, - ) - - else -> - ZashiAccount( - sdkAccount = account.sdkAccount, - unified = - UnifiedInfo( - address = account.unifiedAddress, - balance = account.orchardBalance - ), - transparent = - TransparentInfo( - address = account.transparentAddress, - balance = account.transparentBalance - ), - sapling = - SaplingInfo( - address = account.saplingAddress!!, - balance = account.saplingBalance!! - ), - isSelected = - uuid == null || - account.sdkAccount.accountUuid == uuid || - accounts.size == 1, - ) - } - }?.sortedDescending() - }.flowOn(Dispatchers.IO) + } + .flowOn(Dispatchers.IO) .stateIn( scope = scope, started = SharingStarted.Eagerly, @@ -201,20 +146,11 @@ class AccountDataSourceImpl( account?.filterIsInstance()?.firstOrNull() }.distinctUntilChanged() - override suspend fun getAllAccounts() = - withContext(Dispatchers.IO) { - allAccounts.filterNotNull().first() - } + override suspend fun getAllAccounts() = withContext(Dispatchers.IO) { allAccounts.filterNotNull().first() } - override suspend fun getSelectedAccount() = - withContext(Dispatchers.IO) { - selectedAccount.filterNotNull().first() - } + override suspend fun getSelectedAccount() = withContext(Dispatchers.IO) { selectedAccount.filterNotNull().first() } - override suspend fun getZashiAccount() = - withContext(Dispatchers.IO) { - zashiAccount.filterNotNull().first() - } + override suspend fun getZashiAccount() = withContext(Dispatchers.IO) { zashiAccount.filterNotNull().first() } override suspend fun selectAccount(account: Account) { withContext(Dispatchers.IO) { @@ -246,34 +182,76 @@ class AccountDataSourceImpl( ), ) } + + override suspend fun requestNextShieldedAddress() { + scope + .launch { + requestNextShieldedAddressPipeline.emit(getSelectedAccount().sdkAccount.accountUuid) + } + .join() + } + + private fun observeIsSelected(sdkAccount: Account, allAccounts: List) = selectedAccountUUIDProvider + .uuid + .map { uuid -> + when (sdkAccount.keySource?.lowercase()) { + KEYSTONE_KEYSOURCE -> sdkAccount.accountUuid == uuid || allAccounts.size == 1 + else -> uuid == null || sdkAccount.accountUuid == uuid || allAccounts.size == 1 + } + } + + private suspend fun observeUnified(synchronizer: Synchronizer, sdkAccount: Account): Flow { + return combine( + requestNextShieldedAddressPipeline + .onStart { emit(sdkAccount.accountUuid) } + .map { + WalletAddress.Unified.new( + synchronizer.getCustomUnifiedAddress(sdkAccount, UnifiedAddressRequest.SHIELDED) + ) + }, + synchronizer.walletBalances + ) { address, balances -> + val balance = balances?.get(sdkAccount.accountUuid) + UnifiedInfo( + address = address, + balance = balance?.orchard ?: createEmptyWalletBalance() + ) + } + } + + private suspend fun observeTransparent(synchronizer: Synchronizer, sdkAccount: Account): Flow { + val address = WalletAddress.Transparent.new(synchronizer.getTransparentAddress(sdkAccount)) + return synchronizer.walletBalances.map { + val balance = it?.get(sdkAccount.accountUuid) + TransparentInfo( + address = address, + balance = balance?.unshielded ?: Zatoshi.ZERO + ) + } + } + + private suspend fun observeSapling(synchronizer: Synchronizer, sdkAccount: Account): Flow { + return if (sdkAccount.keySource == KEYSTONE_KEYSOURCE) { + flowOf(null) + } else { + val address = WalletAddress.Sapling.new(synchronizer.getSaplingAddress(sdkAccount)) + synchronizer.walletBalances.map { + val balance = it?.get(sdkAccount.accountUuid) + SaplingInfo( + address = address, + balance = balance?.sapling ?: createEmptyWalletBalance() + ) + } + } + } + + private fun createEmptyWalletBalance() = + WalletBalance( + available = Zatoshi.ZERO, + changePending = Zatoshi.ZERO, + valuePending = Zatoshi.ZERO, + ) } -private fun createEmptyWalletBalance() = - WalletBalance( - available = Zatoshi.ZERO, - changePending = Zatoshi.ZERO, - valuePending = Zatoshi.ZERO, - ) - -private val Zatoshi.Companion.ZERO: Zatoshi - get() = Zatoshi(0) - -private data class InternalAccountWithAddresses( - val sdkAccount: Account, - val unifiedAddress: WalletAddress.Unified, - val saplingAddress: WalletAddress.Sapling?, - val transparentAddress: WalletAddress.Transparent, -) - -private data class InternalAccountWithBalances( - val sdkAccount: Account, - val unifiedAddress: WalletAddress.Unified, - val saplingAddress: WalletAddress.Sapling?, - val transparentAddress: WalletAddress.Transparent, - val saplingBalance: WalletBalance?, - val orchardBalance: WalletBalance, - val transparentBalance: Zatoshi, -) - private const val RETRY_DELAY = 3L private const val KEYSTONE_KEYSOURCE = "keystone" diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToReceiveUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToReceiveUseCase.kt new file mode 100644 index 000000000..7318e1876 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToReceiveUseCase.kt @@ -0,0 +1,22 @@ +package co.electriccoin.zcash.ui.common.usecase + +import co.electriccoin.zcash.ui.NavigationRouter +import co.electriccoin.zcash.ui.R +import co.electriccoin.zcash.ui.common.datasource.AccountDataSource +import co.electriccoin.zcash.ui.common.repository.BiometricRepository +import co.electriccoin.zcash.ui.common.repository.BiometricRequest +import co.electriccoin.zcash.ui.common.repository.BiometricsCancelledException +import co.electriccoin.zcash.ui.common.repository.BiometricsFailureException +import co.electriccoin.zcash.ui.design.util.stringRes +import co.electriccoin.zcash.ui.screen.addressbook.AddressBookArgs +import co.electriccoin.zcash.ui.screen.receive.Receive + +class NavigateToReceiveUseCase( + private val navigationRouter: NavigationRouter, + private val accountDataSource: AccountDataSource +) { + suspend operator fun invoke() { + accountDataSource.requestNextShieldedAddress() + navigationRouter.forward(Receive) + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToRequestShieldedUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToRequestShieldedUseCase.kt new file mode 100644 index 000000000..7cbb65638 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/NavigateToRequestShieldedUseCase.kt @@ -0,0 +1,18 @@ +package co.electriccoin.zcash.ui.common.usecase + +import co.electriccoin.zcash.ui.NavigationRouter +import co.electriccoin.zcash.ui.NavigationTargets +import co.electriccoin.zcash.ui.common.datasource.AccountDataSource +import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType + +class NavigateToRequestShieldedUseCase( + private val navigationRouter: NavigationRouter, + private val accountDataSource: AccountDataSource +) { + suspend operator fun invoke(requestNewAddress: Boolean = true) { + if (requestNewAddress) { + accountDataSource.requestNextShieldedAddress() + } + navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}") + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/HomeViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/HomeViewModel.kt index 11d419615..6c5fe6e72 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/HomeViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/HomeViewModel.kt @@ -18,6 +18,8 @@ import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase +import co.electriccoin.zcash.ui.common.usecase.NavigateToReceiveUseCase +import co.electriccoin.zcash.ui.common.usecase.NavigateToRequestShieldedUseCase import co.electriccoin.zcash.ui.common.usecase.ShieldFundsMessageUseCase import co.electriccoin.zcash.ui.design.component.BigIconButtonState import co.electriccoin.zcash.ui.design.util.stringRes @@ -39,7 +41,6 @@ import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingInfo import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessageState import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations -import co.electriccoin.zcash.ui.screen.receive.Receive import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType import co.electriccoin.zcash.ui.screen.scan.Scan import co.electriccoin.zcash.ui.screen.scan.ScanFlow @@ -61,9 +62,11 @@ class HomeViewModel( shieldFundsInfoProvider: ShieldFundsInfoProvider, private val navigationRouter: NavigationRouter, private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase, - private val navigateToCoinbase: NavigateToCoinbaseUseCase, private val shieldFunds: ShieldFundsMessageUseCase, + private val navigateToCoinbase: NavigateToCoinbaseUseCase, private val navigateToError: NavigateToErrorUseCase, + private val navigateToReceive: NavigateToReceiveUseCase, + private val navigateToRequestShielded: NavigateToRequestShieldedUseCase ) : ViewModel() { private val messageState = combine( @@ -234,14 +237,13 @@ class HomeViewModel( private fun onSendButtonClick() = navigationRouter.forward(Send()) - private fun onReceiveButtonClick() = navigationRouter.forward(Receive) + private fun onReceiveButtonClick() = viewModelScope.launch { navigateToReceive() } private fun onScanButtonClick() = navigationRouter.forward(Scan(ScanFlow.HOMEPAGE)) private fun onBuyClick() = viewModelScope.launch { navigateToCoinbase(replaceCurrentScreen = false) } - private fun onRequestClick() = - navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}") + private fun onRequestClick() = viewModelScope.launch { navigateToRequestShielded() } private fun onWalletUpdatingMessageClick() = navigationRouter.forward(WalletUpdatingInfo) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt index 3adc795c0..3dda11ee6 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt @@ -46,6 +46,7 @@ import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography import co.electriccoin.zcash.ui.design.util.getValue +import co.electriccoin.zcash.ui.design.util.orDark import co.electriccoin.zcash.ui.design.util.scaffoldScrollPadding import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.fixture.ZashiMainTopAppBarStateFixture @@ -140,9 +141,9 @@ private fun AddressPanel( .wrapContentHeight() .background( if (state.isShielded) { - ZashiColors.Utility.Purple.utilityPurple50 + ZashiColors.Utility.Purple.utilityPurple50 orDark ZashiColors.Utility.Indigo.utilityIndigo50 } else { - ZashiColors.Utility.Gray.utilityGray50 + ZashiColors.Surfaces.bgSecondary }, RoundedCornerShape(ZashiDimensions.Radius.radius3xl) ).clip(RoundedCornerShape(ZashiDimensions.Radius.radius3xl)) @@ -152,7 +153,7 @@ private fun AddressPanel( Row(modifier = Modifier.fillMaxWidth()) { Box { Image( - modifier = Modifier.sizeIn(maxWidth = 34.dp, maxHeight = 34.dp), + modifier = Modifier.size(40.dp), painter = painterResource(id = state.icon), contentDescription = null ) @@ -162,7 +163,7 @@ private fun AddressPanel( Modifier .size(14.dp) .align(Alignment.BottomEnd) - .offset(3.5.dp, 3.5.dp), + .offset(1.5.dp, .5.dp), painter = painterResource(R.drawable.ic_receive_shield), contentDescription = "", ) @@ -178,9 +179,7 @@ private fun AddressPanel( style = ZashiTypography.textMd, fontWeight = FontWeight.SemiBold ) - - Spacer(Modifier.height(ZcashTheme.dimens.spacingTiny)) - + Spacer(4.dp) Text( text = state.subtitle.getValue(), color = ZashiColors.Text.textTertiary, @@ -191,13 +190,6 @@ private fun AddressPanel( Spacer(Modifier.width(ZcashTheme.dimens.spacingSmall)) Spacer(modifier = Modifier.weight(1f)) - - if (state.isShielded) { - Image( - painter = painterResource(id = R.drawable.ic_check_shielded_solid), - contentDescription = null - ) - } } AnimatedVisibility(visible = state.isExpanded) { @@ -210,13 +202,13 @@ private fun AddressPanel( ) { val containerColor = if (state.isShielded) { - ZashiColors.Utility.Purple.utilityPurple100 + ZashiColors.Utility.Purple.utilityPurple100 orDark ZashiColors.Utility.Indigo.utilityIndigo100 } else { ZashiColors.Surfaces.bgTertiary } val contentColor = if (state.isShielded) { - ZashiColors.Utility.Purple.utilityPurple800 + ZashiColors.Utility.Purple.utilityPurple800 orDark ZashiColors.Utility.Indigo.utilityIndigo800 } else { ZashiColors.Text.textPrimary } @@ -272,16 +264,14 @@ private fun ReceiveIconButton( .background(containerColor, RoundedCornerShape(ZashiDimensions.Radius.radiusXl)) .clip(RoundedCornerShape(ZashiDimensions.Radius.radiusXl)) .clickable { onClick() } - .padding(ZcashTheme.dimens.spacingMid) + .padding(12.dp) ) { Icon( painter = iconPainter, contentDescription = text, tint = contentColor ) - - Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny)) - + Spacer(4.dp) Text( text = text, color = contentColor, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/viewmodel/ReceiveViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/viewmodel/ReceiveViewModel.kt index 25954345d..474e71a7c 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/viewmodel/ReceiveViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/viewmodel/ReceiveViewModel.kt @@ -78,12 +78,7 @@ class ReceiveViewModel( icon = when (account) { is KeystoneAccount -> co.electriccoin.zcash.ui.design.R.drawable.ic_item_keystone - is ZashiAccount -> - if (type == ReceiveAddressType.Unified) { - R.drawable.ic_zec_round_full - } else { - R.drawable.ic_zec_round_stroke - } + is ZashiAccount -> R.drawable.ic_zec_round_full }, title = when (account) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt index a3313b745..9287fa578 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt @@ -12,6 +12,7 @@ import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.repository.Transaction import co.electriccoin.zcash.ui.common.usecase.GetTransactionsUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase +import co.electriccoin.zcash.ui.common.usecase.NavigateToRequestShieldedUseCase import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType @@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch class TransactionHistoryWidgetViewModel( getTransactions: GetTransactionsUseCase, @@ -28,6 +30,7 @@ class TransactionHistoryWidgetViewModel( private val transactionHistoryMapper: TransactionHistoryMapper, private val navigationRouter: NavigationRouter, private val restoreTimestampDataSource: RestoreTimestampDataSource, + private val navigateToRequestShielded: NavigateToRequestShieldedUseCase ) : ViewModel() { val state = combine( @@ -88,9 +91,7 @@ class TransactionHistoryWidgetViewModel( navigationRouter.forward(TransactionHistory) } - private fun onRequestZecClick() { - navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}") - } + private fun onRequestZecClick() = viewModelScope.launch { navigateToRequestShielded() } } private const val MAX_TRANSACTION_COUNT = 5