From 7c9267a866026ffb84291a778420ee1fc5059036 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:40:03 +0100 Subject: [PATCH] New SettingsScreen with export dir preference --- app/build.gradle.kts | 1 + .../main/java/org/fairscan/app/FairScanApp.kt | 4 + .../java/org/fairscan/app/MainActivity.kt | 29 +++ .../java/org/fairscan/app/ui/Navigation.kt | 2 + .../java/org/fairscan/app/ui/PreviewUtils.kt | 2 +- .../org/fairscan/app/ui/components/Buttons.kt | 17 -- .../fairscan/app/ui/components/Scaffold.kt | 66 ++++++- .../fairscan/app/ui/screens/DocumentScreen.kt | 3 +- .../app/ui/screens/camera/CameraScreen.kt | 3 +- .../app/ui/screens/export/ExportScreen.kt | 4 +- .../app/ui/screens/home/HomeScreen.kt | 12 +- .../ui/screens/settings/SettingsRepository.kt | 44 +++++ .../app/ui/screens/settings/SettingsScreen.kt | 173 ++++++++++++++++++ .../ui/screens/settings/SettingsViewModel.kt | 45 +++++ gradle/libs.versions.toml | 3 +- 15 files changed, 371 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsRepository.kt create mode 100644 app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsScreen.kt create mode 100644 app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d21e8c2..b381d26 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,6 +116,7 @@ dependencies { implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.view) implementation(libs.androidx.datastore) + implementation(libs.androidx.datastore.preferences) implementation(libs.protobuf.javalite) implementation(libs.litert) implementation(libs.litert.support) diff --git a/app/src/main/java/org/fairscan/app/FairScanApp.kt b/app/src/main/java/org/fairscan/app/FairScanApp.kt index 9de959e..a072fb3 100644 --- a/app/src/main/java/org/fairscan/app/FairScanApp.kt +++ b/app/src/main/java/org/fairscan/app/FairScanApp.kt @@ -32,6 +32,8 @@ import org.fairscan.app.ui.screens.about.AboutViewModel import org.fairscan.app.ui.screens.camera.CameraViewModel import org.fairscan.app.ui.screens.export.ExportViewModel import org.fairscan.app.ui.screens.home.HomeViewModel +import org.fairscan.app.ui.screens.settings.SettingsRepository +import org.fairscan.app.ui.screens.settings.SettingsViewModel import java.io.File class FairScanApp : Application() { @@ -58,6 +60,7 @@ class AppContainer(context: Context) { val logger = FileLogger(logRepository) val imageSegmentationService = ImageSegmentationService(context, logger) val recentDocumentsDataStore = context.recentDocumentsDataStore + val settingsRepository = SettingsRepository(context) @Suppress("UNCHECKED_CAST") inline fun viewModelFactory( @@ -73,4 +76,5 @@ class AppContainer(context: Context) { val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) } val exportViewModelFactory = viewModelFactory { ExportViewModel(it) } val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) } + val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) } } diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 3e35265..29806b5 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -67,6 +67,8 @@ import org.fairscan.app.ui.screens.export.ExportViewModel import org.fairscan.app.ui.screens.export.PdfGenerationActions import org.fairscan.app.ui.screens.home.HomeScreen import org.fairscan.app.ui.screens.home.HomeViewModel +import org.fairscan.app.ui.screens.settings.SettingsScreen +import org.fairscan.app.ui.screens.settings.SettingsViewModel import org.fairscan.app.ui.theme.FairScanTheme import org.opencv.android.OpenCVLoader @@ -83,6 +85,8 @@ class MainActivity : ComponentActivity() { val cameraViewModel: CameraViewModel by viewModels { appContainer.cameraViewModelFactory } val exportViewModel: ExportViewModel by viewModels { appContainer.exportViewModelFactory } val aboutViewModel: AboutViewModel by viewModels { appContainer.aboutViewModelFactory } + val settingsViewModel: SettingsViewModel + by viewModels { appContainer.settingsViewModelFactory } lifecycleScope.launch(Dispatchers.IO) { exportViewModel.cleanUpOldPdfs(1000 * 3600) } @@ -158,11 +162,35 @@ class MainActivity : ComponentActivity() { is Screen.Overlay.Libraries -> { LibrariesScreen(onBack = navigation.back) } + is Screen.Overlay.Settings -> { + SettingsScreenWrapper(settingsViewModel, navigation) + } } } } } + @Composable + private fun SettingsScreenWrapper(settingsViewModel: SettingsViewModel, nav: Navigation) { + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocumentTree() + ) { uri -> + if (uri != null) { + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + contentResolver.takePersistableUriPermission(uri, flags) + settingsViewModel.setExportDirUri(uri.toString()) + } + } + val settingsUiState by settingsViewModel.uiState.collectAsStateWithLifecycle() + SettingsScreen( + settingsUiState, + onChooseDirectoryClick = { launcher.launch(null) }, + onResetExportDirClick = { settingsViewModel.setExportDirUri(null) }, + onBack = nav.back + ) + } + @Composable private fun CollectAboutEvents( context: Context, @@ -303,5 +331,6 @@ private fun navigation(viewModel: MainViewModel): Navigation = Navigation( toExportScreen = { viewModel.navigateTo(Screen.Main.Export) }, toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) }, toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) }, + toSettingsScreen = { viewModel.navigateTo(Screen.Overlay.Settings) }, back = { viewModel.navigateBack() } ) diff --git a/app/src/main/java/org/fairscan/app/ui/Navigation.kt b/app/src/main/java/org/fairscan/app/ui/Navigation.kt index e94916c..9ceb6a4 100644 --- a/app/src/main/java/org/fairscan/app/ui/Navigation.kt +++ b/app/src/main/java/org/fairscan/app/ui/Navigation.kt @@ -24,6 +24,7 @@ sealed class Screen { sealed class Overlay : Screen() { object About : Overlay() object Libraries : Overlay() + object Settings : Overlay() } } @@ -34,6 +35,7 @@ data class Navigation( val toExportScreen: () -> Unit, val toAboutScreen: () -> Unit, val toLibrariesScreen: () -> Unit, + val toSettingsScreen: () -> Unit, val back: () -> Unit, ) diff --git a/app/src/main/java/org/fairscan/app/ui/PreviewUtils.kt b/app/src/main/java/org/fairscan/app/ui/PreviewUtils.kt index c267dad..f073437 100644 --- a/app/src/main/java/org/fairscan/app/ui/PreviewUtils.kt +++ b/app/src/main/java/org/fairscan/app/ui/PreviewUtils.kt @@ -21,7 +21,7 @@ import kotlinx.collections.immutable.persistentListOf import org.fairscan.app.ui.state.DocumentUiModel fun dummyNavigation(): Navigation { - return Navigation({}, {}, {}, {}, {}, {}, {}) + return Navigation({}, {}, {}, {}, {}, {}, {}, {}) } fun fakeDocument(): DocumentUiModel { diff --git a/app/src/main/java/org/fairscan/app/ui/components/Buttons.kt b/app/src/main/java/org/fairscan/app/ui/components/Buttons.kt index f28c698..53be71c 100644 --- a/app/src/main/java/org/fairscan/app/ui/components/Buttons.kt +++ b/app/src/main/java/org/fairscan/app/ui/components/Buttons.kt @@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Info import androidx.compose.material3.Button import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon @@ -85,19 +84,3 @@ fun BackButton(onClick: () -> Unit, modifier: Modifier = Modifier) { ) } } - -@Composable -fun AboutScreenNavButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - IconButton( - onClick = onClick, - modifier = modifier - ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = stringResource(R.string.about), - tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) - } -} diff --git a/app/src/main/java/org/fairscan/app/ui/components/Scaffold.kt b/app/src/main/java/org/fairscan/app/ui/components/Scaffold.kt index 83c8a79..691fc2b 100644 --- a/app/src/main/java/org/fairscan/app/ui/components/Scaffold.kt +++ b/app/src/main/java/org/fairscan/app/ui/components/Scaffold.kt @@ -26,18 +26,32 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp +import org.fairscan.app.ui.Navigation @Composable fun MyScaffold( - toAboutScreen: () -> Unit, + navigation: Navigation, pageListState: CommonPageListState, pageListButton: (@Composable () -> Unit)? = null, bottomBar: @Composable () -> Unit, @@ -69,8 +83,8 @@ fun MyScaffold( .windowInsetsPadding(WindowInsets.safeDrawing) ) } - AboutScreenNavButton( - onClick = toAboutScreen, + AppOverflowMenu( + navigation, modifier = Modifier .align(Alignment.TopEnd) .windowInsetsPadding(WindowInsets.safeDrawing) @@ -86,11 +100,11 @@ fun DocumentBar( pageListButton: (@Composable () -> Unit)? = null, ) { val isLandscape = isLandscape(LocalConfiguration.current) - Column ( + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.background(MaterialTheme.colorScheme.surfaceContainer) ) { - Box ( + Box( if (isLandscape) Modifier .weight(1f) @@ -144,3 +158,45 @@ fun DocumentBar( fun isLandscape(configuration: Configuration): Boolean { return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } + +@Composable +fun AppOverflowMenu( + navigation: Navigation, + modifier: Modifier = Modifier, +) { + var expanded by remember { mutableStateOf(false) } + Box( + modifier + ) { + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "Menu") + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + ) { + + DropdownMenuItem( + leadingIcon = { Icon(Icons.Default.Settings, contentDescription = null) }, + text = { Text("Settings") }, + onClick = { + expanded = false + navigation.toSettingsScreen() + } + ) + + DropdownMenuItem( + leadingIcon = { Icon(Icons.Default.Info, contentDescription = null) }, + text = { Text("About") }, + onClick = { + expanded = false + navigation.toAboutScreen() + } + ) + } + } +} + diff --git a/app/src/main/java/org/fairscan/app/ui/screens/DocumentScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/DocumentScreen.kt index f0286ee..9366cca 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/DocumentScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/DocumentScreen.kt @@ -95,7 +95,7 @@ fun DocumentScreen( } MyScaffold( - toAboutScreen = navigation.toAboutScreen, + navigation = navigation, pageListState = CommonPageListState( document, onPageClick = { index -> currentPageIndex.intValue = index }, @@ -103,7 +103,6 @@ fun DocumentScreen( currentPageIndex = currentPageIndex.intValue, listState = listState, ), - onBack = navigation.back, bottomBar = { BottomBar(navigation) }, diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt index 8158466..c78bc0c 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt @@ -221,9 +221,8 @@ private fun CameraScreenScaffold( Box { MyScaffold( - toAboutScreen = navigation.toAboutScreen, + navigation = navigation, pageListState = pageListState, - onBack = navigation.back, bottomBar = { Bar(cameraUiState.pageCount, onFinalizePressed) } ) { modifier -> CameraPreviewBox( diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt index 7423a28..ea25c47 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt @@ -69,7 +69,7 @@ import androidx.core.net.toUri import org.fairscan.app.R import org.fairscan.app.data.GeneratedPdf import org.fairscan.app.ui.Navigation -import org.fairscan.app.ui.components.AboutScreenNavButton +import org.fairscan.app.ui.components.AppOverflowMenu import org.fairscan.app.ui.components.BackButton import org.fairscan.app.ui.components.MainActionButton import org.fairscan.app.ui.components.NewDocumentDialog @@ -154,7 +154,7 @@ fun ExportScreen( title = { Text(stringResource(R.string.export_pdf)) }, navigationIcon = { BackButton(navigation.back) }, actions = { - AboutScreenNavButton(onClick = navigation.toAboutScreen) + AppOverflowMenu(navigation) } ) } diff --git a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt index 06e0d0a..5fbdf75 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt @@ -55,13 +55,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.persistentListOf -import org.fairscan.app.ui.components.CameraPermissionState -import org.fairscan.app.ui.Navigation import org.fairscan.app.R -import org.fairscan.app.ui.components.rememberCameraPermissionState -import org.fairscan.app.ui.components.AboutScreenNavButton +import org.fairscan.app.ui.Navigation +import org.fairscan.app.ui.components.AppOverflowMenu +import org.fairscan.app.ui.components.CameraPermissionState import org.fairscan.app.ui.components.formatDate import org.fairscan.app.ui.components.pageCountText +import org.fairscan.app.ui.components.rememberCameraPermissionState import org.fairscan.app.ui.dummyNavigation import org.fairscan.app.ui.fakeDocument import org.fairscan.app.ui.state.DocumentUiModel @@ -82,9 +82,7 @@ fun HomeScreen( topBar = { TopAppBar( title = { Text(stringResource(R.string.app_name)) }, - actions = { - AboutScreenNavButton(onClick = navigation.toAboutScreen) - } + actions = { AppOverflowMenu(navigation) } ) }, ) { padding -> diff --git a/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsRepository.kt b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsRepository.kt new file mode 100644 index 0000000..4d32da1 --- /dev/null +++ b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsRepository.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2025 Pierre-Yves Nicolas + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +package org.fairscan.app.ui.screens.settings + +import android.content.Context +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private val Context.dataStore by preferencesDataStore(name = "fairscan_settings") + +class SettingsRepository(private val context: Context) { + + private val EXPORT_DIR_URI = stringPreferencesKey("export_dir_uri") + + val exportDirUri: Flow = + context.dataStore.data.map { prefs -> + prefs[EXPORT_DIR_URI] + } + + suspend fun setExportDirUri(uri: String?) { + context.dataStore.edit { prefs -> + if (uri == null) { + prefs.remove(EXPORT_DIR_URI) + } else { + prefs[EXPORT_DIR_URI] = uri + } + } + } +} diff --git a/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..4a32e07 --- /dev/null +++ b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsScreen.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2025 Pierre-Yves Nicolas + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +package org.fairscan.app.ui.screens.settings + + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Folder +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import org.fairscan.app.ui.components.BackButton +import org.fairscan.app.ui.theme.FairScanTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + uiState: SettingsUiState, + onChooseDirectoryClick: () -> Unit, + onResetExportDirClick: () -> Unit, + onBack: () -> Unit, +) { + BackHandler { onBack() } + Scaffold( + topBar = { + TopAppBar( + title = { Text("Settings") }, + navigationIcon = { BackButton(onBack) }, + ) + } + ) { paddingValues -> + SettingsContent(uiState, onChooseDirectoryClick, onResetExportDirClick, modifier = Modifier.padding(paddingValues)) + } + + +} + +@Composable +private fun SettingsContent( + uiState: SettingsUiState, + onChooseDirectoryClick: () -> Unit, + onResetExportDirClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val folderName = remember(uiState.exportDirUri) { + extractFolderName(uiState.exportDirUri) + } + + Column( + modifier + .fillMaxSize() + .padding(20.dp) + ) { + DirectorySettingItem( + label = "Export directory", + folderName = folderName, + onClick = onChooseDirectoryClick + ) + + Spacer(Modifier.height(12.dp)) + + if (uiState.exportDirUri != null) { + OutlinedButton( + onClick = onResetExportDirClick, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary), + ) { + Text("Reset to default") + } + } + } +} + +@Composable +fun DirectorySettingItem( + label: String, + folderName: String, + onClick: () -> Unit, +) { + Column { + Text( + text = label, + style = MaterialTheme.typography.titleLarge + ) + + Spacer(Modifier.height(8.dp)) + + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = MaterialTheme.shapes.medium + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 14.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = folderName, + style = MaterialTheme.typography.bodyLarge + ) + + Icon( + Icons.Default.Folder, + contentDescription = "Change directory", + ) + } + } + } +} + +private fun extractFolderName(uriString: String?): String { + if (uriString == null) return "Downloads (default)" + return runCatching { + val uri = uriString.toUri() + uri.lastPathSegment?.substringAfter(':')?.substringAfter('/') ?: uriString + }.getOrElse { uriString } +} + +@Preview +@Composable +fun SettingsScreenPreviewWithoutDir() { + SettingsScreenPreview(SettingsUiState(null)) +} + +@Preview +@Composable +fun SettingsScreenPreviewWithDir() { + SettingsScreenPreview(SettingsUiState("content://root/dir")) +} + +@Composable +fun SettingsScreenPreview(uiState: SettingsUiState) { + FairScanTheme { + SettingsScreen(uiState, onChooseDirectoryClick = {}, onResetExportDirClick = {}, onBack= {}) + } +} diff --git a/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsViewModel.kt new file mode 100644 index 0000000..a01bae2 --- /dev/null +++ b/app/src/main/java/org/fairscan/app/ui/screens/settings/SettingsViewModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2025 Pierre-Yves Nicolas + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +package org.fairscan.app.ui.screens.settings + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import org.fairscan.app.AppContainer + +data class SettingsUiState( + val exportDirUri: String? = null +) + +class SettingsViewModel(container: AppContainer) : ViewModel() { + + private val repo = container.settingsRepository + + val uiState: StateFlow = + repo.exportDirUri + .map { uri -> SettingsUiState(exportDirUri = uri) } + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5000), + SettingsUiState() + ) + + fun setExportDirUri(uri: String?) { + viewModelScope.launch { + repo.setExportDirUri(uri) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1124417..fbce78d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ lifecycleRuntimeKtx = "2.9.3" activityCompose = "1.10.1" composeBom = "2025.08.01" camerax = "1.4.2" -datastore = "1.1.7" +datastore = "1.2.0" litert = "1.4.0" opencv = "4.12.0" assertj = "3.27.4" @@ -46,6 +46,7 @@ androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" } androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" } androidx-datastore = { group = "androidx.datastore", name = "datastore" , version.ref = "datastore" } +androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences" , version.ref = "datastore" } protobuf-javalite = { group = "com.google.protobuf", name="protobuf-javalite", version.ref = "protobufJavaLite"} litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" } litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }