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 4599e7c9..f35a03a4 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 @@ -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) } 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 2c5d6189..e5e8895e 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 @@ -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 + val allAccounts: StateFlow?> val selectedAccount: Flow @@ -72,6 +77,8 @@ class AccountDataSourceImpl( ) : AccountDataSource { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + override val onAccountChanged = MutableSharedFlow() + @OptIn(ExperimentalCoroutinesApi::class) private val internalAccounts: Flow?> = synchronizerProvider @@ -199,12 +206,14 @@ class AccountDataSourceImpl( .map { account -> account?.firstOrNull { it.isSelected } } + .distinctUntilChanged() override val zashiAccount: Flow = allAccounts .map { account -> account?.filterIsInstance()?.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( diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveOnAccountChangedUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveOnAccountChangedUseCase.kt new file mode 100644 index 00000000..7fa43b8a --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveOnAccountChangedUseCase.kt @@ -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 +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/model/ReceiveState.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/model/ReceiveState.kt index 5401cd56..dc099cd2 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/model/ReceiveState.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/model/ReceiveState.kt @@ -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, 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 16eb0f18..823a1885 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 @@ -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, 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() - ) - } 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 47c9d5d9..4ba7576a 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 @@ -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 } + } }