Skip to content

Commit e6bb738

Browse files
refactor: clean up MVI pattern
1 parent ddb82af commit e6bb738

File tree

13 files changed

+114
-119
lines changed

13 files changed

+114
-119
lines changed

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/domain/Wallet.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import org.bitcoindevkit.devkitwallet.data.Timestamp
3333
import org.bitcoindevkit.devkitwallet.data.TxDetails
3434
import org.bitcoindevkit.devkitwallet.domain.utils.intoDomain
3535
import org.bitcoindevkit.devkitwallet.domain.utils.intoProto
36-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.Recipient
36+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.Recipient
3737
import java.util.UUID
3838
import org.bitcoindevkit.Wallet as BdkWallet
3939

@@ -63,7 +63,7 @@ class Wallet private constructor(
6363
// txBuilder = txBuilder.feeRate(satPerVbyte = fee_rate)
6464

6565
// technique 2 for adding a list of recipients to the TxBuilder
66-
var txBuilder =
66+
val txBuilder =
6767
recipientList.fold(TxBuilder()) { builder, recipient ->
6868
// val address = Address(recipient.address)
6969
val scriptPubKey: Script = Address(recipient.address, Network.TESTNET).scriptPubkey()

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/HomeNavigation.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ package org.bitcoindevkit.devkitwallet.presentation.navigation
88
import androidx.compose.animation.AnimatedContentTransitionScope
99
import androidx.compose.animation.core.tween
1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.collectAsState
12+
import androidx.compose.runtime.getValue
13+
import androidx.lifecycle.viewmodel.compose.viewModel
1114
import androidx.navigation.NavHostController
1215
import androidx.navigation.compose.NavHost
1316
import androidx.navigation.compose.composable
@@ -25,7 +28,9 @@ private const val ANIMATION_DURATION: Int = 400
2528
@Composable
2629
fun HomeNavigation(activeWallet: Wallet) {
2730
val navController: NavHostController = rememberNavController()
28-
val walletViewModel = WalletViewModel(activeWallet)
31+
val walletViewModel = viewModel<WalletViewModel> {
32+
WalletViewModel(activeWallet)
33+
}
2934

3035
NavHost(
3136
navController = navController,
@@ -126,8 +131,9 @@ fun HomeNavigation(activeWallet: Wallet) {
126131
)
127132
}
128133
) {
134+
val state by walletViewModel.state.collectAsState()
129135
BlockchainClientScreen(
130-
state = walletViewModel.state,
136+
state = state,
131137
navController = navController
132138
)
133139
}

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/WalletNavigation.kt

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import androidx.compose.animation.AnimatedContentTransitionScope
99
import androidx.compose.animation.core.tween
1010
import androidx.compose.material3.DrawerState
1111
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.collectAsState
13+
import androidx.compose.runtime.getValue
14+
import androidx.lifecycle.viewmodel.compose.viewModel
1215
import androidx.navigation.NavHostController
1316
import androidx.navigation.compose.NavHost
1417
import androidx.navigation.compose.composable
@@ -30,8 +33,12 @@ private const val ANIMATION_DURATION: Int = 400
3033
@Composable
3134
fun WalletNavigation(drawerState: DrawerState, activeWallet: Wallet, walletViewModel: WalletViewModel) {
3235
val navController: NavHostController = rememberNavController()
33-
val addressViewModel = AddressViewModel(activeWallet)
34-
val sendViewModel = SendViewModel(activeWallet)
36+
val addressViewModel = viewModel<AddressViewModel> {
37+
AddressViewModel(activeWallet)
38+
}
39+
val sendViewModel = viewModel<SendViewModel> {
40+
SendViewModel(activeWallet)
41+
}
3542

3643
NavHost(
3744
navController = navController,
@@ -50,7 +57,15 @@ fun WalletNavigation(drawerState: DrawerState, activeWallet: Wallet, walletViewM
5057
animationSpec = tween(ANIMATION_DURATION)
5158
)
5259
},
53-
) { WalletHomeScreen(navController, drawerState, walletViewModel) }
60+
) {
61+
val walletState by walletViewModel.state.collectAsState()
62+
WalletHomeScreen(
63+
drawerState = drawerState,
64+
state = walletState,
65+
onAction = walletViewModel::onAction,
66+
navController = navController
67+
)
68+
}
5469

5570
composable<ReceiveScreen>(
5671
enterTransition = {
@@ -78,10 +93,11 @@ fun WalletNavigation(drawerState: DrawerState, activeWallet: Wallet, walletViewM
7893
)
7994
}
8095
) {
96+
val state by addressViewModel.state.collectAsState()
8197
ReceiveScreen(
82-
state = addressViewModel.state,
98+
state = state,
8399
onAction = addressViewModel::onAction,
84-
navController
100+
navController = navController
85101
)
86102
}
87103

@@ -110,7 +126,8 @@ fun WalletNavigation(drawerState: DrawerState, activeWallet: Wallet, walletViewM
110126
animationSpec = tween(ANIMATION_DURATION)
111127
)
112128
}
113-
) { SendScreen(navController, sendViewModel) }
129+
) { SendScreen(onAction = sendViewModel::onAction, navController = navController) }
130+
114131

