Skip to content

Commit 8325b53

Browse files
author
Noman R
committed
feat: Asset account details and list screens to view account-specific asset transactions and balances
1 parent 9c3a14f commit 8325b53

File tree

18 files changed

+907
-127
lines changed

18 files changed

+907
-127
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.
33

44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

6+
## [Unreleased]
7+
8+
#### Added
9+
- Asset account details and list screens to view account-specific asset transactions and balances
10+
- Load more transactions functionality in asset account details screen
11+
12+
#### Fixed
13+
614
## [5.0.6] - 2025-07-01
715

816
#### Fixed

androidApp/src/main/java/com/blockstream/green/di/ViewModels.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ val viewModels = module {
158158
AccountExchangeViewModel(get(), getOrNull())
159159
}
160160
viewModel {
161-
SendViewModel(get(), getOrNull(), getOrNull())
161+
SendViewModel(get(), getOrNull(), getOrNull(), getOrNull())
162162
}
163163
viewModel {
164164
SimpleGreenViewModel(getOrNull(), getOrNull(), getOrNull(), getOrNull())

common/src/commonMain/kotlin/com/blockstream/common/di/ViewModels.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ val factoryViewModels = module {
155155
AccountExchangeViewModel(get(), getOrNull())
156156
}
157157
factory {
158-
SendViewModel(get(), getOrNull(), getOrNull())
158+
SendViewModel(get(), getOrNull(), getOrNull(), getOrNull())
159159
}
160160
factory {
161161
SimpleGreenViewModel(getOrNull(), getOrNull(), getOrNull())

common/src/commonMain/kotlin/com/blockstream/common/extensions/GdkExtensions.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ fun Account.hasTwoFactorReset(session: GdkSession): Boolean {
8686
return isMultisig && session.twoFactorReset(network).value?.isActive == true
8787
}
8888

89+
fun List<Account>.filterForAsset(assetId: String, session: GdkSession): List<Account> {
90+
val enrichedAsset = session.getEnrichedAssets(assetId)
91+
return filter { account ->
92+
when {
93+
enrichedAsset?.isAmp == true -> account.type == AccountType.AMP_ACCOUNT
94+
assetId.isPolicyAsset(session) -> account.network.policyAsset == assetId
95+
else -> account.isLiquid && !account.isAmp
96+
}
97+
}
98+
}
99+
89100
fun String?.isBitcoinPolicyAsset(): Boolean = (this == null || this == BTC_POLICY_ASSET)
90101
fun String?.isLightningPolicyAsset(): Boolean = (this == LN_BTC_POLICY_ASSET)
91102
fun String?.isPolicyAsset(network: Network?): Boolean = (this == null || this == network?.policyAsset)
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package com.blockstream.common.models.assetaccounts
2+
3+
import com.blockstream.common.data.DataState
4+
import com.blockstream.common.data.Denomination
5+
import com.blockstream.common.data.EnrichedAsset
6+
import com.blockstream.common.data.GreenWallet
7+
import com.blockstream.common.extensions.ifConnected
8+
import com.blockstream.common.gdk.data.AccountAsset
9+
import com.blockstream.common.gdk.data.AccountBalance
10+
import com.blockstream.common.looks.transaction.TransactionLook
11+
import com.blockstream.common.models.GreenViewModel
12+
import com.blockstream.common.navigation.NavigateDestinations
13+
import com.blockstream.common.sideeffects.SideEffects
14+
import com.blockstream.common.utils.toAmountLook
15+
import com.blockstream.green.utils.Loggable
16+
import com.blockstream.ui.events.Event
17+
import com.blockstream.ui.navigation.NavData
18+
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
19+
import com.rickclephas.kmp.observableviewmodel.coroutineScope
20+
import com.rickclephas.kmp.observableviewmodel.launch
21+
import com.rickclephas.kmp.observableviewmodel.stateIn
22+
import kotlinx.coroutines.flow.SharingStarted
23+
import kotlinx.coroutines.flow.StateFlow
24+
import kotlinx.coroutines.flow.combine
25+
import kotlinx.coroutines.flow.launchIn
26+
import kotlinx.coroutines.flow.map
27+
import kotlinx.coroutines.flow.onEach
28+
29+
abstract class AssetAccountDetailsViewModelAbstract(
30+
greenWallet: GreenWallet, accountAssetOrNull: AccountAsset? = null
31+
) : GreenViewModel(greenWalletOrNull = greenWallet, accountAssetOrNull = accountAssetOrNull) {
32+
override fun screenName(): String = "AssetAccountDetails"
33+
34+
@NativeCoroutinesState
35+
abstract val asset: StateFlow<EnrichedAsset?>
36+
37+
@NativeCoroutinesState
38+
abstract val accountBalance: StateFlow<AccountBalance>
39+
40+
@NativeCoroutinesState
41+
abstract val transactions: StateFlow<DataState<List<TransactionLook>>>
42+
43+
@NativeCoroutinesState
44+
abstract val totalBalance: StateFlow<String>
45+
46+
@NativeCoroutinesState
47+
abstract val totalBalanceFiat: StateFlow<String?>
48+
49+
@NativeCoroutinesState
50+
abstract val showBuyButton: StateFlow<Boolean>
51+
52+
@NativeCoroutinesState
53+
abstract val hasMoreTransactions: StateFlow<Boolean>
54+
}
55+
56+
class AssetAccountDetailsViewModel(
57+
greenWallet: GreenWallet, accountAssetOrNull: AccountAsset
58+
) : AssetAccountDetailsViewModelAbstract(greenWallet = greenWallet, accountAssetOrNull = accountAssetOrNull) {
59+
60+
class LocalEvents {
61+
object ClickBuy : Event
62+
object ClickSend : Event
63+
object ClickReceive : Event
64+
object LoadMoreTransactions : Event
65+
}
66+
67+
override val asset: StateFlow<EnrichedAsset?> =
68+
accountAsset.map { it?.asset }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), accountAsset.value?.asset)
69+
70+
override val accountBalance: StateFlow<AccountBalance> = session.accountsAndBalanceUpdated.map {
71+
AccountBalance.create(account = account, session = session)
72+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountBalance.create(account = account))
73+
74+
override val showBuyButton: StateFlow<Boolean> = accountAsset.map {
75+
it?.asset?.isBitcoin == true
76+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), accountAsset.value?.asset?.isBitcoin == true)
77+
78+
private val hideAmounts: StateFlow<Boolean> = settingsManager.appSettingsStateFlow.map {
79+
it.hideAmounts
80+
}.stateIn(
81+
viewModelScope, SharingStarted.WhileSubscribed(), settingsManager.appSettings.hideAmounts
82+
)
83+
84+
private val _totalBalance = MutableStateFlow("")
85+
override val totalBalance: StateFlow<String> = _totalBalance
86+
87+
private val _totalBalanceFiat = MutableStateFlow<String?>(null)
88+
override val totalBalanceFiat: StateFlow<String?> = _totalBalanceFiat
89+
90+
init {
91+
viewModelScope.launch {
92+
val assetName = accountAsset.value?.asset?.name(session)?.toString() ?: accountAsset.value?.assetId ?: ""
93+
_navData.value = NavData(
94+
title = assetName, subtitle = account.name
95+
)
96+
}
97+
98+
session.ifConnected {
99+
accountBalance.onEach {
100+
updateTotalBalance()
101+
}.launchIn(viewModelScope.coroutineScope)
102+
103+
session.getTransactions(account = account, isReset = true, isLoadMore = false)
104+
}
105+
106+
bootstrap()
107+
}
108+
109+
private val _transactions: StateFlow<DataState<List<TransactionLook>>> = combine(
110+
session.accountTransactions(account), session.settings()
111+
) { transactions, _ ->
112+
DataState.Success(
113+
(transactions.data() ?: emptyList()).map { transaction ->
114+
TransactionLook.create(
115+
transaction = transaction, session = session
116+
)
117+
})
118+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), DataState.Loading)
119+
120+
override val transactions: StateFlow<DataState<List<TransactionLook>>> = combine(
121+
hideAmounts, _transactions
122+
) { hideAmounts, transactionsLooks ->
123+
if (transactionsLooks is DataState.Success && hideAmounts) {
124+
DataState.Success(transactionsLooks.data.map { it.asMasked })
125+
} else {
126+
transactionsLooks
127+
}
128+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), DataState.Loading)
129+
130+
override val hasMoreTransactions: StateFlow<Boolean> = session.accountTransactionsPager(account)
131+
132+
private fun updateTotalBalance() {
133+
viewModelScope.launch {
134+
accountAsset.value?.let { accountAsset ->
135+
_totalBalance.value = accountAsset.balance(session).toAmountLook(
136+
session = session, assetId = accountAsset.assetId, withUnit = true, withGrouping = true, withMinimumDigits = false
137+
) ?: ""
138+
139+
_totalBalanceFiat.value = accountAsset.balance(session).toAmountLook(
140+
session = session, assetId = accountAsset.assetId, withUnit = true, denomination = Denomination.fiat(session)
141+
)?.let { fiatBalance ->
142+
_totalBalance.value.takeIf { it.isNotBlank() && it != fiatBalance }?.let {
143+
fiatBalance
144+
}
145+
}
146+
}
147+
}
148+
}
149+
150+
fun buy() {
151+
countly.buyInitiate()
152+
postSideEffect(
153+
SideEffects.NavigateTo(
154+
NavigateDestinations.Buy(
155+
greenWallet = greenWallet
156+
)
157+
)
158+
)
159+
}
160+
161+
override suspend fun handleEvent(event: Event) {
162+
super.handleEvent(event)
163+
164+
when (event) {
165+
is LocalEvents.ClickBuy -> {
166+
buy()
167+
}
168+
169+
is LocalEvents.ClickSend -> {
170+
postSideEffect(
171+
SideEffects.NavigateTo(
172+
NavigateDestinations.Send(
173+
greenWallet = greenWallet, accountAsset = accountAsset.value
174+
)
175+
)
176+
)
177+
}
178+
179+
is LocalEvents.ClickReceive -> {
180+
postSideEffect(
181+
SideEffects.NavigateTo(
182+
NavigateDestinations.Receive(
183+
greenWallet = greenWallet, accountAsset = accountAsset.value!!
184+
)
185+
)
186+
)
187+
}
188+
189+
is LocalEvents.LoadMoreTransactions -> {
190+
loadMoreTransactions()
191+
}
192+
}
193+
}
194+
195+
private fun loadMoreTransactions() {
196+
session.getTransactions(account = account, isReset = false, isLoadMore = true)
197+
}
198+
199+
companion object : Loggable()
200+
}

0 commit comments

Comments
 (0)