Skip to content

Commit 76f8d36

Browse files
committed
Simplify effect with LaunchedEffectCollect
1 parent 88f3906 commit 76f8d36

13 files changed

+89
-121
lines changed

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt

+4-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.compose.material3.AlertDialog
88
import androidx.compose.material3.MaterialTheme
99
import androidx.compose.material3.Text
1010
import androidx.compose.runtime.Composable
11-
import androidx.compose.runtime.LaunchedEffect
1211
import androidx.compose.runtime.getValue
1312
import androidx.compose.ui.Modifier
1413
import androidx.compose.ui.graphics.Color
@@ -22,6 +21,7 @@ import net.mullvad.mullvadvpn.R
2221
import net.mullvad.mullvadvpn.compose.button.NegativeButton
2322
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
2423
import net.mullvad.mullvadvpn.compose.textfield.DnsTextField
24+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
2525
import net.mullvad.mullvadvpn.lib.theme.AppTheme
2626
import net.mullvad.mullvadvpn.lib.theme.Dimens
2727
import net.mullvad.mullvadvpn.viewmodel.DnsDialogSideEffect
@@ -100,11 +100,9 @@ fun DnsDialog(
100100
val viewModel =
101101
koinViewModel<DnsDialogViewModel>(parameters = { parametersOf(initialValue, index) })
102102

103-
LaunchedEffect(Unit) {
104-
viewModel.uiSideEffect.collect {
105-
when (it) {
106-
DnsDialogSideEffect.Complete -> resultNavigator.navigateBack(result = true)
107-
}
103+
LaunchedEffectCollect(viewModel.uiSideEffect) {
104+
when (it) {
105+
DnsDialogSideEffect.Complete -> resultNavigator.navigateBack(result = true)
108106
}
109107
}
110108
val state by viewModel.uiState.collectAsStateWithLifecycle()

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialog.kt

+4-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.compose.material3.AlertDialog
88
import androidx.compose.material3.MaterialTheme
99
import androidx.compose.material3.Text
1010
import androidx.compose.runtime.Composable
11-
import androidx.compose.runtime.LaunchedEffect
1211
import androidx.compose.runtime.mutableStateOf
1312
import androidx.compose.runtime.remember
1413
import androidx.compose.ui.Modifier
@@ -21,6 +20,7 @@ import com.ramcosta.composedestinations.spec.DestinationStyle
2120
import net.mullvad.mullvadvpn.R
2221
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
2322
import net.mullvad.mullvadvpn.compose.textfield.MtuTextField
23+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
2424
import net.mullvad.mullvadvpn.constant.MTU_MAX_VALUE
2525
import net.mullvad.mullvadvpn.constant.MTU_MIN_VALUE
2626
import net.mullvad.mullvadvpn.lib.theme.AppTheme
@@ -42,11 +42,9 @@ private fun PreviewMtuDialog() {
4242
fun MtuDialog(mtuInitial: Int?, navigator: DestinationsNavigator) {
4343
val viewModel = koinViewModel<MtuDialogViewModel>()
4444

45-
LaunchedEffect(Unit) {
46-
viewModel.uiSideEffect.collect {
47-
when (it) {
48-
MtuDialogSideEffect.Complete -> navigator.navigateUp()
49-
}
45+
LaunchedEffectCollect(viewModel.uiSideEffect) {
46+
when (it) {
47+
MtuDialogSideEffect.Complete -> navigator.navigateUp()
5048
}
5149
}
5250
MtuDialog(

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/payment/PaymentDialog.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.ramcosta.composedestinations.spec.DestinationStyle
2020
import net.mullvad.mullvadvpn.R
2121
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
2222
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorMedium
23+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
2324
import net.mullvad.mullvadvpn.lib.payment.model.ProductId
2425
import net.mullvad.mullvadvpn.lib.theme.AppTheme
2526
import net.mullvad.mullvadvpn.lib.theme.color.AlphaDescription
@@ -125,12 +126,10 @@ fun Payment(productId: ProductId, resultBackNavigator: ResultBackNavigator<Boole
125126
val vm = koinViewModel<PaymentViewModel>()
126127
val state by vm.uiState.collectAsStateWithLifecycle()
127128

128-
LaunchedEffect(Unit) {
129-
vm.uiSideEffect.collect {
130-
when (it) {
131-
is PaymentUiSideEffect.PaymentCancelled ->
132-
resultBackNavigator.navigateBack(result = false)
133-
}
129+
LaunchedEffectCollect(vm.uiSideEffect) {
130+
when (it) {
131+
is PaymentUiSideEffect.PaymentCancelled ->
132+
resultBackNavigator.navigateBack(result = false)
134133
}
135134
}
136135

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt

+13-15
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import androidx.compose.material3.MaterialTheme
1616
import androidx.compose.material3.SnackbarHostState
1717
import androidx.compose.material3.Text
1818
import androidx.compose.runtime.Composable
19-
import androidx.compose.runtime.LaunchedEffect
2019
import androidx.compose.runtime.getValue
2120
import androidx.compose.runtime.remember
2221
import androidx.compose.ui.Alignment
@@ -55,6 +54,7 @@ import net.mullvad.mullvadvpn.compose.destinations.RedeemVoucherDestination
5554
import net.mullvad.mullvadvpn.compose.destinations.VerificationPendingDialogDestination
5655
import net.mullvad.mullvadvpn.compose.state.PaymentState
5756
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromBottomTransition
57+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
5858
import net.mullvad.mullvadvpn.compose.util.SecureScreenWhileInView
5959
import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
6060
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
@@ -171,22 +171,20 @@ fun AccountScreen(
171171
val clipboardManager = LocalClipboardManager.current
172172
val snackbarHostState = remember { SnackbarHostState() }
173173
val copyTextString = stringResource(id = R.string.copied_mullvad_account_number)
174-
LaunchedEffect(Unit) {
175-
uiSideEffect.collect { uiSideEffect ->
176-
when (uiSideEffect) {
177-
AccountViewModel.UiSideEffect.NavigateToLogin -> navigateToLogin()
178-
is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser ->
179-
context.openAccountPageInBrowser(uiSideEffect.token)
180-
is AccountViewModel.UiSideEffect.CopyAccountNumber ->
181-
launch {
182-
clipboardManager.setText(AnnotatedString(uiSideEffect.accountNumber))
174+
LaunchedEffectCollect(uiSideEffect) { sideEffect ->
175+
when (sideEffect) {
176+
AccountViewModel.UiSideEffect.NavigateToLogin -> navigateToLogin()
177+
is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser ->
178+
context.openAccountPageInBrowser(sideEffect.token)
179+
is AccountViewModel.UiSideEffect.CopyAccountNumber ->
180+
launch {
181+
clipboardManager.setText(AnnotatedString(sideEffect.accountNumber))
183182

184-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
185-
snackbarHostState.currentSnackbarData?.dismiss()
186-
snackbarHostState.showSnackbar(message = copyTextString)
187-
}
183+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
184+
snackbarHostState.currentSnackbarData?.dismiss()
185+
snackbarHostState.showSnackbar(message = copyTextString)
188186
}
189-
}
187+
}
190188
}
191189
}
192190

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import androidx.compose.ui.platform.LocalContext
3838
import androidx.compose.ui.platform.testTag
3939
import androidx.compose.ui.res.stringResource
4040
import androidx.compose.ui.tooling.preview.Preview
41-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4241
import androidx.lifecycle.Lifecycle
42+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4343
import com.ramcosta.composedestinations.annotation.Destination
4444
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
4545
import com.ramcosta.composedestinations.navigation.popUpTo

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt

+7-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.size
99
import androidx.compose.material3.MaterialTheme
1010
import androidx.compose.material3.Text
1111
import androidx.compose.runtime.Composable
12-
import androidx.compose.runtime.LaunchedEffect
1312
import androidx.compose.runtime.getValue
1413
import androidx.compose.ui.Modifier
1514
import androidx.compose.ui.graphics.Color
@@ -32,6 +31,7 @@ import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
3231
import net.mullvad.mullvadvpn.compose.destinations.LoginDestination
3332
import net.mullvad.mullvadvpn.compose.destinations.SettingsDestination
3433
import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState
34+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
3535
import net.mullvad.mullvadvpn.lib.theme.AppTheme
3636
import net.mullvad.mullvadvpn.lib.theme.Dimens
3737
import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedSideEffect
@@ -51,16 +51,13 @@ fun DeviceRevoked(navigator: DestinationsNavigator) {
5151

5252
val state by viewModel.uiState.collectAsStateWithLifecycle()
5353

54-
LaunchedEffect(Unit) {
55-
viewModel.uiSideEffect.collect { sideEffect ->
56-
when (sideEffect) {
57-
DeviceRevokedSideEffect.NavigateToLogin -> {
58-
navigator.navigate(LoginDestination()) {
59-
launchSingleTop = true
60-
popUpTo(NavGraphs.root) { inclusive = true }
61-
}
54+
LaunchedEffectCollect(viewModel.uiSideEffect) { sideEffect ->
55+
when (sideEffect) {
56+
DeviceRevokedSideEffect.NavigateToLogin ->
57+
navigator.navigate(LoginDestination()) {
58+
launchSingleTop = true
59+
popUpTo(NavGraphs.root) { inclusive = true }
6260
}
63-
}
6461
}
6562
}
6663

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt

+4-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import androidx.compose.material3.MaterialTheme
1717
import androidx.compose.material3.Scaffold
1818
import androidx.compose.material3.Text
1919
import androidx.compose.runtime.Composable
20-
import androidx.compose.runtime.LaunchedEffect
2120
import androidx.compose.runtime.getValue
2221
import androidx.compose.runtime.mutableStateOf
2322
import androidx.compose.runtime.saveable.rememberSaveable
@@ -39,6 +38,7 @@ import net.mullvad.mullvadvpn.compose.cell.ExpandableComposeCell
3938
import net.mullvad.mullvadvpn.compose.cell.SelectableCell
4039
import net.mullvad.mullvadvpn.compose.state.RelayFilterState
4140
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
41+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
4242
import net.mullvad.mullvadvpn.lib.theme.AppTheme
4343
import net.mullvad.mullvadvpn.lib.theme.Dimens
4444
import net.mullvad.mullvadvpn.model.Ownership
@@ -72,11 +72,9 @@ fun FilterScreen(navigator: DestinationsNavigator) {
7272
val viewModel = koinViewModel<FilterViewModel>()
7373
val state by viewModel.uiState.collectAsStateWithLifecycle()
7474

75-
LaunchedEffect(Unit) {
76-
viewModel.uiSideEffect.collect {
77-
when (it) {
78-
FilterScreenSideEffect.CloseScreen -> navigator.navigateUp()
79-
}
75+
LaunchedEffectCollect(viewModel.uiSideEffect) {
76+
when (it) {
77+
FilterScreenSideEffect.CloseScreen -> navigator.navigateUp()
8078
}
8179
}
8280
FilterScreen(

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt

-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ import net.mullvad.mullvadvpn.compose.util.accountTokenVisualTransformation
8080
import net.mullvad.mullvadvpn.lib.theme.AppTheme
8181
import net.mullvad.mullvadvpn.lib.theme.Dimens
8282
import net.mullvad.mullvadvpn.lib.theme.color.AlphaTopBar
83-
import net.mullvad.mullvadvpn.util.CollectSideEffectWithLifecycle
8483
import net.mullvad.mullvadvpn.viewmodel.LoginUiSideEffect
8584
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
8685
import org.koin.androidx.compose.koinViewModel

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt

+12-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package net.mullvad.mullvadvpn.compose.screen
33
import androidx.compose.foundation.layout.fillMaxSize
44
import androidx.compose.runtime.Composable
55
import androidx.compose.runtime.DisposableEffect
6-
import androidx.compose.runtime.LaunchedEffect
76
import androidx.compose.ui.ExperimentalComposeUiApi
87
import androidx.compose.ui.Modifier
98
import androidx.compose.ui.semantics.semantics
@@ -14,14 +13,14 @@ import com.ramcosta.composedestinations.navigation.navigate
1413
import com.ramcosta.composedestinations.navigation.popBackStack
1514
import com.ramcosta.composedestinations.rememberNavHostEngine
1615
import com.ramcosta.composedestinations.utils.destination
17-
import kotlinx.coroutines.flow.collect
1816
import kotlinx.coroutines.flow.first
1917
import kotlinx.coroutines.flow.map
2018
import net.mullvad.mullvadvpn.compose.NavGraphs
2119
import net.mullvad.mullvadvpn.compose.destinations.ChangelogDestination
2220
import net.mullvad.mullvadvpn.compose.destinations.ConnectDestination
2321
import net.mullvad.mullvadvpn.compose.destinations.NoDaemonScreenDestination
2422
import net.mullvad.mullvadvpn.compose.destinations.OutOfTimeDestination
23+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
2524
import net.mullvad.mullvadvpn.viewmodel.ChangelogViewModel
2625
import net.mullvad.mullvadvpn.viewmodel.DaemonScreenEvent
2726
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
@@ -50,28 +49,23 @@ fun MullvadApp() {
5049
)
5150

5251
// Globally handle daemon dropped connection with NoDaemonScreen
53-
LaunchedEffect(Unit) {
54-
serviceVm.uiSideEffect.collect {
55-
when (it) {
56-
DaemonScreenEvent.Show ->
57-
navController.navigate(NoDaemonScreenDestination) { launchSingleTop = true }
58-
DaemonScreenEvent.Remove ->
59-
navController.popBackStack(NoDaemonScreenDestination, true)
60-
}
52+
LaunchedEffectCollect(serviceVm.uiSideEffect) {
53+
when (it) {
54+
DaemonScreenEvent.Show ->
55+
navController.navigate(NoDaemonScreenDestination) { launchSingleTop = true }
56+
DaemonScreenEvent.Remove -> navController.popBackStack(NoDaemonScreenDestination, true)
6157
}
6258
}
6359

6460
// Globally show the changelog
6561
val changeLogsViewModel = koinViewModel<ChangelogViewModel>()
66-
LaunchedEffect(Unit) {
67-
changeLogsViewModel.uiSideEffect.collect {
62+
LaunchedEffectCollect(changeLogsViewModel.uiSideEffect) {
6863

69-
// Wait until we are in an acceptable destination
70-
navController.currentBackStackEntryFlow
71-
.map { it.destination() }
72-
.first { it in changeLogDestinations }
64+
// Wait until we are in an acceptable destination
65+
navController.currentBackStackEntryFlow
66+
.map { it.destination() }
67+
.first { it in changeLogDestinations }
7368

74-
navController.navigate(ChangelogDestination(it).route)
75-
}
69+
navController.navigate(ChangelogDestination(it).route)
7670
}
7771
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt

+20-27
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ import androidx.compose.foundation.verticalScroll
1717
import androidx.compose.material3.MaterialTheme
1818
import androidx.compose.material3.Text
1919
import androidx.compose.runtime.Composable
20-
import androidx.compose.runtime.LaunchedEffect
2120
import androidx.compose.runtime.getValue
22-
import androidx.compose.runtime.rememberCoroutineScope
2321
import androidx.compose.ui.Alignment
2422
import androidx.compose.ui.Modifier
2523
import androidx.compose.ui.graphics.Color
@@ -48,6 +46,7 @@ import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
4846
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
4947
import net.mullvad.mullvadvpn.compose.destinations.LoginDestination
5048
import net.mullvad.mullvadvpn.compose.destinations.SplashDestination
49+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
5150
import net.mullvad.mullvadvpn.compose.util.toDp
5251
import net.mullvad.mullvadvpn.constant.DAEMON_READY_TIMEOUT_MS
5352
import net.mullvad.mullvadvpn.lib.theme.AppTheme
@@ -74,36 +73,30 @@ fun PrivacyDisclaimer(
7473
val state by viewModel.uiState.collectAsStateWithLifecycle()
7574

7675
val context = LocalContext.current
77-
val scope = rememberCoroutineScope()
78-
LaunchedEffect(Unit) {
79-
viewModel.uiSideEffect.collect {
80-
when (it) {
81-
PrivacyDisclaimerUiSideEffect.NavigateToLogin -> {
82-
navigator.navigate(LoginDestination(null)) {
83-
launchSingleTop = true
84-
popUpTo(NavGraphs.root) { inclusive = true }
85-
}
76+
LaunchedEffectCollect(viewModel.uiSideEffect) {
77+
when (it) {
78+
PrivacyDisclaimerUiSideEffect.NavigateToLogin ->
79+
navigator.navigate(LoginDestination(null)) {
80+
launchSingleTop = true
81+
popUpTo(NavGraphs.root) { inclusive = true }
8682
}
87-
PrivacyDisclaimerUiSideEffect.StartService -> {
88-
scope.launch {
89-
try {
90-
withTimeout(DAEMON_READY_TIMEOUT_MS) {
91-
(context as MainActivity).startServiceSuspend()
92-
}
93-
viewModel.onServiceStartedSuccessful()
94-
} catch (e: CancellationException) {
95-
// Timeout
96-
viewModel.onServiceStartedTimeout()
83+
PrivacyDisclaimerUiSideEffect.StartService ->
84+
launch {
85+
try {
86+
withTimeout(DAEMON_READY_TIMEOUT_MS) {
87+
(context as MainActivity).startServiceSuspend()
9788
}
89+
viewModel.onServiceStartedSuccessful()
90+
} catch (e: CancellationException) {
91+
// Timeout
92+
viewModel.onServiceStartedTimeout()
9893
}
9994
}
100-
PrivacyDisclaimerUiSideEffect.NavigateToSplash -> {
101-
navigator.navigate(SplashDestination) {
102-
launchSingleTop = true
103-
popUpTo(NavGraphs.root) { inclusive = true }
104-
}
95+
PrivacyDisclaimerUiSideEffect.NavigateToSplash ->
96+
navigator.navigate(SplashDestination) {
97+
launchSingleTop = true
98+
popUpTo(NavGraphs.root) { inclusive = true }
10599
}
106-
}
107100
}
108101
}
109102
PrivacyDisclaimerScreen(state, {}, viewModel::setPrivacyDisclosureAccepted)

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt

+5-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.compose.material3.MaterialTheme
1414
import androidx.compose.material3.Text
1515
import androidx.compose.material3.TextField
1616
import androidx.compose.runtime.Composable
17-
import androidx.compose.runtime.LaunchedEffect
1817
import androidx.compose.runtime.getValue
1918
import androidx.compose.runtime.remember
2019
import androidx.compose.ui.Alignment
@@ -42,6 +41,7 @@ import net.mullvad.mullvadvpn.compose.destinations.ReportProblemNoEmailDialogDes
4241
import net.mullvad.mullvadvpn.compose.destinations.ViewLogsDestination
4342
import net.mullvad.mullvadvpn.compose.textfield.mullvadWhiteTextFieldColors
4443
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
44+
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
4545
import net.mullvad.mullvadvpn.compose.util.SecureScreenWhileInView
4646
import net.mullvad.mullvadvpn.dataproxy.SendProblemReportResult
4747
import net.mullvad.mullvadvpn.lib.theme.AppTheme
@@ -102,13 +102,10 @@ fun ReportProblem(
102102
val vm = koinViewModel<ReportProblemViewModel>()
103103
val state by vm.uiState.collectAsStateWithLifecycle()
104104

105-
LaunchedEffect(Unit) {
106-
vm.uiSideEffect.collect {
107-
when (it) {
108-
is ReportProblemSideEffect.ShowConfirmNoEmail -> {
109-
navigator.navigate(ReportProblemNoEmailDialogDestination)
110-
}
111-
}
105+
LaunchedEffectCollect(vm.uiSideEffect) {
106+
when (it) {
107+
is ReportProblemSideEffect.ShowConfirmNoEmail ->
108+
navigator.navigate(ReportProblemNoEmailDialogDestination)
112109
}
113110
}
114111

0 commit comments

Comments
 (0)