Receive screen account expanded state is now reset on account change

This commit is contained in:
Milan Cerovsky 2024-12-18 08:57:20 +01:00 committed by Milan
parent 9ae240ce29
commit 1580f7045c
6 changed files with 70 additions and 50 deletions

View File

@ -30,6 +30,7 @@ import co.electriccoin.zcash.ui.common.usecase.ObserveContactPickedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveCurrentTransactionsUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveIsFlexaAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveOnAccountChangedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedWalletAccountUseCase
@ -122,4 +123,5 @@ val useCaseModule =
factoryOf(::ObserveTransactionProposalUseCase)
factoryOf(::SharePCZTUseCase)
factoryOf(::CreateKeystoneProposalPCZTEncoderUseCase)
factoryOf(::ObserveOnAccountChangedUseCase)
}

View File

@ -25,10 +25,12 @@ 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.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@ -37,11 +39,14 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface AccountDataSource {
val onAccountChanged: Flow<Unit>
val allAccounts: StateFlow<List<WalletAccount>?>
val selectedAccount: Flow<WalletAccount?>
@ -72,6 +77,8 @@ class AccountDataSourceImpl(
) : AccountDataSource {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override val onAccountChanged = MutableSharedFlow<Unit>()
@OptIn(ExperimentalCoroutinesApi::class)
private val internalAccounts: Flow<List<InternalAccountWithBalances>?> =
synchronizerProvider
@ -199,12 +206,14 @@ class AccountDataSourceImpl(
.map { account ->
account?.firstOrNull { it.isSelected }
}
.distinctUntilChanged()
override val zashiAccount: Flow<ZashiAccount?> =
allAccounts
.map { account ->
account?.filterIsInstance<ZashiAccount>()?.firstOrNull()
}
.distinctUntilChanged()
override suspend fun getAllAccounts() =
withContext(Dispatchers.IO) {
@ -221,15 +230,21 @@ class AccountDataSourceImpl(
zashiAccount.filterNotNull().first()
}
override suspend fun selectAccount(account: Account) =
override suspend fun selectAccount(account: Account) {
withContext(Dispatchers.IO) {
selectedAccountUUIDProvider.setUUID(account.accountUuid)
}
val current = selectedAccountUUIDProvider.getUUID()
override suspend fun selectAccount(account: WalletAccount) =
withContext(Dispatchers.IO) {
selectedAccountUUIDProvider.setUUID(account.sdkAccount.accountUuid)
selectedAccountUUIDProvider.setUUID(account.accountUuid)
scope.launch {
if (current != account.accountUuid) {
onAccountChanged.emit(Unit)
}
}
}
}
override suspend fun selectAccount(account: WalletAccount) = selectAccount(account.sdkAccount)
@OptIn(ExperimentalStdlibApi::class)
override suspend fun importKeystoneAccount(

View File

@ -0,0 +1,7 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
class ObserveOnAccountChangedUseCase(private val accountDataSource: AccountDataSource) {
operator fun invoke() = accountDataSource.onAccountChanged
}

View File

@ -11,6 +11,8 @@ data class ReceiveAddressState(
val icon: Int,
val title: StringResource,
val subtitle: StringResource,
val isExpanded: Boolean,
val onClick: () -> Unit,
val isShielded: Boolean,
val onCopyClicked: () -> Unit,
val onQrClicked: () -> Unit,

View File

@ -21,10 +21,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -85,8 +81,6 @@ private fun ReceiveContents(
items: List<ReceiveAddressState>,
modifier: Modifier = Modifier,
) {
var expandedIndex by rememberSaveable { mutableIntStateOf(0) }
Column(
modifier =
modifier
@ -122,8 +116,6 @@ private fun ReceiveContents(
AddressPanel(
state = state,
expanded = index == expandedIndex,
onExpand = { expandedIndex = index },
modifier = Modifier.fillMaxWidth()
)
}
@ -133,8 +125,6 @@ private fun ReceiveContents(
@Composable
private fun AddressPanel(
state: ReceiveAddressState,
expanded: Boolean,
onExpand: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -151,7 +141,7 @@ private fun AddressPanel(
RoundedCornerShape(ZashiDimensions.Radius.radius3xl)
)
.clip(RoundedCornerShape(ZashiDimensions.Radius.radius3xl))
.clickable { onExpand() }
.clickable(onClick = state.onClick)
.padding(all = ZcashTheme.dimens.spacingLarge)
) {
Row(modifier = Modifier.fillMaxWidth()) {
@ -192,7 +182,7 @@ private fun AddressPanel(
}
}
AnimatedVisibility(visible = expanded) {
AnimatedVisibility(visible = state.isExpanded) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier =
@ -298,6 +288,8 @@ private fun ZashiPreview() =
onCopyClicked = {},
onQrClicked = { },
onRequestClicked = {},
isExpanded = true,
onClick = {}
),
ReceiveAddressState(
icon = R.drawable.ic_zec_round_stroke,
@ -307,6 +299,8 @@ private fun ZashiPreview() =
onCopyClicked = {},
onQrClicked = { },
onRequestClicked = { },
isExpanded = false,
onClick = {}
)
),
isLoading = false
@ -314,28 +308,3 @@ private fun ZashiPreview() =
zashiMainTopAppBarState = ZashiMainTopAppBarStateFixture.new()
)
}
@PreviewScreens
@Composable
private fun KeystonePreview() =
ZcashTheme {
ReceiveView(
state =
ReceiveState(
items =
listOf(
ReceiveAddressState(
icon = co.electriccoin.zcash.ui.design.R.drawable.ic_item_keystone,
title = stringRes("Keystone Address"),
subtitle = stringRes("subtitle"),
isShielded = true,
onCopyClicked = {},
onQrClicked = {},
onRequestClicked = {},
),
),
isLoading = false
),
zashiMainTopAppBarState = ZashiMainTopAppBarStateFixture.new()
)
}

View File

@ -11,39 +11,48 @@ import co.electriccoin.zcash.ui.common.model.KeystoneAccount
import co.electriccoin.zcash.ui.common.model.WalletAccount
import co.electriccoin.zcash.ui.common.model.ZashiAccount
import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveOnAccountChangedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressState
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class ReceiveViewModel(
observeSelectedWalletAccount: ObserveSelectedWalletAccountUseCase,
observeOnAccountChanged: ObserveOnAccountChangedUseCase,
private val application: Application,
private val copyToClipboard: CopyToClipboardUseCase,
private val navigationRouter: NavigationRouter,
) : ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
private val expandedIndex = MutableStateFlow(0)
internal val state =
observeSelectedWalletAccount.require().mapLatest { account ->
combine(expandedIndex, observeSelectedWalletAccount.require()) { expandedIndex, account ->
ReceiveState(
items =
listOfNotNull(
createAddressState(
account = account,
address = account.unified.address.address,
type = ReceiveAddressType.Unified
type = ReceiveAddressType.Unified,
isExpanded = expandedIndex == 0,
onClick = { onAddressClick(0) }
),
createAddressState(
account = account,
address = account.transparent.address.address,
type = ReceiveAddressType.Transparent
type = ReceiveAddressType.Transparent,
isExpanded = expandedIndex == 1,
onClick = { onAddressClick(1) }
),
),
isLoading = false
@ -54,10 +63,20 @@ class ReceiveViewModel(
initialValue = ReceiveState(items = null, isLoading = true)
)
init {
viewModelScope.launch {
observeOnAccountChanged().collect {
expandedIndex.update { 0 }
}
}
}
private fun createAddressState(
account: WalletAccount,
address: String,
type: ReceiveAddressType
type: ReceiveAddressType,
isExpanded: Boolean,
onClick: () -> Unit,
) = ReceiveAddressState(
icon =
when (account) {
@ -94,6 +113,8 @@ class ReceiveViewModel(
},
onQrClicked = { onQrCodeClick(type) },
onRequestClicked = { onRequestClick(type) },
onClick = onClick,
isExpanded = isExpanded
)
private fun onRequestClick(addressType: ReceiveAddressType) =
@ -101,4 +122,8 @@ class ReceiveViewModel(
private fun onQrCodeClick(addressType: ReceiveAddressType) =
navigationRouter.forward("${NavigationTargets.QR_CODE}/${addressType.ordinal}")
private fun onAddressClick(index: Int) {
expandedIndex.update { index }
}
}