diff --git a/android/src/main/java/com/tailscale/ipn/MainActivity.kt b/android/src/main/java/com/tailscale/ipn/MainActivity.kt index c7c8d60..8f72dcf 100644 --- a/android/src/main/java/com/tailscale/ipn/MainActivity.kt +++ b/android/src/main/java/com/tailscale/ipn/MainActivity.kt @@ -25,6 +25,7 @@ import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.animation.core.LinearOutSlowInEasing @@ -85,6 +86,7 @@ import com.tailscale.ipn.ui.view.UserSwitcherView import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav import com.tailscale.ipn.ui.viewModel.MainViewModel import com.tailscale.ipn.ui.viewModel.MainViewModelFactory +import com.tailscale.ipn.ui.viewModel.PermissionsViewModel import com.tailscale.ipn.ui.viewModel.PingViewModel import com.tailscale.ipn.ui.viewModel.SettingsNav import com.tailscale.ipn.ui.viewModel.VpnViewModel @@ -105,6 +107,7 @@ class MainActivity : ComponentActivity() { ViewModelProvider(this, MainViewModelFactory(vpnViewModel)).get(MainViewModel::class.java) } private lateinit var vpnViewModel: VpnViewModel + val permissionsViewModel: PermissionsViewModel by viewModels() companion object { private const val TAG = "Main Activity" @@ -179,6 +182,7 @@ class MainActivity : ComponentActivity() { try { Libtailscale.setDirectFileRoot(uri.toString()) TaildropDirectoryStore.saveFileDirectory(uri) + permissionsViewModel.refreshCurrentDir() } catch (e: Exception) { TSLog.e("MainActivity", "Failed to set Taildrop root: $e") } @@ -333,7 +337,8 @@ class MainActivity : ComponentActivity() { { navController.navigate("notifications") }) } composable("taildropDir") { - TaildropDirView(backTo("permissions"), directoryPickerLauncher) + TaildropDirView( + backTo("permissions"), directoryPickerLauncher, permissionsViewModel) } composable("notifications") { NotificationsView(backTo("permissions"), ::openApplicationSettings) diff --git a/android/src/main/java/com/tailscale/ipn/TaildropDirectoryStore.kt b/android/src/main/java/com/tailscale/ipn/TaildropDirectoryStore.kt index 02bbaee..be02c95 100644 --- a/android/src/main/java/com/tailscale/ipn/TaildropDirectoryStore.kt +++ b/android/src/main/java/com/tailscale/ipn/TaildropDirectoryStore.kt @@ -15,7 +15,7 @@ object TaildropDirectoryStore { @Throws(IOException::class, GeneralSecurityException::class) fun saveFileDirectory(directoryUri: Uri) { val prefs = App.get().getEncryptedPrefs() - prefs.edit().putString(PREF_KEY_SAF_URI, directoryUri.toString()).apply() + prefs.edit().putString(PREF_KEY_SAF_URI, directoryUri.toString()).commit() try { // Must restart Tailscale because a new LocalBackend with the new directory must be created. App.get().startLibtailscale(directoryUri.toString()) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/PermissionsView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/PermissionsView.kt index 6c19939..3a5a83d 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/PermissionsView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/PermissionsView.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -73,7 +74,9 @@ fun PermissionsView( }, supportingContent = { val displayPath = - permissionsViewModel.currentDir.value?.let { friendlyDirName(it) } ?: "No access" + permissionsViewModel.currentDir.collectAsState().value?.let { + friendlyDirName(it) + } ?: "No access" Text(displayPath) }) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/TaildropDirView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/TaildropDirView.kt index 4996b66..ba9510e 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/TaildropDirView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/TaildropDirView.kt @@ -15,21 +15,23 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.tailscale.ipn.R import com.tailscale.ipn.ui.theme.exitNodeToggleButton import com.tailscale.ipn.ui.util.Lists import com.tailscale.ipn.ui.util.friendlyDirName import com.tailscale.ipn.ui.viewModel.PermissionsViewModel +import com.tailscale.ipn.util.TSLog @Composable fun TaildropDirView( backToPermissionsView: BackNavigation, openDirectoryLauncher: ActivityResultLauncher, - permissionsViewModel: PermissionsViewModel = viewModel() + permissionsViewModel: PermissionsViewModel ) { Scaffold( topBar = { @@ -53,7 +55,8 @@ fun TaildropDirView( item("divider0") { Lists.SectionDivider() } item { - val currentDir = permissionsViewModel.currentDir.value + val currentDir by permissionsViewModel.currentDir.collectAsState() + TSLog.d("TaildropDirView", "currentDir in UI: $currentDir") val displayPath = currentDir?.let { friendlyDirName(it) } ?: "No access" ListItem( diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/PermissionsViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/PermissionsViewModel.kt index 46f2ea9..507ccc5 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/PermissionsViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/PermissionsViewModel.kt @@ -3,37 +3,20 @@ package com.tailscale.ipn.ui.viewModel -import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.tailscale.ipn.TaildropDirectoryStore +import com.tailscale.ipn.util.TSLog import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import libtailscale.Libtailscale - -class PermissionsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { +class PermissionsViewModel : ViewModel() { private val _currentDir = - MutableStateFlow(TaildropDirectoryStore.loadSavedDir().toString()) + MutableStateFlow(TaildropDirectoryStore.loadSavedDir()?.toString()) val currentDir: StateFlow = _currentDir - fun onDirectoryPicked(uri: Uri?, context: Context) { - if (uri == null) return - - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - val cr = context.contentResolver - - // Revoke previous grant so you don’t leak one - _currentDir.value?.let { old -> - runCatching { cr.releasePersistableUriPermission(Uri.parse(old), flags) } - } - - cr.takePersistableUriPermission(uri, flags) // may throw SecurityException - Libtailscale.setDirectFileRoot(uri.toString()) - TaildropDirectoryStore.saveFileDirectory(uri) - - _currentDir.value = uri.toString() + fun refreshCurrentDir() { + val newUri = TaildropDirectoryStore.loadSavedDir()?.toString() + TSLog.d("PermissionsViewModel", "refreshCurrentDir: $newUri") + _currentDir.value = newUri } }