Shielded address rotation
This commit is contained in:
parent
52a5fd53ff
commit
72534e42f4
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<AccountUuid>()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val internalAccounts: Flow<List<InternalAccountWithBalances>?> =
|
||||
override val allAccounts: StateFlow<List<WalletAccount>?> =
|
||||
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<List<WalletAccount>?> =
|
||||
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<ZashiAccount>()?.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<Account>) = 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<UnifiedInfo> {
|
||||
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<TransparentInfo> {
|
||||
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<SaplingInfo?> {
|
||||
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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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}")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue