@@ -9,9 +9,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
9
9
import androidx.compose.foundation.layout.height
10
10
import androidx.compose.foundation.layout.padding
11
11
import androidx.compose.foundation.lazy.LazyColumn
12
+ import androidx.compose.material3.ExperimentalMaterial3Api
13
+ import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
12
14
import androidx.compose.material3.MaterialTheme
13
15
import androidx.compose.material3.Text
14
16
import androidx.compose.runtime.Composable
17
+ import androidx.compose.runtime.CompositionLocalProvider
15
18
import androidx.compose.runtime.collectAsState
16
19
import androidx.compose.runtime.getValue
17
20
import androidx.compose.runtime.remember
@@ -30,13 +33,15 @@ import net.mullvad.mullvadvpn.compose.cell.HeaderCell
30
33
import net.mullvad.mullvadvpn.compose.cell.HeaderSwitchComposeCell
31
34
import net.mullvad.mullvadvpn.compose.cell.SplitTunnelingCell
32
35
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge
36
+ import net.mullvad.mullvadvpn.compose.component.MullvadSwitch
33
37
import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
34
38
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
35
39
import net.mullvad.mullvadvpn.compose.constant.CommonContentKey
36
40
import net.mullvad.mullvadvpn.compose.constant.ContentType
37
41
import net.mullvad.mullvadvpn.compose.constant.SplitTunnelingContentKey
38
42
import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
39
43
import net.mullvad.mullvadvpn.compose.extensions.itemsIndexedWithDivider
44
+ import net.mullvad.mullvadvpn.compose.state.AppListState
40
45
import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState
41
46
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
42
47
import net.mullvad.mullvadvpn.lib.theme.AppTheme
@@ -51,29 +56,32 @@ private fun PreviewSplitTunnelingScreen() {
51
56
AppTheme {
52
57
SplitTunnelingScreen (
53
58
uiState =
54
- SplitTunnelingUiState .ShowAppList (
55
- excludedApps =
56
- listOf (
57
- AppData (
58
- packageName = " my.package.a" ,
59
- name = " TitleA" ,
60
- iconRes = R .drawable.icon_alert,
61
- ),
62
- AppData (
63
- packageName = " my.package.b" ,
64
- name = " TitleB" ,
65
- iconRes = R .drawable.icon_chevron,
66
- )
67
- ),
68
- includedApps =
69
- listOf (
70
- AppData (
71
- packageName = " my.package.c" ,
72
- name = " TitleC" ,
73
- iconRes = R .drawable.icon_alert
74
- )
75
- ),
76
- showSystemApps = true
59
+ SplitTunnelingUiState (
60
+ appListState =
61
+ AppListState .ShowAppList (
62
+ excludedApps =
63
+ listOf (
64
+ AppData (
65
+ packageName = " my.package.a" ,
66
+ name = " TitleA" ,
67
+ iconRes = R .drawable.icon_alert
68
+ ),
69
+ AppData (
70
+ packageName = " my.package.b" ,
71
+ name = " TitleB" ,
72
+ iconRes = R .drawable.icon_chevron
73
+ )
74
+ ),
75
+ includedApps =
76
+ listOf (
77
+ AppData (
78
+ packageName = " my.package.c" ,
79
+ name = " TitleC" ,
80
+ iconRes = R .drawable.icon_alert
81
+ )
82
+ ),
83
+ showSystemApps = true
84
+ )
77
85
)
78
86
)
79
87
}
@@ -88,6 +96,7 @@ fun SplitTunneling(navigator: DestinationsNavigator) {
88
96
val packageManager = remember(context) { context.packageManager }
89
97
SplitTunnelingScreen (
90
98
uiState = state,
99
+ onShowSplitTunneling = viewModel::enableSplitTunneling,
91
100
onShowSystemAppsClick = viewModel::onShowSystemAppsClick,
92
101
onExcludeAppClick = viewModel::onExcludeAppClick,
93
102
onIncludeAppClick = viewModel::onIncludeAppClick,
@@ -99,20 +108,29 @@ fun SplitTunneling(navigator: DestinationsNavigator) {
99
108
}
100
109
101
110
@Composable
102
- @OptIn(ExperimentalFoundationApi ::class )
111
+ @OptIn(ExperimentalFoundationApi ::class , ExperimentalMaterial3Api :: class )
103
112
fun SplitTunnelingScreen (
104
- uiState : SplitTunnelingUiState = SplitTunnelingUiState .Loading ,
113
+ uiState : SplitTunnelingUiState = SplitTunnelingUiState (),
114
+ onShowSplitTunneling : (Boolean ) -> Unit = {},
105
115
onShowSystemAppsClick : (show: Boolean ) -> Unit = {},
106
116
onExcludeAppClick : (packageName: String ) -> Unit = {},
107
117
onIncludeAppClick : (packageName: String ) -> Unit = {},
108
118
onBackClick : () -> Unit = {},
109
- onResolveIcon : (String ) -> Bitmap ? = { null },
119
+ onResolveIcon : (String ) -> Bitmap ? = { null }
110
120
) {
111
121
val focusManager = LocalFocusManager .current
112
122
113
123
ScaffoldWithMediumTopBar (
114
124
modifier = Modifier .fillMaxSize(),
115
125
appBarTitle = stringResource(id = R .string.split_tunneling),
126
+ switch = {
127
+ CompositionLocalProvider (LocalMinimumInteractiveComponentEnforcement provides false ) {
128
+ MullvadSwitch (
129
+ checked = uiState.enabled,
130
+ onCheckedChange = { newValue -> onShowSplitTunneling(newValue) }
131
+ )
132
+ }
133
+ },
116
134
navigationIcon = { NavigateBackIconButton (onBackClick) }
117
135
) { modifier, lazyListState ->
118
136
LazyColumn (
@@ -134,14 +152,14 @@ fun SplitTunnelingScreen(
134
152
)
135
153
}
136
154
}
137
- when (uiState) {
138
- SplitTunnelingUiState .Loading -> {
155
+ when (val appList = uiState.appListState ) {
156
+ AppListState .Loading -> {
139
157
item(key = CommonContentKey .PROGRESS , contentType = ContentType .PROGRESS ) {
140
158
MullvadCircularProgressIndicatorLarge ()
141
159
}
142
160
}
143
- is SplitTunnelingUiState .ShowAppList -> {
144
- if (uiState .excludedApps.isNotEmpty()) {
161
+ is AppListState .ShowAppList -> {
162
+ if (appList .excludedApps.isNotEmpty()) {
145
163
itemWithDivider(
146
164
key = SplitTunnelingContentKey .EXCLUDED_APPLICATIONS ,
147
165
contentType = ContentType .HEADER
@@ -153,7 +171,7 @@ fun SplitTunnelingScreen(
153
171
)
154
172
}
155
173
itemsIndexedWithDivider(
156
- items = uiState .excludedApps,
174
+ items = appList .excludedApps,
157
175
key = { _, listItem -> listItem.packageName },
158
176
contentType = { _, _ -> ContentType .ITEM }
159
177
) { index, listItem ->
@@ -166,7 +184,7 @@ fun SplitTunnelingScreen(
166
184
) {
167
185
// Move focus down unless the clicked item was the last in this
168
186
// section.
169
- if (index < uiState .excludedApps.size - 1 ) {
187
+ if (index < appList .excludedApps.size - 1 ) {
170
188
focusManager.moveFocus(FocusDirection .Down )
171
189
} else {
172
190
focusManager.moveFocus(FocusDirection .Up )
@@ -189,7 +207,7 @@ fun SplitTunnelingScreen(
189
207
) {
190
208
HeaderSwitchComposeCell (
191
209
title = stringResource(id = R .string.show_system_apps),
192
- isToggled = uiState .showSystemApps,
210
+ isToggled = appList .showSystemApps,
193
211
onCellClicked = { newValue -> onShowSystemAppsClick(newValue) },
194
212
modifier = Modifier .animateItemPlacement()
195
213
)
@@ -205,7 +223,7 @@ fun SplitTunnelingScreen(
205
223
)
206
224
}
207
225
itemsIndexedWithDivider(
208
- items = uiState .includedApps,
226
+ items = appList .includedApps,
209
227
key = { _, listItem -> listItem.packageName },
210
228
contentType = { _, _ -> ContentType .ITEM }
211
229
) { index, listItem ->
@@ -218,7 +236,7 @@ fun SplitTunnelingScreen(
218
236
) {
219
237
// Move focus down unless the clicked item was the last in this
220
238
// section.
221
- if (index < uiState .includedApps.size - 1 ) {
239
+ if (index < appList .includedApps.size - 1 ) {
222
240
focusManager.moveFocus(FocusDirection .Down )
223
241
} else {
224
242
focusManager.moveFocus(FocusDirection .Up )
@@ -228,6 +246,7 @@ fun SplitTunnelingScreen(
228
246
}
229
247
}
230
248
}
249
+ AppListState .Disabled -> {}
231
250
}
232
251
}
233
252
}
0 commit comments