mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-21 12:02:33 +01:00
Android compatibility WIP 2
- Shared file chooser and saver api - Categories window/screen support
This commit is contained in:
@@ -13,6 +13,9 @@ accompanist = "0.20.1"
|
|||||||
kamel = "0.3.0"
|
kamel = "0.3.0"
|
||||||
materialDialogs = "0.6.4"
|
materialDialogs = "0.6.4"
|
||||||
|
|
||||||
|
# Android
|
||||||
|
activityCompose = "1.3.1"
|
||||||
|
|
||||||
# Swing
|
# Swing
|
||||||
darklaf = "2.7.3"
|
darklaf = "2.7.3"
|
||||||
|
|
||||||
@@ -61,6 +64,9 @@ accompanistFlowLayout = { module = "ca.gosyer:accompanist-flowlayout", version.r
|
|||||||
kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
|
kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
|
||||||
materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
||||||
|
|
||||||
|
# Android
|
||||||
|
activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||||
|
|
||||||
# Swing
|
# Swing
|
||||||
darklaf = { module = "com.github.weisj:darklaf-core", version.ref = "darklaf" }
|
darklaf = { module = "com.github.weisj:darklaf-core", version.ref = "darklaf" }
|
||||||
|
|
||||||
@@ -99,4 +105,4 @@ desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref =
|
|||||||
|
|
||||||
# Localization
|
# Localization
|
||||||
mokoCore = { module = "dev.icerock.moko:resources", version.ref = "moko" }
|
mokoCore = { module = "dev.icerock.moko:resources", version.ref = "moko" }
|
||||||
mokoCompose= { module = "dev.icerock.moko:resources-compose", version.ref = "moko" }
|
mokoCompose = { module = "dev.icerock.moko:resources-compose", version.ref = "moko" }
|
||||||
@@ -82,6 +82,7 @@ kotlin {
|
|||||||
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
|
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
|
||||||
dependencies {
|
dependencies {
|
||||||
api(kotlin("stdlib-jdk8"))
|
api(kotlin("stdlib-jdk8"))
|
||||||
|
api(libs.activityCompose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val androidTest by getting {
|
val androidTest by getting {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import okio.Path
|
||||||
|
import okio.Path.Companion.toOkioPath
|
||||||
|
|
||||||
|
actual class FileChooser(private val resultLauncher: ManagedActivityResultLauncher<String, Uri?>) {
|
||||||
|
actual fun launch(extension: String) {
|
||||||
|
resultLauncher.launch(MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberFileChooser(onFileFound: (Path) -> Unit): FileChooser {
|
||||||
|
val result = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
|
it?.toFile()?.toOkioPath()?.let(onFileFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember { FileChooser(result) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import okio.Path
|
||||||
|
import okio.Path.Companion.toOkioPath
|
||||||
|
|
||||||
|
actual class FileSaver(
|
||||||
|
private val resultLauncher: ManagedActivityResultLauncher<String, Uri?>,
|
||||||
|
) {
|
||||||
|
actual fun save(name: String) {
|
||||||
|
resultLauncher.launch(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberFileSaver(
|
||||||
|
onFileSelected: (Path) -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
onError: () -> Unit,
|
||||||
|
): FileSaver {
|
||||||
|
val result = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument()) {
|
||||||
|
if (it != null) {
|
||||||
|
it.toFile().toOkioPath().let(onFileSelected)
|
||||||
|
} else {
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember { FileSaver(result) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.categories
|
||||||
|
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
|
actual fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator) {
|
||||||
|
navigator push CategoriesScreen(notifyFinished)
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import ca.gosyer.core.lang.launchDefault
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import okio.Path
|
||||||
|
import okio.Path.Companion.toOkioPath
|
||||||
|
import javax.swing.JFileChooser
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter
|
||||||
|
|
||||||
|
actual class FileChooser(private val onFileFound: (Path) -> Unit, private val scope: CoroutineScope) {
|
||||||
|
private val fileChooser = JFileChooser()
|
||||||
|
.apply {
|
||||||
|
val details = actionMap.get("viewTypeDetails")
|
||||||
|
details?.actionPerformed(null)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun launch(extension: String) {
|
||||||
|
scope.launchDefault {
|
||||||
|
fileChooser.fileFilter = FileNameExtensionFilter("$extension file", extension)
|
||||||
|
when (fileChooser.showOpenDialog(null)) {
|
||||||
|
JFileChooser.APPROVE_OPTION -> onFileFound(fileChooser.selectedFile.toOkioPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberFileChooser(onFileFound: (Path) -> Unit): FileChooser {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
return remember { FileChooser(onFileFound, coroutineScope) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import ca.gosyer.core.lang.launchDefault
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import okio.Path
|
||||||
|
import okio.Path.Companion.toOkioPath
|
||||||
|
import javax.swing.JFileChooser
|
||||||
|
|
||||||
|
actual class FileSaver(
|
||||||
|
private val onFileSelected: (Path) -> Unit,
|
||||||
|
private val onCancel: () -> Unit,
|
||||||
|
private val onError: () -> Unit,
|
||||||
|
private val scope: CoroutineScope
|
||||||
|
) {
|
||||||
|
private val fileChooser = JFileChooser()
|
||||||
|
.apply {
|
||||||
|
val details = actionMap.get("viewTypeDetails")
|
||||||
|
details?.actionPerformed(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun save(name: String) {
|
||||||
|
scope.launchDefault {
|
||||||
|
fileChooser.selectedFile = fileChooser.currentDirectory.resolve(name)
|
||||||
|
when (fileChooser.showSaveDialog(null)) {
|
||||||
|
JFileChooser.APPROVE_OPTION -> onFileSelected(fileChooser.selectedFile.toOkioPath())
|
||||||
|
JFileChooser.CANCEL_OPTION -> onCancel()
|
||||||
|
JFileChooser.ERROR_OPTION -> onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberFileSaver(
|
||||||
|
onFileSelected: (Path) -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
onError: () -> Unit,
|
||||||
|
): FileSaver {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
return remember { FileSaver(onFileSelected, onCancel, onError, coroutineScope) }
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import cafe.adriel.voyager.navigator.Navigator
|
|||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun openCategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
actual fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator) {
|
||||||
launchApplication {
|
launchApplication {
|
||||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
||||||
ThemedWindow(::exitApplication, title = "${BuildKonfig.NAME} - Categories") {
|
ThemedWindow(::exitApplication, title = "${BuildKonfig.NAME} - Categories") {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import okio.Path
|
||||||
|
|
||||||
|
expect class FileChooser {
|
||||||
|
fun launch(extension: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun rememberFileChooser(onFileFound: (Path) -> Unit): FileChooser
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.file
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import okio.Path
|
||||||
|
|
||||||
|
expect class FileSaver {
|
||||||
|
fun save(name: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun rememberFileSaver(
|
||||||
|
onFileSelected: (Path) -> Unit,
|
||||||
|
onCancel: () -> Unit = {},
|
||||||
|
onError: () -> Unit = {},
|
||||||
|
): FileSaver
|
||||||
@@ -13,6 +13,9 @@ import ca.gosyer.uicore.vm.viewModel
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
|
expect fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator)
|
||||||
|
|
||||||
class CategoriesScreen(
|
class CategoriesScreen(
|
||||||
@Transient
|
@Transient
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ import ca.gosyer.i18n.MR
|
|||||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||||
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
|
||||||
|
import ca.gosyer.ui.base.file.rememberFileChooser
|
||||||
|
import ca.gosyer.ui.base.file.rememberFileSaver
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.util.system.filePicker
|
|
||||||
import ca.gosyer.ui.util.system.fileSaver
|
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import ca.gosyer.uicore.vm.ContextWrapper
|
import ca.gosyer.uicore.vm.ContextWrapper
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
@@ -66,6 +66,8 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import okio.FileSystem
|
import okio.FileSystem
|
||||||
import okio.Path
|
import okio.Path
|
||||||
@@ -90,7 +92,8 @@ class SettingsBackupScreen : Screen {
|
|||||||
restoreFile = vm::restoreFile,
|
restoreFile = vm::restoreFile,
|
||||||
restoreBackup = vm::restoreBackup,
|
restoreBackup = vm::restoreBackup,
|
||||||
stopRestore = vm::stopRestore,
|
stopRestore = vm::stopRestore,
|
||||||
exportBackup = vm::exportBackup
|
exportBackup = vm::exportBackup,
|
||||||
|
exportBackupFileFound = vm::exportBackupFileFound
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,13 +117,13 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
val creatingProgress = _creatingProgress.asStateFlow()
|
val creatingProgress = _creatingProgress.asStateFlow()
|
||||||
private val _creatingStatus = MutableStateFlow<Status>(Status.Nothing)
|
private val _creatingStatus = MutableStateFlow<Status>(Status.Nothing)
|
||||||
internal val creatingStatus = _creatingStatus.asStateFlow()
|
internal val creatingStatus = _creatingStatus.asStateFlow()
|
||||||
private val _createFlow = MutableSharedFlow<Pair<String, (Path) -> Unit>>()
|
private val _createFlow = MutableSharedFlow<String>()
|
||||||
val createFlow = _createFlow.asSharedFlow()
|
val createFlow = _createFlow.asSharedFlow()
|
||||||
|
|
||||||
fun restoreFile(file: Path?) {
|
fun restoreFile(file: Path) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (file == null || !FileSystem.SYSTEM.exists(file)) {
|
if (!FileSystem.SYSTEM.exists(file)) {
|
||||||
info { "Invalid file ${file?.toString()}" }
|
info { "Invalid file ${file.toString()}" }
|
||||||
_restoreStatus.value = Status.Error
|
_restoreStatus.value = Status.Error
|
||||||
_restoring.value = false
|
_restoring.value = false
|
||||||
} else {
|
} else {
|
||||||
@@ -167,6 +170,9 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
_restoring.value = false
|
_restoring.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val tempFile = MutableStateFlow<Path?>(null)
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
fun exportBackup() {
|
fun exportBackup() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
_creatingStatus.value = Status.Nothing
|
_creatingStatus.value = Status.Nothing
|
||||||
@@ -181,31 +187,56 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
info(e) { "Error exporting backup" }
|
info(e) { "Error exporting backup" }
|
||||||
_creatingStatus.value = Status.Error
|
_creatingStatus.value = Status.Error
|
||||||
|
_creating.value = false
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
_creatingProgress.value = 1.0F
|
_creatingProgress.value = 1.0F
|
||||||
if (backup != null && backup.status.isSuccess()) {
|
if (backup != null && backup.status.isSuccess()) {
|
||||||
_createFlow.emit(
|
val filename = backup.headers["content-disposition"]?.substringAfter("filename=")?.trim('"') ?: "backup"
|
||||||
(backup.headers["content-disposition"]?.substringAfter("filename=")?.trim('"') ?: "backup") to {
|
tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also {
|
||||||
scope.launch {
|
launch {
|
||||||
try {
|
try {
|
||||||
backup.content.toInputStream()
|
backup.content.toInputStream()
|
||||||
.source()
|
.source()
|
||||||
.copyTo(
|
.copyTo(
|
||||||
FileSystem.SYSTEM.sink(it).buffer()
|
FileSystem.SYSTEM.sink(it).buffer()
|
||||||
)
|
)
|
||||||
_creatingStatus.value = Status.Success
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
e.throwIfCancellation()
|
||||||
e.throwIfCancellation()
|
error(e) { "Error creating backup" }
|
||||||
error(e) { "Error creating backup" }
|
_creatingStatus.value = Status.Error
|
||||||
_creatingStatus.value = Status.Error
|
_creating.value = false
|
||||||
} finally {
|
} finally {
|
||||||
_creating.value = false
|
mutex.unlock()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
mutex.tryLock()
|
||||||
|
_createFlow.emit(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportBackupFileFound(backupPath: Path) {
|
||||||
|
scope.launch {
|
||||||
|
mutex.withLock {
|
||||||
|
val tempFile = tempFile.value
|
||||||
|
if (_creating.value && tempFile != null) {
|
||||||
|
try {
|
||||||
|
FileSystem.SYSTEM.atomicMove(tempFile, backupPath)
|
||||||
|
_creatingStatus.value = Status.Success
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.throwIfCancellation()
|
||||||
|
error(e) { "Error moving created backup" }
|
||||||
|
_creatingStatus.value = Status.Error
|
||||||
|
} finally {
|
||||||
|
_creating.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_creatingStatus.value = Status.Error
|
||||||
|
_creating.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,15 +259,18 @@ private fun SettingsBackupScreenContent(
|
|||||||
creatingProgress: Float?,
|
creatingProgress: Float?,
|
||||||
creatingStatus: SettingsBackupViewModel.Status,
|
creatingStatus: SettingsBackupViewModel.Status,
|
||||||
missingSourceFlow: SharedFlow<Pair<Path, List<String>>>,
|
missingSourceFlow: SharedFlow<Pair<Path, List<String>>>,
|
||||||
createFlow: SharedFlow<Pair<String, (Path) -> Unit>>,
|
createFlow: SharedFlow<String>,
|
||||||
restoreFile: (Path?) -> Unit,
|
restoreFile: (Path) -> Unit,
|
||||||
restoreBackup: (Path) -> Unit,
|
restoreBackup: (Path) -> Unit,
|
||||||
stopRestore: () -> Unit,
|
stopRestore: () -> Unit,
|
||||||
exportBackup: () -> Unit
|
exportBackup: () -> Unit,
|
||||||
|
exportBackupFileFound: (Path) -> Unit
|
||||||
) {
|
) {
|
||||||
var backupFile by remember { mutableStateOf<Path?>(null) }
|
var backupFile by remember { mutableStateOf<Path?>(null) }
|
||||||
var missingSources by remember { mutableStateOf(emptyList<String>()) }
|
var missingSources by remember { mutableStateOf(emptyList<String>()) }
|
||||||
val dialogState = rememberMaterialDialogState()
|
val dialogState = rememberMaterialDialogState()
|
||||||
|
val fileSaver = rememberFileSaver(exportBackupFileFound)
|
||||||
|
val fileChooser = rememberFileChooser(restoreFile)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch {
|
launch {
|
||||||
missingSourceFlow.collect { (backup, sources) ->
|
missingSourceFlow.collect { (backup, sources) ->
|
||||||
@@ -246,12 +280,14 @@ private fun SettingsBackupScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
createFlow.collect { (filename, function) ->
|
createFlow.collect { filename ->
|
||||||
fileSaver(filename, "proto.gz", onApprove = function)
|
fileSaver.save(filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Toolbar(stringResource(MR.strings.settings_backup_screen))
|
Toolbar(stringResource(MR.strings.settings_backup_screen))
|
||||||
@@ -268,7 +304,7 @@ private fun SettingsBackupScreenContent(
|
|||||||
restoringProgress,
|
restoringProgress,
|
||||||
restoreStatus
|
restoreStatus
|
||||||
) {
|
) {
|
||||||
filePicker("gz", onApprove = restoreFile)
|
fileChooser.launch("gz")
|
||||||
}
|
}
|
||||||
PreferenceFile(
|
PreferenceFile(
|
||||||
stringResource(MR.strings.backup_create),
|
stringResource(MR.strings.backup_create),
|
||||||
|
|||||||
@@ -6,14 +6,12 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.settings
|
package ca.gosyer.ui.settings
|
||||||
|
|
||||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -23,6 +21,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import ca.gosyer.data.library.LibraryPreferences
|
import ca.gosyer.data.library.LibraryPreferences
|
||||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
|
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||||
|
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
import ca.gosyer.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.PreferenceRow
|
import ca.gosyer.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.base.prefs.SwitchPreference
|
import ca.gosyer.ui.base.prefs.SwitchPreference
|
||||||
@@ -35,6 +35,8 @@ import ca.gosyer.uicore.vm.viewModel
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -46,10 +48,11 @@ class SettingsLibraryScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val vm = viewModel<SettingsLibraryViewModel>()
|
val vm = viewModel<SettingsLibraryViewModel>()
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
SettingsLibraryScreenContent(
|
SettingsLibraryScreenContent(
|
||||||
showAllCategory = vm.showAllCategory,
|
showAllCategory = vm.showAllCategory,
|
||||||
refreshCategoryCount = vm::refreshCategoryCount,
|
categoriesSize = vm.categories.collectAsState().value,
|
||||||
categoriesSize = vm.categories.collectAsState().value
|
openCategoriesScreen = { openCategoriesMenu(vm::refreshCategoryCount, navigator) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,8 +81,8 @@ class SettingsLibraryViewModel @Inject constructor(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SettingsLibraryScreenContent(
|
fun SettingsLibraryScreenContent(
|
||||||
showAllCategory: PreferenceMutableStateFlow<Boolean>,
|
showAllCategory: PreferenceMutableStateFlow<Boolean>,
|
||||||
refreshCategoryCount: () -> Unit,
|
categoriesSize: Int,
|
||||||
categoriesSize: Int
|
openCategoriesScreen: () -> Unit
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -98,7 +101,7 @@ fun SettingsLibraryScreenContent(
|
|||||||
item {
|
item {
|
||||||
PreferenceRow(
|
PreferenceRow(
|
||||||
stringResource(MR.strings.location_categories),
|
stringResource(MR.strings.location_categories),
|
||||||
onClick = { openCategoriesMenu(refreshCategoryCount) },
|
onClick = { openCategoriesScreen() },
|
||||||
subtitle = categoriesSize.toString()
|
subtitle = categoriesSize.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user