44package  com.tailscale.ipn.ui.view 
55
66import  androidx.compose.foundation.Image 
7+ import  androidx.compose.foundation.background 
8+ import  androidx.compose.foundation.layout.Row 
79import  androidx.compose.foundation.layout.height 
810import  androidx.compose.foundation.layout.padding 
911import  androidx.compose.foundation.layout.width 
1012import  androidx.compose.foundation.lazy.LazyColumn 
1113import  androidx.compose.foundation.lazy.items 
14+ import  androidx.compose.material.icons.Icons 
15+ import  androidx.compose.material.icons.filled.MoreVert 
16+ import  androidx.compose.material3.AlertDialog 
1217import  androidx.compose.material3.Checkbox 
18+ import  androidx.compose.material3.DropdownMenu 
19+ import  androidx.compose.material3.Icon 
20+ import  androidx.compose.material3.IconButton 
1321import  androidx.compose.material3.ListItem 
1422import  androidx.compose.material3.MaterialTheme 
1523import  androidx.compose.material3.Scaffold 
@@ -27,6 +35,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
2735import  com.tailscale.ipn.App 
2836import  com.tailscale.ipn.R 
2937import  com.tailscale.ipn.ui.util.Lists 
38+ import  com.tailscale.ipn.ui.util.set 
3039import  com.tailscale.ipn.ui.viewModel.SplitTunnelAppPickerViewModel 
3140
3241@Composable
@@ -35,23 +44,39 @@ fun SplitTunnelAppPickerView(
3544    model :  SplitTunnelAppPickerViewModel  = viewModel()
3645) {
3746  val  installedApps by model.installedApps.collectAsState()
38-   val  excludedPackageNames by model.excludedPackageNames.collectAsState()
47+   val  selectedPackageNames by model.selectedPackageNames.collectAsState()
48+   val  allowSelected by model.allowSelected.collectAsState()
3949  val  builtInDisallowedPackageNames:  List <String > =  App .get().builtInDisallowedPackageNames
4050  val  mdmIncludedPackages by model.mdmIncludedPackages.collectAsState()
4151  val  mdmExcludedPackages by model.mdmExcludedPackages.collectAsState()
52+   val  showHeaderMenu by model.showHeaderMenu.collectAsState()
53+   val  showSwitchDialog by model.showSwitchDialog.collectAsState()
4254
43-   Scaffold (topBar =  { Header (titleRes =  R .string.split_tunneling, onBack =  backToSettings) }) {
44-       innerPadding -> 
45-     LazyColumn (modifier =  Modifier .padding(innerPadding)) {
46-       item(key =  " header"  ) {
47-         ListItem (
48-             headlineContent =  {
49-               Text (
50-                   stringResource(
51-                       R .string
52-                           .selected_apps_will_access_the_internet_directly_without_using_tailscale))
55+   if  (showSwitchDialog) {
56+     SwitchAlertDialog (
57+         onConfirm =  {
58+           model.showSwitchDialog.set(false )
59+           model.performSelectionSwitch()
60+         },
61+         onDismiss =  { model.showSwitchDialog.set(false ) })
62+   }
63+ 
64+   Scaffold (
65+       topBar =  {
66+         Header (
67+             titleRes =  R .string.split_tunneling,
68+             onBack =  backToSettings,
69+             actions =  {
70+               Row  {
71+                 FusMenu (viewModel =  model, onSwitchClick =  { model.showSwitchDialog.set(true ) })
72+                 IconButton (onClick =  { model.showHeaderMenu.set(! showHeaderMenu) }) {
73+                   Icon (Icons .Default .MoreVert , " menu"  )
74+                 }
75+               }
5376            })
54-       }
77+       },
78+   ) { innerPadding -> 
79+     LazyColumn (modifier =  Modifier .padding(innerPadding)) {
5580      if  (mdmExcludedPackages.value?.isNotEmpty() ==  true ) {
5681        item(" mdmExcludedNotice"  ) {
5782          ListItem (
@@ -67,9 +92,22 @@ fun SplitTunnelAppPickerView(
6792              })
6893        }
6994      } else  {
95+         item(" header"  ) {
96+           ListItem (
97+               headlineContent =  {
98+                 Text (
99+                     stringResource(
100+                         if  (allowSelected) R .string.selected_apps_will_access_tailscale
101+                         else 
102+                             R .string
103+                                 .selected_apps_will_access_the_internet_directly_without_using_tailscale))
104+               })
105+         }
70106        item(" resolversHeader"  ) {
71107          Lists .SectionDivider (
72-               stringResource(R .string.count_excluded_apps, excludedPackageNames.count()))
108+               stringResource(
109+                   if  (allowSelected) R .string.count_included_apps else  R .string.count_excluded_apps,
110+                   selectedPackageNames.count()))
73111        }
74112        items(installedApps) { app -> 
75113          ListItem (
@@ -93,13 +131,13 @@ fun SplitTunnelAppPickerView(
93131              },
94132              trailingContent =  {
95133                Checkbox (
96-                     checked =  excludedPackageNames .contains(app.packageName),
134+                     checked =  selectedPackageNames .contains(app.packageName),
97135                    enabled =  ! builtInDisallowedPackageNames.contains(app.packageName),
98136                    onCheckedChange =  { checked -> 
99137                      if  (checked) {
100-                         model.exclude (packageName =  app.packageName)
138+                         model.select (packageName =  app.packageName)
101139                      } else  {
102-                         model.unexclude (packageName =  app.packageName)
140+                         model.deselect (packageName =  app.packageName)
103141                      }
104142                    })
105143              })
@@ -109,3 +147,40 @@ fun SplitTunnelAppPickerView(
109147    }
110148  }
111149}
150+ 
151+ @Composable
152+ fun  FusMenu (viewModel :  SplitTunnelAppPickerViewModel , onSwitchClick :  (() ->  Unit )) {
153+   val  expanded by viewModel.showHeaderMenu.collectAsState()
154+   val  allowSelected by viewModel.allowSelected.collectAsState()
155+ 
156+   DropdownMenu (
157+       expanded =  expanded,
158+       onDismissRequest =  { viewModel.showHeaderMenu.set(false ) },
159+       modifier =  Modifier .background(MaterialTheme .colorScheme.surfaceContainer)) {
160+         MenuItem (
161+             onClick =  {
162+               viewModel.showHeaderMenu.set(false )
163+               onSwitchClick()
164+             },
165+             text = 
166+                 stringResource(
167+                     if  (allowSelected) R .string.switch_to_select_to_exclude
168+                     else  R .string.switch_to_select_to_include))
169+       }
170+ }
171+ 
172+ @Composable
173+ fun  SwitchAlertDialog (onConfirm :  (() ->  Unit ), onDismiss :  (() ->  Unit )) {
174+   AlertDialog (
175+       title =  { Text (text =  stringResource(R .string.switch_warning_dialog_title)) },
176+       text =  { Text (text =  stringResource(R .string.switch_warning_dialog_description)) },
177+       onDismissRequest =  onDismiss,
178+       confirmButton =  {
179+         WarningActionButton (onClick =  onConfirm) {
180+           Text (text =  stringResource(R .string.confirm_switch))
181+         }
182+       },
183+       dismissButton =  {
184+         DismissActionButton (onClick =  onDismiss) { Text (text =  stringResource(R .string.cancel)) }
185+       })
186+ }
0 commit comments