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"
|
||||
materialDialogs = "0.6.4"
|
||||
|
||||
# Android
|
||||
activityCompose = "1.3.1"
|
||||
|
||||
# Swing
|
||||
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" }
|
||||
materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
|
||||
|
||||
# Android
|
||||
activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||
|
||||
# Swing
|
||||
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
|
||||
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")
|
||||
dependencies {
|
||||
api(kotlin("stdlib-jdk8"))
|
||||
api(libs.activityCompose)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun openCategoriesMenu(notifyFinished: (() -> Unit)? = null) {
|
||||
actual fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator) {
|
||||
launchApplication {
|
||||
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
|
||||
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.ScreenKey
|
||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
|
||||
expect fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator)
|
||||
|
||||
class CategoriesScreen(
|
||||
@Transient
|
||||
|
||||
@@ -41,10 +41,10 @@ import ca.gosyer.i18n.MR
|
||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||
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.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.vm.ContextWrapper
|
||||
import ca.gosyer.uicore.vm.ViewModel
|
||||
@@ -66,6 +66,8 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
@@ -90,7 +92,8 @@ class SettingsBackupScreen : Screen {
|
||||
restoreFile = vm::restoreFile,
|
||||
restoreBackup = vm::restoreBackup,
|
||||
stopRestore = vm::stopRestore,
|
||||
exportBackup = vm::exportBackup
|
||||
exportBackup = vm::exportBackup,
|
||||
exportBackupFileFound = vm::exportBackupFileFound
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -114,13 +117,13 @@ class SettingsBackupViewModel @Inject constructor(
|
||||
val creatingProgress = _creatingProgress.asStateFlow()
|
||||
private val _creatingStatus = MutableStateFlow<Status>(Status.Nothing)
|
||||
internal val creatingStatus = _creatingStatus.asStateFlow()
|
||||
private val _createFlow = MutableSharedFlow<Pair<String, (Path) -> Unit>>()
|
||||
private val _createFlow = MutableSharedFlow<String>()
|
||||
val createFlow = _createFlow.asSharedFlow()
|
||||
|
||||
fun restoreFile(file: Path?) {
|
||||
fun restoreFile(file: Path) {
|
||||
scope.launch {
|
||||
if (file == null || !FileSystem.SYSTEM.exists(file)) {
|
||||
info { "Invalid file ${file?.toString()}" }
|
||||
if (!FileSystem.SYSTEM.exists(file)) {
|
||||
info { "Invalid file ${file.toString()}" }
|
||||
_restoreStatus.value = Status.Error
|
||||
_restoring.value = false
|
||||
} else {
|
||||
@@ -167,6 +170,9 @@ class SettingsBackupViewModel @Inject constructor(
|
||||
_restoring.value = false
|
||||
}
|
||||
|
||||
private val tempFile = MutableStateFlow<Path?>(null)
|
||||
private val mutex = Mutex()
|
||||
|
||||
fun exportBackup() {
|
||||
scope.launch {
|
||||
_creatingStatus.value = Status.Nothing
|
||||
@@ -181,32 +187,57 @@ class SettingsBackupViewModel @Inject constructor(
|
||||
} catch (e: Exception) {
|
||||
info(e) { "Error exporting backup" }
|
||||
_creatingStatus.value = Status.Error
|
||||
_creating.value = false
|
||||
e.throwIfCancellation()
|
||||
null
|
||||
}
|
||||
_creatingProgress.value = 1.0F
|
||||
if (backup != null && backup.status.isSuccess()) {
|
||||
_createFlow.emit(
|
||||
(backup.headers["content-disposition"]?.substringAfter("filename=")?.trim('"') ?: "backup") to {
|
||||
scope.launch {
|
||||
val filename = backup.headers["content-disposition"]?.substringAfter("filename=")?.trim('"') ?: "backup"
|
||||
tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also {
|
||||
launch {
|
||||
try {
|
||||
backup.content.toInputStream()
|
||||
.source()
|
||||
.copyTo(
|
||||
FileSystem.SYSTEM.sink(it).buffer()
|
||||
)
|
||||
_creatingStatus.value = Status.Success
|
||||
} catch (e: Exception) {
|
||||
e.throwIfCancellation()
|
||||
error(e) { "Error creating backup" }
|
||||
_creatingStatus.value = Status.Error
|
||||
_creating.value = false
|
||||
} finally {
|
||||
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?,
|
||||
creatingStatus: SettingsBackupViewModel.Status,
|
||||
missingSourceFlow: SharedFlow<Pair<Path, List<String>>>,
|
||||
createFlow: SharedFlow<Pair<String, (Path) -> Unit>>,
|
||||
restoreFile: (Path?) -> Unit,
|
||||
createFlow: SharedFlow<String>,
|
||||
restoreFile: (Path) -> Unit,
|
||||
restoreBackup: (Path) -> Unit,
|
||||
stopRestore: () -> Unit,
|
||||
exportBackup: () -> Unit
|
||||
exportBackup: () -> Unit,
|
||||
exportBackupFileFound: (Path) -> Unit
|
||||
) {
|
||||
var backupFile by remember { mutableStateOf<Path?>(null) }
|
||||
var missingSources by remember { mutableStateOf(emptyList<String>()) }
|
||||
val dialogState = rememberMaterialDialogState()
|
||||
val fileSaver = rememberFileSaver(exportBackupFileFound)
|
||||
val fileChooser = rememberFileChooser(restoreFile)
|
||||
LaunchedEffect(Unit) {
|
||||
launch {
|
||||
missingSourceFlow.collect { (backup, sources) ->
|
||||
@@ -246,12 +280,14 @@ private fun SettingsBackupScreenContent(
|
||||
}
|
||||
}
|
||||
launch {
|
||||
createFlow.collect { (filename, function) ->
|
||||
fileSaver(filename, "proto.gz", onApprove = function)
|
||||
createFlow.collect { filename ->
|
||||
fileSaver.save(filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Toolbar(stringResource(MR.strings.settings_backup_screen))
|
||||
@@ -268,7 +304,7 @@ private fun SettingsBackupScreenContent(
|
||||
restoringProgress,
|
||||
restoreStatus
|
||||
) {
|
||||
filePicker("gz", onApprove = restoreFile)
|
||||
fileChooser.launch("gz")
|
||||
}
|
||||
PreferenceFile(
|
||||
stringResource(MR.strings.backup_create),
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
|
||||
package ca.gosyer.ui.settings
|
||||
|
||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
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.server.interactions.CategoryInteractionHandler
|
||||
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.prefs.PreferenceRow
|
||||
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.ScreenKey
|
||||
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.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -46,10 +48,11 @@ class SettingsLibraryScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val vm = viewModel<SettingsLibraryViewModel>()
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
SettingsLibraryScreenContent(
|
||||
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
|
||||
fun SettingsLibraryScreenContent(
|
||||
showAllCategory: PreferenceMutableStateFlow<Boolean>,
|
||||
refreshCategoryCount: () -> Unit,
|
||||
categoriesSize: Int
|
||||
categoriesSize: Int,
|
||||
openCategoriesScreen: () -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -98,7 +101,7 @@ fun SettingsLibraryScreenContent(
|
||||
item {
|
||||
PreferenceRow(
|
||||
stringResource(MR.strings.location_categories),
|
||||
onClick = { openCategoriesMenu(refreshCategoryCount) },
|
||||
onClick = { openCategoriesScreen() },
|
||||
subtitle = categoriesSize.toString()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user