Skip to content

Commit

Permalink
fix: reopening a typst project folder crashing because of scoped stor…
Browse files Browse the repository at this point in the history
…age permission issues (fixed via anggrayudi's SimpleStorage lib) (fixes soupslurpr#149)

Signed-off-by: Stephen L. <[email protected]>
  • Loading branch information
lrq3000 committed May 20, 2024
1 parent e889b7b commit 6c19289
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 6 deletions.
10 changes: 10 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,6 @@ dependencies {

implementation("androidx.datastore:datastore-preferences:1.1.0")
implementation("net.java.dev.jna:jna:5.14.0@aar")

implementation("com.anggrayudi:storage:1.5.5")
}
39 changes: 38 additions & 1 deletion app/src/main/kotlin/dev/soupslurpr/beautyxt/BeauTyXT.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import android.net.Uri
import android.os.Build
import android.print.PrintAttributes
import android.print.PrintManager
import android.util.Log
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -71,6 +73,12 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.anggrayudi.storage.SimpleStorageHelper
import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.FileFullPath
import com.anggrayudi.storage.file.StorageId
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.makeFile
import dev.soupslurpr.beautyxt.constants.mimeTypeDocx
import dev.soupslurpr.beautyxt.constants.mimeTypeHtml
import dev.soupslurpr.beautyxt.constants.mimeTypeMarkdown
Expand Down Expand Up @@ -493,6 +501,7 @@ fun FileTypeSelectionDialogItem(

@Composable
fun BeauTyXTApp(
activity: MainActivity,
modifier: Modifier,
fileViewModel: FileViewModel,
typstProjectViewModel: TypstProjectViewModel,
Expand Down Expand Up @@ -1331,7 +1340,35 @@ ${
)
},
onOpenTypstProjectButtonClicked = {
openTypstProjectLauncher.launch(Uri.EMPTY)
//openTypstProjectLauncher.launch(Uri.EMPTY)
activity.storageHelper.onFolderSelected =
{ _, projectFolder -> // could also use simpleStorageHelper.onStorageAccessGranted()
Log.d("APP", "Callback Success Folder Pick! Now requesting " +
"permission from" +
" OS and launch the Rust service...")
// Get absolute path to folder
val fpath = projectFolder.getAbsolutePath(activity)
// Request write access to the folder using DocumentFileCompat (this
// is the magic sauce to avoid crashing upon opening an already existing folder, and
// yes it is counter-intuitive that we use an object called
// DocumentFileCompat to request write access on a folder)
val folder = DocumentFileCompat.fromFullPath(activity,
fpath,
requiresWriteAccess = true)
// We can create any file with the following method
//val file = folder?.makeFile(activity, "notes", "text/plain")
typstProjectViewModel.bindService(projectFolder.uri)
navController.navigate(BeauTyXTScreens.TypstProject.name)
}
Log.d("APP", "Ask user to get Storage Access permission")
activity.storageHelper.openFolderPicker(
// We could also use simpleStorageHelper.requestStorageAccess()
//initialPath = FileFullPath(
// activity,
// StorageId.PRIMARY,
// "TypstProjects"
//)
)
},
onOpenAnyButtonClicked = {
openFileLauncher.launch(
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/kotlin/dev/soupslurpr/beautyxt/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import dev.soupslurpr.beautyxt.ui.FileViewModel
import dev.soupslurpr.beautyxt.ui.ReviewPrivacyPolicyAndLicense
import dev.soupslurpr.beautyxt.ui.TypstProjectViewModel
import dev.soupslurpr.beautyxt.ui.theme.BeauTyXTTheme
import com.anggrayudi.storage.SimpleStorageHelper

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

class MainActivity : ComponentActivity() {
val storageHelper = SimpleStorageHelper(this) // for scoped storage permission management on Android 10+
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -88,6 +90,7 @@ class MainActivity : ComponentActivity() {
ReviewPrivacyPolicyAndLicense(preferencesViewModel = preferencesViewModel)
} else if (preferencesUiState.acceptedPrivacyPolicyAndLicense.second.value) {
BeauTyXTApp(
activity = this@MainActivity, // provide the parent, MainActivity
modifier = Modifier,
fileViewModel = fileViewModel,
typstProjectViewModel = typstProjectViewModel,
Expand All @@ -99,4 +102,23 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onSaveInstanceState(outState: Bundle) {
// Save scoped storage permission on Android 10+
storageHelper.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
// Restore scoped storage permission on Android 10+
super.onRestoreInstanceState(savedInstanceState)
storageHelper.onRestoreInstanceState(savedInstanceState)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
// Restore scoped storage permission on Android 10+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// Mandatory for Activity, but not for Fragment & ComponentActivity
storageHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.io.FileOutputStream
import com.anggrayudi.storage.SimpleStorageHelper
import com.anggrayudi.storage.file.*

private const val TAG = "TypstProjectViewModel"

Expand All @@ -45,9 +47,22 @@ class TypstProjectViewModel(application: Application) : AndroidViewModel(applica

private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val rustService = ITypstProjectViewModelRustLibraryAidlInterface.Stub.asInterface(service)
var rustService = ITypstProjectViewModelRustLibraryAidlInterface.Stub.asInterface(service)

rustService.initializeTypstWorld()
try {
rustService?.initializeTypstWorld()
} catch (e: DeadObjectException) {
Log.w(
TAG,
"Failed to call initializeTypstWorld(), seems like the service" +
" is dead $e"
)
// If service is dead, we try to kill and restart it
stopAndUnbindService()
rustService = ITypstProjectViewModelRustLibraryAidlInterface.Stub.asInterface(service)
rustService!!.initializeTypstWorld()
bindService(_uiState.value.projectFolderUri.value)
}

this@TypstProjectViewModel.rustService = rustService

Expand Down Expand Up @@ -376,7 +391,7 @@ class TypstProjectViewModel(application: Application) : AndroidViewModel(applica
"Failed to call clearTypstProjectFiles(), might have already cleared as service is dead $e"
)
}
stopAndUnbindService()
//stopAndUnbindService()
}
}

Expand Down

0 comments on commit 6c19289

Please sign in to comment.