@@ -34,10 +34,18 @@ import androidx.compose.animation.fadeIn
34
34
import androidx.compose.animation.fadeOut
35
35
import androidx.compose.animation.slideInHorizontally
36
36
import androidx.compose.animation.slideOutHorizontally
37
+ import androidx.compose.material3.AlertDialog
37
38
import androidx.compose.material3.MaterialTheme
38
39
import androidx.compose.material3.Surface
40
+ import androidx.compose.material3.Text
41
+ import androidx.compose.runtime.LaunchedEffect
39
42
import androidx.compose.runtime.collectAsState
43
+ import androidx.compose.runtime.getValue
44
+ import androidx.compose.runtime.mutableStateOf
45
+ import androidx.compose.runtime.remember
46
+ import androidx.compose.runtime.setValue
40
47
import androidx.compose.ui.Modifier
48
+ import androidx.compose.ui.res.stringResource
41
49
import androidx.core.net.toUri
42
50
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
43
51
import androidx.lifecycle.ViewModelProvider
@@ -75,39 +83,41 @@ import com.tailscale.ipn.ui.view.MullvadInfoView
75
83
import com.tailscale.ipn.ui.view.NotificationsView
76
84
import com.tailscale.ipn.ui.view.PeerDetails
77
85
import com.tailscale.ipn.ui.view.PermissionsView
86
+ import com.tailscale.ipn.ui.view.PrimaryActionButton
78
87
import com.tailscale.ipn.ui.view.RunExitNodeView
79
88
import com.tailscale.ipn.ui.view.SearchView
80
89
import com.tailscale.ipn.ui.view.SettingsView
81
90
import com.tailscale.ipn.ui.view.SplitTunnelAppPickerView
82
91
import com.tailscale.ipn.ui.view.SubnetRoutingView
83
92
import com.tailscale.ipn.ui.view.TaildropDirView
93
+ import com.tailscale.ipn.ui.view.TaildropDirectoryPickerPrompt
84
94
import com.tailscale.ipn.ui.view.TailnetLockSetupView
85
95
import com.tailscale.ipn.ui.view.UserSwitcherNav
86
96
import com.tailscale.ipn.ui.view.UserSwitcherView
97
+ import com.tailscale.ipn.ui.viewModel.AppViewModel
87
98
import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav
88
99
import com.tailscale.ipn.ui.viewModel.MainViewModel
89
100
import com.tailscale.ipn.ui.viewModel.MainViewModelFactory
90
101
import com.tailscale.ipn.ui.viewModel.PermissionsViewModel
91
102
import com.tailscale.ipn.ui.viewModel.PingViewModel
92
103
import com.tailscale.ipn.ui.viewModel.SettingsNav
93
- import com.tailscale.ipn.ui.viewModel.VpnViewModel
104
+ import com.tailscale.ipn.util.ShareFileHelper
94
105
import com.tailscale.ipn.util.TSLog
95
106
import kotlinx.coroutines.Dispatchers
96
107
import kotlinx.coroutines.cancel
97
108
import kotlinx.coroutines.flow.MutableStateFlow
98
109
import kotlinx.coroutines.flow.StateFlow
99
110
import kotlinx.coroutines.launch
100
- import libtailscale.Libtailscale
101
111
102
112
class MainActivity : ComponentActivity () {
103
113
private lateinit var navController: NavHostController
104
114
private lateinit var vpnPermissionLauncher: ActivityResultLauncher <Intent >
105
115
private val viewModel: MainViewModel by lazy {
106
116
val app = App .get()
107
- vpnViewModel = app.getAppScopedViewModel()
108
- ViewModelProvider (this , MainViewModelFactory (vpnViewModel )).get(MainViewModel ::class .java)
117
+ appViewModel = app.getAppScopedViewModel()
118
+ ViewModelProvider (this , MainViewModelFactory (appViewModel )).get(MainViewModel ::class .java)
109
119
}
110
- private lateinit var vpnViewModel : VpnViewModel
120
+ private lateinit var appViewModel : AppViewModel
111
121
val permissionsViewModel: PermissionsViewModel by viewModels()
112
122
113
123
companion object {
@@ -132,7 +142,7 @@ class MainActivity : ComponentActivity() {
132
142
133
143
// grab app to make sure it initializes
134
144
App .get()
135
- vpnViewModel = ViewModelProvider (App .get()).get(VpnViewModel ::class .java)
145
+ appViewModel = ViewModelProvider (App .get()).get(AppViewModel ::class .java)
136
146
137
147
val rm = getSystemService(Context .RESTRICTIONS_SERVICE ) as RestrictionsManager
138
148
MDMSettings .update(App .get(), rm)
@@ -154,15 +164,15 @@ class MainActivity : ComponentActivity() {
154
164
registerForActivityResult(VpnPermissionContract ()) { granted ->
155
165
if (granted) {
156
166
TSLog .d(" VpnPermission" , " VPN permission granted" )
157
- vpnViewModel .setVpnPrepared(true )
167
+ appViewModel .setVpnPrepared(true )
158
168
App .get().startVPN()
159
169
} else {
160
170
if (isAnotherVpnActive(this )) {
161
171
TSLog .d(" VpnPermission" , " Another VPN is likely active" )
162
172
showOtherVPNConflictDialog()
163
173
} else {
164
174
TSLog .d(" VpnPermission" , " Permission was denied by the user" )
165
- vpnViewModel .setVpnPrepared(false )
175
+ appViewModel .setVpnPrepared(false )
166
176
167
177
AlertDialog .Builder (this )
168
178
.setTitle(R .string.vpn_permission_needed)
@@ -198,9 +208,10 @@ class MainActivity : ComponentActivity() {
198
208
199
209
lifecycleScope.launch(Dispatchers .IO ) {
200
210
try {
201
- Libtailscale .setDirectFileRoot(uri.toString())
202
211
TaildropDirectoryStore .saveFileDirectory(uri)
203
212
permissionsViewModel.refreshCurrentDir()
213
+ ShareFileHelper .notifyDirectoryReady()
214
+ ShareFileHelper .setUri(uri.toString())
204
215
} catch (e: Exception ) {
205
216
TSLog .e(" MainActivity" , " Failed to set Taildrop root: $e " )
206
217
}
@@ -219,9 +230,38 @@ class MainActivity : ComponentActivity() {
219
230
}
220
231
}
221
232
222
- viewModel.setDirectoryPickerLauncher( directoryPickerLauncher)
233
+ appViewModel. directoryPickerLauncher = directoryPickerLauncher
223
234
224
235
setContent {
236
+ var showDialog by remember { mutableStateOf(false ) }
237
+
238
+ LaunchedEffect (Unit ) {
239
+ appViewModel.showDirectoryPickerInterstitial.collect { showDialog = true }
240
+ }
241
+
242
+ if (showDialog) {
243
+ AppTheme {
244
+ AlertDialog (
245
+ onDismissRequest = {
246
+ showDialog = false
247
+ appViewModel.directoryPickerLauncher?.launch(null )
248
+ },
249
+ title = {
250
+ Text (text = stringResource(id = R .string.taildrop_directory_picker_title))
251
+ },
252
+ text = { TaildropDirectoryPickerPrompt () },
253
+ confirmButton = {
254
+ PrimaryActionButton (
255
+ onClick = {
256
+ showDialog = false
257
+ appViewModel.directoryPickerLauncher?.launch(null )
258
+ }) {
259
+ Text (text = stringResource(id = R .string.taildrop_directory_picker_button))
260
+ }
261
+ })
262
+ }
263
+ }
264
+
225
265
navController = rememberNavController()
226
266
227
267
AppTheme {
@@ -308,7 +348,11 @@ class MainActivity : ComponentActivity() {
308
348
onNavigateToAuthKey = { navController.navigate(" loginWithAuthKey" ) })
309
349
310
350
composable(" main" , enterTransition = { fadeIn(animationSpec = tween(150 )) }) {
311
- MainView (loginAtUrl = ::login, navigation = mainViewNav, viewModel = viewModel)
351
+ MainView (
352
+ loginAtUrl = ::login,
353
+ navigation = mainViewNav,
354
+ viewModel = viewModel,
355
+ appViewModel = appViewModel)
312
356
}
313
357
composable(" search" ) {
314
358
val autoFocus = viewModel.autoFocusSearch
0 commit comments