115132
composable<RbfScreen>(
116133
enterTransition = {

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/drawer/BlockchainClientScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors
2424
import org.bitcoindevkit.devkitwallet.presentation.theme.quattroBold
2525
import org.bitcoindevkit.devkitwallet.presentation.theme.standardText
2626
import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar
27-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState
27+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletScreenState
2828

2929
@Composable
3030
internal fun BlockchainClientScreen(state: WalletScreenState, navController: NavController) {

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/ReceiveScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors
6262
import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular
6363
import org.bitcoindevkit.devkitwallet.presentation.theme.standardText
6464
import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar
65-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenAction
66-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenState
6765
import androidx.core.graphics.set
66+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.ReceiveScreenAction
67+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.ReceiveScreenState
6868

6969
private const val TAG = "ReceiveScreen"
7070

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/SendScreen.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,19 @@ import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors
6969
import org.bitcoindevkit.devkitwallet.presentation.theme.standardText
7070
import org.bitcoindevkit.devkitwallet.presentation.ui.components.NeutralButton
7171
import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar
72-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.SendViewModel
73-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.Recipient
74-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.SendScreenAction
75-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TransactionType
76-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TxDataBundle
72+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.Recipient
73+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.SendScreenAction
74+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.TransactionType
75+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.TxDataBundle
7776

7877
private const val TAG = "SendScreen"
7978

8079
@OptIn(ExperimentalMaterial3Api::class)
8180
@Composable
82-
internal fun SendScreen(navController: NavController, sendViewModel: SendViewModel) {
83-
val onAction = sendViewModel::onAction
84-
81+
internal fun SendScreen(
82+
onAction: (SendScreenAction) -> Unit,
83+
navController: NavController,
84+
) {
8585
val context = LocalContext.current
8686
val coroutineScope = rememberCoroutineScope()
8787

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/WalletHomeScreen.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,19 @@ import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular
5959
import org.bitcoindevkit.devkitwallet.presentation.theme.quattroBold
6060
import org.bitcoindevkit.devkitwallet.presentation.ui.components.LoadingAnimation
6161
import org.bitcoindevkit.devkitwallet.presentation.ui.components.NeutralButton
62-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletViewModel
63-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenAction
64-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState
62+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletScreenAction
63+
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletScreenState
6564

6665
private const val TAG = "WalletHomeScreen"
6766

6867
@Composable
6968
internal fun WalletHomeScreen(
70-
navController: NavHostController,
7169
drawerState: DrawerState,
72-
walletViewModel: WalletViewModel,
70+
state: WalletScreenState,
71+
onAction: (WalletScreenAction) -> Unit,
72+
navController: NavHostController,
7373
) {
7474
val networkAvailable: Boolean = isOnline(LocalContext.current)
75-
val state: WalletScreenState = walletViewModel.state
76-
val onAction = walletViewModel::onAction
77-
7875
val interactionSource = remember { MutableInteractionSource() }
7976
val scope: CoroutineScope = rememberCoroutineScope()
8077

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/AddressViewModel.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55

66
package org.bitcoindevkit.devkitwallet.presentation.viewmodels
77

8-
import androidx.compose.runtime.getValue
9-
import androidx.compose.runtime.mutableStateOf
10-
import androidx.compose.runtime.setValue
118
import androidx.lifecycle.ViewModel
9+
import kotlinx.coroutines.flow.MutableStateFlow
10+
import kotlinx.coroutines.flow.StateFlow
11+
import kotlinx.coroutines.flow.asStateFlow
12+
import kotlinx.coroutines.flow.update
1213
import org.bitcoindevkit.AddressInfo
1314
import org.bitcoindevkit.devkitwallet.domain.DwLogger
1415
import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO
1516
import org.bitcoindevkit.devkitwallet.domain.Wallet
16-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenAction
17-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenState
17+
18+
data class ReceiveScreenState(
19+
val address: String? = null,
20+
val addressIndex: UInt? = null,
21+
)
22+
23+
sealed interface ReceiveScreenAction {
24+
data object UpdateAddress : ReceiveScreenAction
25+
}
1826

1927
internal class AddressViewModel(private val wallet: Wallet) : ViewModel() {
20-
var state: ReceiveScreenState by mutableStateOf(ReceiveScreenState())
21-
private set
28+
private val _state: MutableStateFlow<ReceiveScreenState> = MutableStateFlow(ReceiveScreenState())
29+
val state: StateFlow<ReceiveScreenState> = _state.asStateFlow()
2230

2331
fun onAction(action: ReceiveScreenAction) {
2432
when (action) {
@@ -30,9 +38,11 @@ internal class AddressViewModel(private val wallet: Wallet) : ViewModel() {
3038
val newAddress: AddressInfo = wallet.getNewAddress()
3139
DwLogger.log(INFO, "Revealing new address at index ${newAddress.index}")
3240

33-
state = ReceiveScreenState(
34-
address = newAddress.address.toString(),
35-
addressIndex = newAddress.index
36-
)
41+
_state.update {
42+
ReceiveScreenState(
43+
address = newAddress.address.toString(),
44+
addressIndex = newAddress.index
45+
)
46+
}
3747
}
3848
}

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/SendViewModel.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,26 @@ import androidx.lifecycle.ViewModel
1010
import org.bitcoindevkit.FeeRate
1111
import org.bitcoindevkit.Psbt
1212
import org.bitcoindevkit.devkitwallet.domain.Wallet
13-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.SendScreenAction
14-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TransactionType
15-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TxDataBundle
1613

1714
private const val TAG = "SendViewModel"
1815

16+
sealed class SendScreenAction {
17+
data class Broadcast(val txDataBundle: TxDataBundle) : SendScreenAction()
18+
}
19+
20+
data class TxDataBundle(
21+
val recipients: List<Recipient>,
22+
val feeRate: ULong,
23+
val transactionType: TransactionType,
24+
)
25+
26+
data class Recipient(var address: String, var amount: ULong)
27+
28+
enum class TransactionType {
29+
STANDARD,
30+
SEND_ALL,
31+
}
32+
1933
internal class SendViewModel(private val wallet: Wallet) : ViewModel() {
2034
fun onAction(action: SendScreenAction) {
2135
when (action) {

Variant-Esplora/app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/WalletViewModel.kt

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,37 @@
66
package org.bitcoindevkit.devkitwallet.presentation.viewmodels
77

88
import android.util.Log
9-
import androidx.compose.runtime.getValue
10-
import androidx.compose.runtime.mutableStateOf
11-
import androidx.compose.runtime.setValue
129
import androidx.lifecycle.ViewModel
1310
import androidx.lifecycle.viewModelScope
1411
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.flow.MutableStateFlow
13+
import kotlinx.coroutines.flow.StateFlow
14+
import kotlinx.coroutines.flow.asStateFlow
15+
import kotlinx.coroutines.flow.update
1516
import kotlinx.coroutines.launch
1617
import kotlinx.coroutines.withContext
1718
import org.bitcoindevkit.devkitwallet.domain.CurrencyUnit
1819
import org.bitcoindevkit.devkitwallet.domain.Wallet
19-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenAction
20-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState
2120

2221
private const val TAG = "WalletViewModel"
2322

23+
data class WalletScreenState(
24+
val balance: ULong = 0u,
25+
val syncing: Boolean = false,
26+
val unit: CurrencyUnit = CurrencyUnit.Bitcoin,
27+
val esploraEndpoint: String = "",
28+
)
29+
30+
sealed interface WalletScreenAction {
31+
data object UpdateBalance : WalletScreenAction
32+
data object SwitchUnit : WalletScreenAction
33+
}
34+
2435
class WalletViewModel(
2536
private val wallet: Wallet,
2637
) : ViewModel() {
27-
var state: WalletScreenState by mutableStateOf(WalletScreenState())
28-
private set
38+
private val _state: MutableStateFlow<WalletScreenState> = MutableStateFlow(WalletScreenState())
39+
val state: StateFlow<WalletScreenState> = _state.asStateFlow()
2940

3041
init {
3142
updateClientEndpoint()
@@ -39,20 +50,23 @@ class WalletViewModel(
3950
}
4051

4152
private fun switchUnit() {
42-
state = when (state.unit) {
43-
CurrencyUnit.Bitcoin -> state.copy(unit = CurrencyUnit.Satoshi)
44-
CurrencyUnit.Satoshi -> state.copy(unit = CurrencyUnit.Bitcoin)
53+
_state.update { state ->
54+
when (state.unit) {
55+
CurrencyUnit.Bitcoin -> state.copy(unit = CurrencyUnit.Satoshi)
56+
CurrencyUnit.Satoshi -> state.copy(unit = CurrencyUnit.Bitcoin)
57+
}
4558
}
4659
}
4760

4861
private fun updateBalance() {
49-
state = state.copy(syncing = true)
62+
_state.update { it.copy(syncing = true) }
63+
5064
viewModelScope.launch(Dispatchers.IO) {
5165
wallet.sync()
5266
withContext(Dispatchers.Main) {
5367
val newBalance = wallet.getBalance()
5468
Log.i(TAG, "New balance: $newBalance")
55-
state = state.copy(balance = newBalance, syncing = false)
69+
_state.update { it.copy(balance = newBalance, syncing = false) }
5670
}
5771
}
5872
}
@@ -61,7 +75,7 @@ class WalletViewModel(
6175
viewModelScope.launch(Dispatchers.IO) {
6276
val endpoint = wallet.getClientEndpoint()
6377
withContext(Dispatchers.Main) {
64-
state = state.copy(esploraEndpoint = endpoint)
78+
_state.update { it.copy(esploraEndpoint = endpoint) }
6579
}
6680
}
6781
}

0 commit comments

Comments
 (0)