android: make currentDir reactive

-The composables were reading the currentDir value once and not observing it. This fixes it so that we recompose when the StateFlow changes.
-Use commit() instead of apply() when writing to EncryptedSharedPreferences since we are reading from it immediately and need the writes to happen synchronously
-Remove unused function in PermissionsViewModel

Fixes tailscale/corp#29283

Signed-off-by: kari-ts <kari@tailscale.com>
pull/661/head
kari-ts 6 months ago
parent 87f0e9754b
commit 4b0a3af69a

@ -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)

@ -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())

@ -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)
})

@ -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<Uri?>,
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(

@ -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<String?>(TaildropDirectoryStore.loadSavedDir().toString())
MutableStateFlow<String?>(TaildropDirectoryStore.loadSavedDir()?.toString())
val currentDir: StateFlow<String?> = _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 dont 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
}
}

Loading…
Cancel
Save