Remove Home screen
This commit is contained in:
@@ -3,7 +3,6 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.aboutLibrariesAndroid)
|
alias(libs.plugins.aboutLibrariesAndroid)
|
||||||
alias(libs.plugins.protobuf)
|
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +121,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.camera.camera2)
|
implementation(libs.androidx.camera.camera2)
|
||||||
implementation(libs.androidx.camera.lifecycle)
|
implementation(libs.androidx.camera.lifecycle)
|
||||||
implementation(libs.androidx.camera.view)
|
implementation(libs.androidx.camera.view)
|
||||||
implementation(libs.androidx.datastore)
|
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
implementation(libs.androidx.documentfile)
|
implementation(libs.androidx.documentfile)
|
||||||
implementation(libs.protobuf.javalite)
|
|
||||||
implementation(libs.litert)
|
implementation(libs.litert)
|
||||||
implementation(libs.litert.support)
|
implementation(libs.litert.support)
|
||||||
implementation(libs.litert.metadata)
|
implementation(libs.litert.metadata)
|
||||||
@@ -160,21 +157,6 @@ aboutLibraries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protobuf {
|
|
||||||
protoc {
|
|
||||||
artifact = "com.google.protobuf:protoc:4.32.0"
|
|
||||||
}
|
|
||||||
generateProtoTasks {
|
|
||||||
all().forEach { task ->
|
|
||||||
task.builtins {
|
|
||||||
create("java") {
|
|
||||||
option("lite")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://developer.android.com/build/configure-apk-splits
|
// See https://developer.android.com/build/configure-apk-splits
|
||||||
androidComponents {
|
androidComponents {
|
||||||
onVariants { variant ->
|
onVariants { variant ->
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ import androidx.lifecycle.viewmodel.CreationExtras
|
|||||||
import org.fairscan.app.data.FileLogger
|
import org.fairscan.app.data.FileLogger
|
||||||
import org.fairscan.app.data.FileManager
|
import org.fairscan.app.data.FileManager
|
||||||
import org.fairscan.app.data.LogRepository
|
import org.fairscan.app.data.LogRepository
|
||||||
import org.fairscan.app.data.recentDocumentsDataStore
|
|
||||||
import org.fairscan.app.domain.ImageSegmentationService
|
import org.fairscan.app.domain.ImageSegmentationService
|
||||||
import org.fairscan.app.platform.AndroidImageLoader
|
import org.fairscan.app.platform.AndroidImageLoader
|
||||||
import org.fairscan.app.platform.AndroidPdfWriter
|
import org.fairscan.app.platform.AndroidPdfWriter
|
||||||
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
||||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
|
||||||
import org.fairscan.app.ui.screens.settings.SettingsRepository
|
import org.fairscan.app.ui.screens.settings.SettingsRepository
|
||||||
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -57,7 +55,6 @@ class AppContainer(context: Context) {
|
|||||||
val logger = FileLogger(logRepository)
|
val logger = FileLogger(logRepository)
|
||||||
val imageSegmentationService = ImageSegmentationService(context, logger)
|
val imageSegmentationService = ImageSegmentationService(context, logger)
|
||||||
val imageLoader = AndroidImageLoader(context.contentResolver)
|
val imageLoader = AndroidImageLoader(context.contentResolver)
|
||||||
val recentDocumentsDataStore = context.recentDocumentsDataStore
|
|
||||||
val settingsRepository = SettingsRepository(context)
|
val settingsRepository = SettingsRepository(context)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -69,7 +66,6 @@ class AppContainer(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) }
|
|
||||||
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
|
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
|
||||||
val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) }
|
val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) }
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import org.fairscan.app.data.ImageRepository
|
|||||||
import org.fairscan.app.ui.Navigation
|
import org.fairscan.app.ui.Navigation
|
||||||
import org.fairscan.app.ui.Screen
|
import org.fairscan.app.ui.Screen
|
||||||
import org.fairscan.app.ui.components.rememberCameraPermissionState
|
import org.fairscan.app.ui.components.rememberCameraPermissionState
|
||||||
import org.fairscan.app.ui.screens.document.DocumentScreen
|
|
||||||
import org.fairscan.app.ui.screens.LibrariesScreen
|
import org.fairscan.app.ui.screens.LibrariesScreen
|
||||||
import org.fairscan.app.ui.screens.about.AboutEvent
|
import org.fairscan.app.ui.screens.about.AboutEvent
|
||||||
import org.fairscan.app.ui.screens.about.AboutScreen
|
import org.fairscan.app.ui.screens.about.AboutScreen
|
||||||
@@ -63,14 +62,12 @@ import org.fairscan.app.ui.screens.about.createEmailWithImageIntent
|
|||||||
import org.fairscan.app.ui.screens.camera.CameraEvent
|
import org.fairscan.app.ui.screens.camera.CameraEvent
|
||||||
import org.fairscan.app.ui.screens.camera.CameraScreen
|
import org.fairscan.app.ui.screens.camera.CameraScreen
|
||||||
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
||||||
|
import org.fairscan.app.ui.screens.document.DocumentScreen
|
||||||
import org.fairscan.app.ui.screens.export.ExportActions
|
import org.fairscan.app.ui.screens.export.ExportActions
|
||||||
import org.fairscan.app.ui.screens.export.ExportEvent
|
import org.fairscan.app.ui.screens.export.ExportEvent
|
||||||
import org.fairscan.app.ui.screens.export.ExportResult
|
import org.fairscan.app.ui.screens.export.ExportResult
|
||||||
import org.fairscan.app.ui.screens.export.ExportScreenWrapper
|
import org.fairscan.app.ui.screens.export.ExportScreenWrapper
|
||||||
import org.fairscan.app.ui.screens.export.ExportViewModel
|
import org.fairscan.app.ui.screens.export.ExportViewModel
|
||||||
import org.fairscan.app.ui.screens.home.HomeScreen
|
|
||||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
|
||||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
|
||||||
import org.fairscan.app.ui.screens.settings.SettingsScreen
|
import org.fairscan.app.ui.screens.settings.SettingsScreen
|
||||||
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
||||||
import org.fairscan.app.ui.theme.FairScanTheme
|
import org.fairscan.app.ui.theme.FairScanTheme
|
||||||
@@ -97,7 +94,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val imageRepository = sessionViewModel.imageRepository
|
val imageRepository = sessionViewModel.imageRepository
|
||||||
val viewModel: MainViewModel by viewModels {
|
val viewModel: MainViewModel by viewModels {
|
||||||
appContainer.viewModelFactory {
|
appContainer.viewModelFactory {
|
||||||
MainViewModel(imageRepository, launchMode)
|
MainViewModel(imageRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val exportViewModel: ExportViewModel by viewModels {
|
val exportViewModel: ExportViewModel by viewModels {
|
||||||
@@ -110,7 +107,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
AboutViewModel(appContainer, imageRepository)
|
AboutViewModel(appContainer, imageRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val homeViewModel: HomeViewModel by viewModels { appContainer.homeViewModelFactory }
|
|
||||||
val cameraViewModel: CameraViewModel by viewModels { appContainer.cameraViewModelFactory }
|
val cameraViewModel: CameraViewModel by viewModels { appContainer.cameraViewModelFactory }
|
||||||
|
|
||||||
val settingsViewModel: SettingsViewModel
|
val settingsViewModel: SettingsViewModel
|
||||||
@@ -157,16 +153,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (currentScreen) {
|
when (currentScreen) {
|
||||||
is Screen.Main.Home -> {
|
null -> {
|
||||||
val recentDocs by homeViewModel.recentDocuments.collectAsStateWithLifecycle()
|
// waiting to load pages to get an initial screen
|
||||||
HomeScreen(
|
|
||||||
cameraPermission = cameraPermission,
|
|
||||||
currentDocument = document,
|
|
||||||
navigation = navigation,
|
|
||||||
onClearScan = { viewModel.startNewDocument() },
|
|
||||||
recentDocuments = recentDocs,
|
|
||||||
onOpenPdf = { fileUri -> openUri(fileUri, ExportFormat.PDF.mimeType, logger) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
is Screen.Main.Camera -> {
|
is Screen.Main.Camera -> {
|
||||||
val pickMultiple = rememberLauncherForActivityResult(
|
val pickMultiple = rememberLauncherForActivityResult(
|
||||||
@@ -216,7 +204,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
onCloseScan = {
|
onCloseScan = {
|
||||||
exportViewModel.resetFilename()
|
exportViewModel.resetFilename()
|
||||||
viewModel.startNewDocument()
|
viewModel.startNewDocument()
|
||||||
viewModel.navigateTo(Screen.Main.Home)
|
viewModel.navigateTo(Screen.Main.Camera)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -468,7 +456,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun navigation(viewModel: MainViewModel, launchMode: LaunchMode): Navigation = Navigation(
|
private fun navigation(viewModel: MainViewModel, launchMode: LaunchMode): Navigation = Navigation(
|
||||||
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
|
|
||||||
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
|
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
|
||||||
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
|
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
|
||||||
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
|
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
|
||||||
|
|||||||
@@ -45,17 +45,27 @@ import org.fairscan.imageprocessing.ColorMode
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode): ViewModel() {
|
class MainViewModel(val imageRepository: ImageRepository): ViewModel() {
|
||||||
|
|
||||||
private val _navigationState = MutableStateFlow(NavigationState.initial(launchMode))
|
private val _navigationState = MutableStateFlow<NavigationState?>(null)
|
||||||
val currentScreen: StateFlow<Screen> = _navigationState.map { it.current }
|
val currentScreen: StateFlow<Screen?> = _navigationState.map { it?.current }
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, _navigationState.value.current)
|
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
private val _pages = MutableStateFlow<List<ScanPage>>(emptyList())
|
private val _pages = MutableStateFlow<List<ScanPage>>(emptyList())
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_pages.value = imageRepository.pages()
|
val pages = imageRepository.pages()
|
||||||
|
|
||||||
|
_pages.value = pages
|
||||||
|
|
||||||
|
_navigationState.value =
|
||||||
|
if (pages.isEmpty()) {
|
||||||
|
NavigationState.initial()
|
||||||
|
} else {
|
||||||
|
NavigationState.initial().navigateTo(Screen.Main.Export)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +120,11 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
|
|||||||
}
|
}
|
||||||
_currentPageIndex.value = min(_pages.value.size - 1, destination.initialPage)
|
_currentPageIndex.value = min(_pages.value.size - 1, destination.initialPage)
|
||||||
}
|
}
|
||||||
_navigationState.update { it.navigateTo(destination) }
|
_navigationState.update { it?.navigateTo(destination) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateBack() {
|
fun navigateBack() {
|
||||||
_navigationState.update { stack -> stack.navigateBack() }
|
_navigationState.update { stack -> stack?.navigateBack() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rotateCurrentPage(clockwise: Boolean) {
|
fun rotateCurrentPage(clockwise: Boolean) {
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025-2026 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.data
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.datastore.core.CorruptionException
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import androidx.datastore.core.Serializer
|
|
||||||
import androidx.datastore.dataStore
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException
|
|
||||||
import org.fairscan.app.RecentDocuments
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
object RecentDocumentsSerializer : Serializer<RecentDocuments> {
|
|
||||||
override val defaultValue: RecentDocuments = RecentDocuments.getDefaultInstance()
|
|
||||||
|
|
||||||
override suspend fun readFrom(input: InputStream): RecentDocuments {
|
|
||||||
return try {
|
|
||||||
RecentDocuments.parseFrom(input)
|
|
||||||
} catch (e: InvalidProtocolBufferException) {
|
|
||||||
throw CorruptionException("Cannot read proto.", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeTo(
|
|
||||||
t: RecentDocuments,
|
|
||||||
output: OutputStream
|
|
||||||
) = t.writeTo(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Context.recentDocumentsDataStore: DataStore<RecentDocuments> by dataStore(
|
|
||||||
fileName = "recent_documents.pb",
|
|
||||||
serializer = RecentDocumentsSerializer
|
|
||||||
)
|
|
||||||
@@ -14,11 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.fairscan.app.ui
|
package org.fairscan.app.ui
|
||||||
|
|
||||||
import org.fairscan.app.LaunchMode
|
|
||||||
|
|
||||||
sealed class Screen {
|
sealed class Screen {
|
||||||
sealed class Main : Screen() {
|
sealed class Main : Screen() {
|
||||||
object Home : Main()
|
|
||||||
object Camera : Main()
|
object Camera : Main()
|
||||||
data class Document(val initialPage: Int = 0) : Main()
|
data class Document(val initialPage: Int = 0) : Main()
|
||||||
object Export : Main()
|
object Export : Main()
|
||||||
@@ -31,7 +28,6 @@ sealed class Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Navigation(
|
data class Navigation(
|
||||||
val toHomeScreen: () -> Unit,
|
|
||||||
val toCameraScreen: () -> Unit,
|
val toCameraScreen: () -> Unit,
|
||||||
val toDocumentScreen: () -> Unit,
|
val toDocumentScreen: () -> Unit,
|
||||||
val toExportScreen: () -> Unit,
|
val toExportScreen: () -> Unit,
|
||||||
@@ -41,18 +37,12 @@ data class Navigation(
|
|||||||
val back: () -> Unit,
|
val back: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun startScreenFor(mode: LaunchMode): Screen.Main =
|
|
||||||
when (mode) {
|
|
||||||
LaunchMode.NORMAL -> Screen.Main.Home
|
|
||||||
LaunchMode.EXTERNAL_SCAN_TO_PDF -> Screen.Main.Camera
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConsistentCopyVisibility
|
@ConsistentCopyVisibility
|
||||||
data class NavigationState private constructor(val stack: List<Screen>, val root: Screen.Main) {
|
data class NavigationState private constructor(val stack: List<Screen>, val root: Screen.Main) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun initial(mode: LaunchMode): NavigationState {
|
fun initial(): NavigationState {
|
||||||
val root = startScreenFor(mode)
|
val root = Screen.Main.Camera
|
||||||
return NavigationState(listOf(root), root)
|
return NavigationState(listOf(root), root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +60,7 @@ data class NavigationState private constructor(val stack: List<Screen>, val root
|
|||||||
fun navigateBack(): NavigationState {
|
fun navigateBack(): NavigationState {
|
||||||
return when (current) {
|
return when (current) {
|
||||||
root -> this // Back handled by system
|
root -> this // Back handled by system
|
||||||
is Screen.Main.Home -> this // Back handled by system
|
is Screen.Main.Camera -> this // Back handled by system
|
||||||
is Screen.Main.Camera -> copy(stack = listOf(Screen.Main.Home))
|
|
||||||
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
|
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
|
||||||
is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Camera))
|
is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Camera))
|
||||||
is Screen.Overlay -> copy(stack = stack.dropLast(1))
|
is Screen.Overlay -> copy(stack = stack.dropLast(1))
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package org.fairscan.app.ui
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import org.fairscan.app.domain.Jpeg
|
import org.fairscan.app.domain.Jpeg
|
||||||
import org.fairscan.app.domain.PageViewKey
|
import org.fairscan.app.domain.PageViewKey
|
||||||
@@ -26,11 +25,7 @@ import org.fairscan.app.ui.state.PageThumbnail
|
|||||||
import org.fairscan.imageprocessing.ColorMode
|
import org.fairscan.imageprocessing.ColorMode
|
||||||
|
|
||||||
fun dummyNavigation(): Navigation {
|
fun dummyNavigation(): Navigation {
|
||||||
return Navigation({}, {}, {}, {}, {}, {}, {}, {})
|
return Navigation({}, {}, {}, {}, {}, {}, {})
|
||||||
}
|
|
||||||
|
|
||||||
fun fakeDocument(): DocumentUiModel {
|
|
||||||
return DocumentUiModel(persistentListOf())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fakeDocument(pageIds: ImmutableList<String>, context: Context): DocumentUiModel {
|
fun fakeDocument(pageIds: ImmutableList<String>, context: Context): DocumentUiModel {
|
||||||
|
|||||||
@@ -141,8 +141,6 @@ fun CameraScreen(
|
|||||||
val isTorchEnabled by cameraViewModel.isTorchEnabled.collectAsStateWithLifecycle()
|
val isTorchEnabled by cameraViewModel.isTorchEnabled.collectAsStateWithLifecycle()
|
||||||
var torchReapplied by remember { mutableStateOf(false) }
|
var torchReapplied by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BackHandler { navigation.back() }
|
|
||||||
|
|
||||||
val captureController = remember { CameraCaptureController() }
|
val captureController = remember { CameraCaptureController() }
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.fairscan.app.AppContainer
|
import org.fairscan.app.AppContainer
|
||||||
import org.fairscan.app.R
|
import org.fairscan.app.R
|
||||||
import org.fairscan.app.RecentDocument
|
|
||||||
import org.fairscan.app.data.FileManager
|
import org.fairscan.app.data.FileManager
|
||||||
import org.fairscan.app.data.ImageRepository
|
import org.fairscan.app.data.ImageRepository
|
||||||
import org.fairscan.app.domain.ExportQuality
|
import org.fairscan.app.domain.ExportQuality
|
||||||
@@ -69,7 +68,6 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
private val preparationDir = container.preparationDir
|
private val preparationDir = container.preparationDir
|
||||||
private val fileManager = container.fileManager
|
private val fileManager = container.fileManager
|
||||||
private val settingsRepository = container.settingsRepository
|
private val settingsRepository = container.settingsRepository
|
||||||
private val recentDocumentsDataStore = container.recentDocumentsDataStore
|
|
||||||
private val logger = container.logger
|
private val logger = container.logger
|
||||||
|
|
||||||
private val _events = MutableSharedFlow<ExportEvent>()
|
private val _events = MutableSharedFlow<ExportEvent>()
|
||||||
@@ -310,12 +308,6 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
val bundle = SavedBundle(savedItems, saveDir)
|
val bundle = SavedBundle(savedItems, saveDir)
|
||||||
_uiState.update { it.copy(savedBundle = bundle) }
|
_uiState.update { it.copy(savedBundle = bundle) }
|
||||||
|
|
||||||
if (exportFormat == ExportFormat.PDF) {
|
|
||||||
savedItems.forEach { item ->
|
|
||||||
addRecentDocument(item.uri, item.fileName, result.pageCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filesForMediaScan.forEach { f -> mediaScan(context, f, exportFormat.mimeType) }
|
filesForMediaScan.forEach { f -> mediaScan(context, f, exportFormat.mimeType) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,27 +387,6 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
DocumentFile.fromTreeUri(context, exportDirUri)?.name
|
DocumentFile.fromTreeUri(context, exportDirUri)?.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addRecentDocument(fileUri: Uri, fileName: String, pageCount: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
recentDocumentsDataStore.updateData { current ->
|
|
||||||
val newDoc = RecentDocument.newBuilder()
|
|
||||||
.setFileUri(fileUri.toString())
|
|
||||||
.setFileName(fileName)
|
|
||||||
.setPageCount(pageCount)
|
|
||||||
.setCreatedAt(System.currentTimeMillis())
|
|
||||||
.build()
|
|
||||||
current.toBuilder()
|
|
||||||
.addDocuments(0, newDoc)
|
|
||||||
.also { builder ->
|
|
||||||
while (builder.documentsCount > 3) {
|
|
||||||
builder.removeDocuments(builder.documentsCount - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ExportPreparationKey(
|
data class ExportPreparationKey(
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025-2026 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.home
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.DeleteOutline
|
|
||||||
import androidx.compose.material.icons.filled.PhotoCamera
|
|
||||||
import androidx.compose.material.icons.filled.PictureAsPdf
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import org.fairscan.app.R
|
|
||||||
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
|
|
||||||
import org.fairscan.app.ui.theme.FairScanTheme
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun HomeScreen(
|
|
||||||
cameraPermission: CameraPermissionState,
|
|
||||||
currentDocument: DocumentUiModel,
|
|
||||||
navigation: Navigation,
|
|
||||||
onClearScan: () -> Unit,
|
|
||||||
recentDocuments: List<RecentDocumentUiState>,
|
|
||||||
onOpenPdf: (Uri) -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold (
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(stringResource(R.string.app_name)) },
|
|
||||||
actions = { AppOverflowMenu(navigation) }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { padding ->
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(padding)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.weight(1f))
|
|
||||||
|
|
||||||
ScanButton(
|
|
||||||
onClick = {
|
|
||||||
onClearScan()
|
|
||||||
navigation.toCameraScreen()
|
|
||||||
},
|
|
||||||
Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
|
||||||
|
|
||||||
if (!currentDocument.isEmpty()) {
|
|
||||||
OngoingScanBanner(
|
|
||||||
currentDocument,
|
|
||||||
onResumeScan = navigation.toDocumentScreen,
|
|
||||||
onClearScan = onClearScan,
|
|
||||||
)
|
|
||||||
} else if (recentDocuments.isNotEmpty()) {
|
|
||||||
RecentDocumentList(recentDocuments, onOpenPdf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScanButton(onClick: () -> Unit, modifier: Modifier) {
|
|
||||||
Button(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier.padding(32.dp),
|
|
||||||
elevation = ButtonDefaults.buttonElevation(defaultElevation = 6.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.PhotoCamera,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(48.dp)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.scan_button),
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun OngoingScanBanner(
|
|
||||||
currentDocument: DocumentUiModel,
|
|
||||||
onResumeScan: () -> Unit,
|
|
||||||
onClearScan: () -> Unit
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
tonalElevation = 2.dp,
|
|
||||||
shadowElevation = 4.dp,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
currentDocument.thumbnail(0)?.let {
|
|
||||||
Image(
|
|
||||||
bitmap = it.asImageBitmap(),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(60.dp)
|
|
||||||
.clip(RoundedCornerShape(4.dp)),
|
|
||||||
contentScale = ContentScale.Fit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.width(12.dp))
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.scan_in_progress),
|
|
||||||
style = MaterialTheme.typography.bodyLarge
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = pageCountText(currentDocument.pageCount()),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f))
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = onClearScan,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.DeleteOutline,
|
|
||||||
contentDescription = stringResource(R.string.discard_scan),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.width(12.dp))
|
|
||||||
Button(onClick = onResumeScan) {
|
|
||||||
Text(stringResource(R.string.resume))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RecentDocumentList(
|
|
||||||
recentDocuments: List<RecentDocumentUiState>,
|
|
||||||
onOpenPdf: (Uri) -> Unit
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.last_saved_pdf_files),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.padding(start = 12.dp, bottom = 4.dp)
|
|
||||||
)
|
|
||||||
Column {
|
|
||||||
recentDocuments.forEach { doc ->
|
|
||||||
ListItem(
|
|
||||||
headlineContent = {
|
|
||||||
Text(
|
|
||||||
doc.fileName,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
},
|
|
||||||
supportingContent = {
|
|
||||||
Text(
|
|
||||||
text = pageCountText(doc.pageCount) + " • " +
|
|
||||||
formatDate(doc.saveTimestamp, LocalContext.current),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
},
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.PictureAsPdf,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.clickable { onOpenPdf(doc.fileUri) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun HomeScreenPreviewOnFirstLaunch() {
|
|
||||||
FairScanTheme {
|
|
||||||
HomeScreen(
|
|
||||||
cameraPermission = rememberCameraPermissionState(),
|
|
||||||
currentDocument = fakeDocument(),
|
|
||||||
navigation = dummyNavigation(),
|
|
||||||
onClearScan = {},
|
|
||||||
recentDocuments = listOf(),
|
|
||||||
onOpenPdf = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun HomeScreenPreviewWithCurrentDocument() {
|
|
||||||
FairScanTheme {
|
|
||||||
HomeScreen(
|
|
||||||
cameraPermission = rememberCameraPermissionState(),
|
|
||||||
currentDocument = fakeDocument(
|
|
||||||
persistentListOf("gallica.bnf.fr-bpt6k5530456s-1"),
|
|
||||||
LocalContext.current
|
|
||||||
),
|
|
||||||
navigation = dummyNavigation(),
|
|
||||||
onClearScan = {},
|
|
||||||
recentDocuments = listOf(),
|
|
||||||
onOpenPdf = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun HomeScreenPreviewWithLastSavedFiles() {
|
|
||||||
FairScanTheme {
|
|
||||||
HomeScreen(
|
|
||||||
cameraPermission = rememberCameraPermissionState(),
|
|
||||||
currentDocument = fakeDocument(),
|
|
||||||
navigation = dummyNavigation(),
|
|
||||||
onClearScan = {},
|
|
||||||
recentDocuments = listOf(
|
|
||||||
RecentDocumentUiState(
|
|
||||||
File("/path/my_file.pdf").toUri(), "my_file.pdf", 1755971180000, 3),
|
|
||||||
RecentDocumentUiState(
|
|
||||||
"content:///path/scan2.pdf".toUri(), "scan2.pdf",1755000500000, 1)
|
|
||||||
),
|
|
||||||
onOpenPdf = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025-2026 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.home
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
|
|
||||||
data class RecentDocumentUiState(
|
|
||||||
val fileUri: Uri,
|
|
||||||
val fileName: String,
|
|
||||||
val saveTimestamp: Long,
|
|
||||||
val pageCount: Int,
|
|
||||||
)
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025-2026 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.home
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import org.fairscan.app.AppContainer
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class HomeViewModel(appContainer: AppContainer, appContext: Context): ViewModel() {
|
|
||||||
|
|
||||||
private val recentDocumentsDataStore = appContainer.recentDocumentsDataStore
|
|
||||||
|
|
||||||
val recentDocuments: StateFlow<List<RecentDocumentUiState>> =
|
|
||||||
recentDocumentsDataStore.data.map {
|
|
||||||
it.documentsList.mapNotNull { doc ->
|
|
||||||
var fileName = doc.fileName
|
|
||||||
var uri: Uri? = null
|
|
||||||
if (doc.fileUri.isNullOrEmpty()) {
|
|
||||||
if (!doc.filePath.isNullOrEmpty()) {
|
|
||||||
val file = File(doc.filePath)
|
|
||||||
uri = file.toUri()
|
|
||||||
fileName = file.name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uri = doc.fileUri.toUri()
|
|
||||||
}
|
|
||||||
if (uri != null) {
|
|
||||||
RecentDocumentUiState(
|
|
||||||
fileUri = uri,
|
|
||||||
fileName = fileName,
|
|
||||||
saveTimestamp = doc.createdAt,
|
|
||||||
pageCount = doc.pageCount,
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
}.filter { item -> uriExists(appContext, item.fileUri) }
|
|
||||||
}.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.WhileSubscribed(5_000),
|
|
||||||
initialValue = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun uriExists(context: Context, uri: Uri): Boolean {
|
|
||||||
return if (uri.scheme == "file") {
|
|
||||||
File(uri.path.orEmpty()).exists()
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
DocumentFile.fromSingleUri(context, uri)?.exists() == true
|
|
||||||
} catch (_: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,7 @@ data class DocumentUiModel(
|
|||||||
return pages.lastIndex
|
return pages.lastIndex
|
||||||
}
|
}
|
||||||
fun thumbnail(index: Int): Bitmap? {
|
fun thumbnail(index: Int): Bitmap? {
|
||||||
return pages[index].thumbnail?.toBitmap()
|
return pages.getOrNull(index)?.thumbnail?.toBitmap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option java_package = "org.fairscan.app";
|
|
||||||
option java_multiple_files = true;
|
|
||||||
|
|
||||||
message RecentDocument {
|
|
||||||
string file_path = 1;
|
|
||||||
int64 created_at = 2; // timestamp in ms
|
|
||||||
int32 page_count = 3;
|
|
||||||
string file_uri = 4;
|
|
||||||
string file_name = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RecentDocuments {
|
|
||||||
repeated RecentDocument documents = 1;
|
|
||||||
}
|
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">اسم الملف</string>
|
<string name="filename">اسم الملف</string>
|
||||||
<string name="grant_permission">امنح الأذن</string>
|
<string name="grant_permission">امنح الأذن</string>
|
||||||
<string name="import_photos">استيراد</string>
|
<string name="import_photos">استيراد</string>
|
||||||
<string name="last_saved_pdf_files">ملفات PDF المحفوظة حديثًا على هذا الجهاز:</string>
|
|
||||||
<string name="libraries">المكتبات</string>
|
<string name="libraries">المكتبات</string>
|
||||||
<string name="libraries_open_source">مكتبات مفتوحة المصدر</string>
|
<string name="libraries_open_source">مكتبات مفتوحة المصدر</string>
|
||||||
<string name="license">الترخيص</string>
|
<string name="license">الترخيص</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Název souboru</string>
|
<string name="filename">Název souboru</string>
|
||||||
<string name="grant_permission">Povolit přístup</string>
|
<string name="grant_permission">Povolit přístup</string>
|
||||||
<string name="import_photos">Import</string>
|
<string name="import_photos">Import</string>
|
||||||
<string name="last_saved_pdf_files">Poslední PDF uložené v tomto zařízení:</string>
|
|
||||||
<string name="libraries">Kníhovny</string>
|
<string name="libraries">Kníhovny</string>
|
||||||
<string name="libraries_open_source">Open-source knihovny</string>
|
<string name="libraries_open_source">Open-source knihovny</string>
|
||||||
<string name="license">Licence</string>
|
<string name="license">Licence</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Dateiname</string>
|
<string name="filename">Dateiname</string>
|
||||||
<string name="grant_permission">Berechtigung erteilen</string>
|
<string name="grant_permission">Berechtigung erteilen</string>
|
||||||
<string name="import_photos">Importieren</string>
|
<string name="import_photos">Importieren</string>
|
||||||
<string name="last_saved_pdf_files">Zuletzt auf diesem Gerät gespeicherte PDFs:</string>
|
|
||||||
<string name="libraries">Bibliotheken</string>
|
<string name="libraries">Bibliotheken</string>
|
||||||
<string name="libraries_open_source">Open-Source-Bibliotheken</string>
|
<string name="libraries_open_source">Open-Source-Bibliotheken</string>
|
||||||
<string name="license">Lizenz</string>
|
<string name="license">Lizenz</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Nombre del archivo</string>
|
<string name="filename">Nombre del archivo</string>
|
||||||
<string name="grant_permission">Conceder permiso</string>
|
<string name="grant_permission">Conceder permiso</string>
|
||||||
<string name="import_photos">Importar</string>
|
<string name="import_photos">Importar</string>
|
||||||
<string name="last_saved_pdf_files">PDF recientes guardados en este dispositivo:</string>
|
|
||||||
<string name="libraries">Bibliotecas</string>
|
<string name="libraries">Bibliotecas</string>
|
||||||
<string name="libraries_open_source">Bibliotecas de código abierto</string>
|
<string name="libraries_open_source">Bibliotecas de código abierto</string>
|
||||||
<string name="license">Licencia</string>
|
<string name="license">Licencia</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Nom de fichier</string>
|
<string name="filename">Nom de fichier</string>
|
||||||
<string name="grant_permission">Autoriser</string>
|
<string name="grant_permission">Autoriser</string>
|
||||||
<string name="import_photos">Importer</string>
|
<string name="import_photos">Importer</string>
|
||||||
<string name="last_saved_pdf_files">Derniers PDF enregistrés sur l’appareil :</string>
|
|
||||||
<string name="libraries">Bibliothèques</string>
|
<string name="libraries">Bibliothèques</string>
|
||||||
<string name="libraries_open_source">Bibliothèques open source</string>
|
<string name="libraries_open_source">Bibliothèques open source</string>
|
||||||
<string name="license">Licence</string>
|
<string name="license">Licence</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Nome do ficheiro</string>
|
<string name="filename">Nome do ficheiro</string>
|
||||||
<string name="grant_permission">Conceder permiso</string>
|
<string name="grant_permission">Conceder permiso</string>
|
||||||
<string name="import_photos">Importar</string>
|
<string name="import_photos">Importar</string>
|
||||||
<string name="last_saved_pdf_files">PDF recentes gardados neste dispositivo:</string>
|
|
||||||
<string name="libraries">Bibliotecas</string>
|
<string name="libraries">Bibliotecas</string>
|
||||||
<string name="libraries_open_source">Bibliotecas de código aberto</string>
|
<string name="libraries_open_source">Bibliotecas de código aberto</string>
|
||||||
<string name="license">Licenza</string>
|
<string name="license">Licenza</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Nome file</string>
|
<string name="filename">Nome file</string>
|
||||||
<string name="grant_permission">Concendi autorizzazione</string>
|
<string name="grant_permission">Concendi autorizzazione</string>
|
||||||
<string name="import_photos">Importa</string>
|
<string name="import_photos">Importa</string>
|
||||||
<string name="last_saved_pdf_files">PDF recenti salvati su questo dispositivo:</string>
|
|
||||||
<string name="libraries">Librerie</string>
|
<string name="libraries">Librerie</string>
|
||||||
<string name="libraries_open_source">Librerie open source</string>
|
<string name="libraries_open_source">Librerie open source</string>
|
||||||
<string name="license">Licenza</string>
|
<string name="license">Licenza</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Nome do arquivo</string>
|
<string name="filename">Nome do arquivo</string>
|
||||||
<string name="grant_permission">Conceder permissão</string>
|
<string name="grant_permission">Conceder permissão</string>
|
||||||
<string name="import_photos">Importar</string>
|
<string name="import_photos">Importar</string>
|
||||||
<string name="last_saved_pdf_files">PDFs recentes salvos neste dispositivo:</string>
|
|
||||||
<string name="libraries">Bibliotecas</string>
|
<string name="libraries">Bibliotecas</string>
|
||||||
<string name="libraries_open_source">Bibliotecas de código aberto</string>
|
<string name="libraries_open_source">Bibliotecas de código aberto</string>
|
||||||
<string name="license">Licença</string>
|
<string name="license">Licença</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Имя файла</string>
|
<string name="filename">Имя файла</string>
|
||||||
<string name="grant_permission">Предоставить разрешение</string>
|
<string name="grant_permission">Предоставить разрешение</string>
|
||||||
<string name="import_photos">Импорт</string>
|
<string name="import_photos">Импорт</string>
|
||||||
<string name="last_saved_pdf_files">Последние PDF, сохранённые на этом устройстве:</string>
|
|
||||||
<string name="libraries">Библиотеки</string>
|
<string name="libraries">Библиотеки</string>
|
||||||
<string name="libraries_open_source">Библиотеки с открытым исходным кодом</string>
|
<string name="libraries_open_source">Библиотеки с открытым исходным кодом</string>
|
||||||
<string name="license">Лицензия</string>
|
<string name="license">Лицензия</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">Dosya adı</string>
|
<string name="filename">Dosya adı</string>
|
||||||
<string name="grant_permission">İzin ver</string>
|
<string name="grant_permission">İzin ver</string>
|
||||||
<string name="import_photos">İçe aktar</string>
|
<string name="import_photos">İçe aktar</string>
|
||||||
<string name="last_saved_pdf_files">Bu cihaza kaydedilen son PDF\'ler:</string>
|
|
||||||
<string name="libraries">Kütüphaneler</string>
|
<string name="libraries">Kütüphaneler</string>
|
||||||
<string name="libraries_open_source">Açık kaynaklı kütüphaneler</string>
|
<string name="libraries_open_source">Açık kaynaklı kütüphaneler</string>
|
||||||
<string name="license">Lisans</string>
|
<string name="license">Lisans</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">檔案名稱</string>
|
<string name="filename">檔案名稱</string>
|
||||||
<string name="grant_permission">授予權限</string>
|
<string name="grant_permission">授予權限</string>
|
||||||
<string name="import_photos">匯入</string>
|
<string name="import_photos">匯入</string>
|
||||||
<string name="last_saved_pdf_files">此裝置上最近儲存的 PDF:</string>
|
|
||||||
<string name="libraries">函式庫</string>
|
<string name="libraries">函式庫</string>
|
||||||
<string name="libraries_open_source">開放原始碼函式庫</string>
|
<string name="libraries_open_source">開放原始碼函式庫</string>
|
||||||
<string name="license">許可證</string>
|
<string name="license">許可證</string>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
<string name="filename">文件名字</string>
|
<string name="filename">文件名字</string>
|
||||||
<string name="grant_permission">授予权限</string>
|
<string name="grant_permission">授予权限</string>
|
||||||
<string name="import_photos">导入</string>
|
<string name="import_photos">导入</string>
|
||||||
<string name="last_saved_pdf_files">最近保存在此设备上的 PDF:</string>
|
|
||||||
<string name="libraries">库</string>
|
<string name="libraries">库</string>
|
||||||
<string name="libraries_open_source">开源库</string>
|
<string name="libraries_open_source">开源库</string>
|
||||||
<string name="license">许可证</string>
|
<string name="license">许可证</string>
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
<string name="filename">Filename</string>
|
<string name="filename">Filename</string>
|
||||||
<string name="grant_permission">Grant permission</string>
|
<string name="grant_permission">Grant permission</string>
|
||||||
<string name="import_photos">Import</string>
|
<string name="import_photos">Import</string>
|
||||||
<string name="last_saved_pdf_files">Recent PDFs saved on this device:</string>
|
|
||||||
<string name="libraries">Libraries</string>
|
<string name="libraries">Libraries</string>
|
||||||
<string name="libraries_open_source">Open-source libraries</string>
|
<string name="libraries_open_source">Open-source libraries</string>
|
||||||
<string name="license">License</string>
|
<string name="license">License</string>
|
||||||
|
|||||||
@@ -15,11 +15,9 @@
|
|||||||
package org.fairscan.app.ui
|
package org.fairscan.app.ui
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.fairscan.app.LaunchMode
|
|
||||||
import org.fairscan.app.ui.Screen.Main.Camera
|
import org.fairscan.app.ui.Screen.Main.Camera
|
||||||
import org.fairscan.app.ui.Screen.Main.Document
|
import org.fairscan.app.ui.Screen.Main.Document
|
||||||
import org.fairscan.app.ui.Screen.Main.Export
|
import org.fairscan.app.ui.Screen.Main.Export
|
||||||
import org.fairscan.app.ui.Screen.Main.Home
|
|
||||||
import org.fairscan.app.ui.Screen.Overlay.About
|
import org.fairscan.app.ui.Screen.Overlay.About
|
||||||
import org.fairscan.app.ui.Screen.Overlay.Libraries
|
import org.fairscan.app.ui.Screen.Overlay.Libraries
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -28,36 +26,33 @@ class NavigationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun empty_ScreenStack() {
|
fun empty_ScreenStack() {
|
||||||
val empty = NavigationState.initial(LaunchMode.NORMAL)
|
val empty = NavigationState.initial()
|
||||||
assertThat(empty.current).isEqualTo(Home)
|
assertThat(empty.current).isEqualTo(Camera)
|
||||||
assertThat(empty.navigateBack()).isEqualTo(empty)
|
assertThat(empty.navigateBack()).isEqualTo(empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun navigate_between_fixed_screens() {
|
fun navigate_between_fixed_screens() {
|
||||||
val atHome = NavigationState.initial(LaunchMode.NORMAL)
|
val atCamera = NavigationState.initial()
|
||||||
val atCamera = atHome.navigateTo(Camera)
|
val atDocument = atCamera.navigateTo(Document())
|
||||||
val atDocument = atHome.navigateTo(Document())
|
val atExport = atCamera.navigateTo(Export)
|
||||||
val atExport = atHome.navigateTo(Export)
|
|
||||||
|
|
||||||
assertThat(atHome.current).isEqualTo(Home)
|
|
||||||
assertThat(atCamera.current).isEqualTo(Camera)
|
assertThat(atCamera.current).isEqualTo(Camera)
|
||||||
assertThat(atDocument.current).isEqualTo(Document())
|
assertThat(atDocument.current).isEqualTo(Document())
|
||||||
assertThat(atExport.current).isEqualTo(Export)
|
assertThat(atExport.current).isEqualTo(Export)
|
||||||
|
|
||||||
assertThat(atCamera.navigateTo(Document())).isEqualTo(atDocument)
|
assertThat(atCamera.navigateTo(Document())).isEqualTo(atDocument)
|
||||||
assertThat(atDocument.navigateTo(Home)).isEqualTo(atHome)
|
assertThat(atDocument.navigateTo(Export)).isEqualTo(atExport)
|
||||||
assertThat(atDocument.navigateTo(Camera)).isEqualTo(atCamera)
|
assertThat(atDocument.navigateTo(Camera)).isEqualTo(atCamera)
|
||||||
|
|
||||||
assertThat(atHome.navigateBack()).isEqualTo(atHome)
|
assertThat(atCamera.navigateBack()).isEqualTo(atCamera)
|
||||||
assertThat(atCamera.navigateBack()).isEqualTo(atHome)
|
|
||||||
assertThat(atDocument.navigateBack()).isEqualTo(atCamera)
|
assertThat(atDocument.navigateBack()).isEqualTo(atCamera)
|
||||||
assertThat(atExport.navigateBack()).isEqualTo(atCamera)
|
assertThat(atExport.navigateBack()).isEqualTo(atCamera)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun navigate_to_secondary_screens() {
|
fun navigate_to_secondary_screens() {
|
||||||
val atHome = NavigationState.initial(LaunchMode.NORMAL)
|
val atHome = NavigationState.initial()
|
||||||
val atCamera = atHome.navigateTo(Camera)
|
val atCamera = atHome.navigateTo(Camera)
|
||||||
|
|
||||||
val atAboutAfterHome = atHome.navigateTo(About)
|
val atAboutAfterHome = atHome.navigateTo(About)
|
||||||
@@ -75,7 +70,7 @@ class NavigationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun external_call() {
|
fun external_call() {
|
||||||
val initial = NavigationState.initial(LaunchMode.EXTERNAL_SCAN_TO_PDF)
|
val initial = NavigationState.initial()
|
||||||
assertThat(initial.current).isEqualTo(Camera)
|
assertThat(initial.current).isEqualTo(Camera)
|
||||||
assertThat(initial.navigateBack().current).isEqualTo(Camera)
|
assertThat(initial.navigateBack().current).isEqualTo(Camera)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ assertj = "3.27.7"
|
|||||||
pdfbox = "2.0.27.0"
|
pdfbox = "2.0.27.0"
|
||||||
zoomable = "2.11.1"
|
zoomable = "2.11.1"
|
||||||
aboutLibraries = "13.2.1"
|
aboutLibraries = "13.2.1"
|
||||||
protobuf = "0.9.6"
|
|
||||||
protobufJavaLite = "4.34.1"
|
|
||||||
kotlinSerialization = "1.10.0"
|
kotlinSerialization = "1.10.0"
|
||||||
reorderable = "3.0.0"
|
reorderable = "3.0.0"
|
||||||
jetbrainsKotlinJvm = "2.3.10"
|
jetbrainsKotlinJvm = "2.3.10"
|
||||||
@@ -49,10 +47,8 @@ androidx-camera-core = { group = "androidx.camera", name = "camera-core", versio
|
|||||||
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
|
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
|
||||||
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
|
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-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" }
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences" , version.ref = "datastore" }
|
||||||
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile" , version.ref = "documentfile" }
|
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile" , version.ref = "documentfile" }
|
||||||
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 = { 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" }
|
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
||||||
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
||||||
@@ -75,5 +71,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
|
|||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
license = { id = "com.github.hierynomus.license", version.ref = "license" }
|
license = { id = "com.github.hierynomus.license", version.ref = "license" }
|
||||||
aboutLibrariesAndroid = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" }
|
aboutLibrariesAndroid = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" }
|
||||||
protobuf = { id = "com.google.protobuf", version.ref = "protobuf" }
|
|
||||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
||||||
|
|||||||
Reference in New Issue
Block a user