New SettingsScreen with export dir preference
This commit is contained in:
committed by
pynicolas
parent
96b2d5b830
commit
7c9267a866
@@ -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)
|
||||
|
||||
@@ -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 <reified VM : ViewModel> 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) }
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<String?> =
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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= {})
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<SettingsUiState> =
|
||||
repo.exportDirUri
|
||||
.map { uri -> SettingsUiState(exportDirUri = uri) }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
SettingsUiState()
|
||||
)
|
||||
|
||||
fun setExportDirUri(uri: String?) {
|
||||
viewModelScope.launch {
|
||||
repo.setExportDirUri(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user