Skip to content

Commit 1ec621c

Browse files
authored
android: make currentDir reactive (#661)
-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 <[email protected]>
1 parent 87f0e97 commit 1ec621c

File tree

5 files changed

+24
-30
lines changed

5 files changed

+24
-30
lines changed

android/src/main/java/com/tailscale/ipn/MainActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.activity.compose.setContent
2525
import androidx.activity.result.ActivityResultLauncher
2626
import androidx.activity.result.contract.ActivityResultContract
2727
import androidx.activity.result.contract.ActivityResultContracts
28+
import androidx.activity.viewModels
2829
import androidx.annotation.RequiresApi
2930
import androidx.browser.customtabs.CustomTabsIntent
3031
import androidx.compose.animation.core.LinearOutSlowInEasing
@@ -85,6 +86,7 @@ import com.tailscale.ipn.ui.view.UserSwitcherView
8586
import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav
8687
import com.tailscale.ipn.ui.viewModel.MainViewModel
8788
import com.tailscale.ipn.ui.viewModel.MainViewModelFactory
89+
import com.tailscale.ipn.ui.viewModel.PermissionsViewModel
8890
import com.tailscale.ipn.ui.viewModel.PingViewModel
8991
import com.tailscale.ipn.ui.viewModel.SettingsNav
9092
import com.tailscale.ipn.ui.viewModel.VpnViewModel
@@ -105,6 +107,7 @@ class MainActivity : ComponentActivity() {
105107
ViewModelProvider(this, MainViewModelFactory(vpnViewModel)).get(MainViewModel::class.java)
106108
}
107109
private lateinit var vpnViewModel: VpnViewModel
110+
val permissionsViewModel: PermissionsViewModel by viewModels()
108111

109112
companion object {
110113
private const val TAG = "Main Activity"
@@ -179,6 +182,7 @@ class MainActivity : ComponentActivity() {
179182
try {
180183
Libtailscale.setDirectFileRoot(uri.toString())
181184
TaildropDirectoryStore.saveFileDirectory(uri)
185+
permissionsViewModel.refreshCurrentDir()
182186
} catch (e: Exception) {
183187
TSLog.e("MainActivity", "Failed to set Taildrop root: $e")
184188
}
@@ -333,7 +337,8 @@ class MainActivity : ComponentActivity() {
333337
{ navController.navigate("notifications") })
334338
}
335339
composable("taildropDir") {
336-
TaildropDirView(backTo("permissions"), directoryPickerLauncher)
340+
TaildropDirView(
341+
backTo("permissions"), directoryPickerLauncher, permissionsViewModel)
337342
}
338343
composable("notifications") {
339344
NotificationsView(backTo("permissions"), ::openApplicationSettings)

android/src/main/java/com/tailscale/ipn/TaildropDirectoryStore.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object TaildropDirectoryStore {
1515
@Throws(IOException::class, GeneralSecurityException::class)
1616
fun saveFileDirectory(directoryUri: Uri) {
1717
val prefs = App.get().getEncryptedPrefs()
18-
prefs.edit().putString(PREF_KEY_SAF_URI, directoryUri.toString()).apply()
18+
prefs.edit().putString(PREF_KEY_SAF_URI, directoryUri.toString()).commit()
1919
try {
2020
// Must restart Tailscale because a new LocalBackend with the new directory must be created.
2121
App.get().startLibtailscale(directoryUri.toString())

android/src/main/java/com/tailscale/ipn/ui/view/PermissionsView.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.material3.MaterialTheme
1313
import androidx.compose.material3.Scaffold
1414
import androidx.compose.material3.Text
1515
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.collectAsState
1617
import androidx.compose.ui.Modifier
1718
import androidx.compose.ui.res.painterResource
1819
import androidx.compose.ui.res.stringResource
@@ -73,7 +74,9 @@ fun PermissionsView(
7374
},
7475
supportingContent = {
7576
val displayPath =
76-
permissionsViewModel.currentDir.value?.let { friendlyDirName(it) } ?: "No access"
77+
permissionsViewModel.currentDir.collectAsState().value?.let {
78+
friendlyDirName(it)
79+
} ?: "No access"
7780

7881
Text(displayPath)
7982
})

android/src/main/java/com/tailscale/ipn/ui/view/TaildropDirView.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,23 @@ import androidx.compose.material3.MaterialTheme
1515
import androidx.compose.material3.Scaffold
1616
import androidx.compose.material3.Text
1717
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.collectAsState
19+
import androidx.compose.runtime.getValue
1820
import androidx.compose.ui.Modifier
1921
import androidx.compose.ui.res.stringResource
2022
import androidx.compose.ui.unit.dp
21-
import androidx.lifecycle.viewmodel.compose.viewModel
2223
import com.tailscale.ipn.R
2324
import com.tailscale.ipn.ui.theme.exitNodeToggleButton
2425
import com.tailscale.ipn.ui.util.Lists
2526
import com.tailscale.ipn.ui.util.friendlyDirName
2627
import com.tailscale.ipn.ui.viewModel.PermissionsViewModel
28+
import com.tailscale.ipn.util.TSLog
2729

2830
@Composable
2931
fun TaildropDirView(
3032
backToPermissionsView: BackNavigation,
3133
openDirectoryLauncher: ActivityResultLauncher<Uri?>,
32-
permissionsViewModel: PermissionsViewModel = viewModel()
34+
permissionsViewModel: PermissionsViewModel
3335
) {
3436
Scaffold(
3537
topBar = {
@@ -53,7 +55,8 @@ fun TaildropDirView(
5355
item("divider0") { Lists.SectionDivider() }
5456

5557
item {
56-
val currentDir = permissionsViewModel.currentDir.value
58+
val currentDir by permissionsViewModel.currentDir.collectAsState()
59+
TSLog.d("TaildropDirView", "currentDir in UI: $currentDir")
5760
val displayPath = currentDir?.let { friendlyDirName(it) } ?: "No access"
5861

5962
ListItem(

android/src/main/java/com/tailscale/ipn/ui/viewModel/PermissionsViewModel.kt

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,20 @@
33

44
package com.tailscale.ipn.ui.viewModel
55

6-
import android.content.Context
7-
import android.content.Intent
8-
import android.net.Uri
9-
import androidx.lifecycle.SavedStateHandle
106
import androidx.lifecycle.ViewModel
117
import com.tailscale.ipn.TaildropDirectoryStore
8+
import com.tailscale.ipn.util.TSLog
129
import kotlinx.coroutines.flow.MutableStateFlow
1310
import kotlinx.coroutines.flow.StateFlow
14-
import libtailscale.Libtailscale
15-
16-
class PermissionsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
1711

12+
class PermissionsViewModel : ViewModel() {
1813
private val _currentDir =
19-
MutableStateFlow<String?>(TaildropDirectoryStore.loadSavedDir().toString())
14+
MutableStateFlow<String?>(TaildropDirectoryStore.loadSavedDir()?.toString())
2015
val currentDir: StateFlow<String?> = _currentDir
2116

22-
fun onDirectoryPicked(uri: Uri?, context: Context) {
23-
if (uri == null) return
24-
25-
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
26-
val cr = context.contentResolver
27-
28-
// Revoke previous grant so you don’t leak one
29-
_currentDir.value?.let { old ->
30-
runCatching { cr.releasePersistableUriPermission(Uri.parse(old), flags) }
31-
}
32-
33-
cr.takePersistableUriPermission(uri, flags) // may throw SecurityException
34-
Libtailscale.setDirectFileRoot(uri.toString())
35-
TaildropDirectoryStore.saveFileDirectory(uri)
36-
37-
_currentDir.value = uri.toString()
17+
fun refreshCurrentDir() {
18+
val newUri = TaildropDirectoryStore.loadSavedDir()?.toString()
19+
TSLog.d("PermissionsViewModel", "refreshCurrentDir: $newUri")
20+
_currentDir.value = newUri
3821
}
3922
}

0 commit comments

Comments
 (0)