diff --git a/README.md b/README.md
index c8db9f1..f1f3120 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,29 @@ FairScan works on any device that:
---
+## Experimental: Scan to PDF via intent
+
+FairScan can be invoked by other Android applications to perform a document scan and return a generated PDF.
+
+This feature is **experimental** and intended for developers who want to rely on FairScan as a
+simple, privacy-respecting scanning tool.
+The intent contract and behavior may change between versions, and backward compatibility
+is not guaranteed at this stage.
+
+Intent action: `org.fairscan.app.action.SCAN_TO_PDF`
+
+This is an **implicit intent** that launches FairScan in a dedicated external mode.
+
+When started via this intent:
+
+- FairScan opens directly in scan mode
+- the user scans one or more pages
+- FairScan generates a single PDF
+- the resulting PDF is returned to the calling application as a URI with a limited lifetime
+- the calling application should immediately copy the content of the URI as FairScan deletes it later
+
+---
+
## Technical details
FairScan uses:
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2d58c80..18a63f7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,9 +27,12 @@
android:theme="@style/Theme.FairScan">
-
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/org/fairscan/app/FairScanApp.kt b/app/src/main/java/org/fairscan/app/FairScanApp.kt
index e065002..7348b7f 100644
--- a/app/src/main/java/org/fairscan/app/FairScanApp.kt
+++ b/app/src/main/java/org/fairscan/app/FairScanApp.kt
@@ -21,16 +21,13 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import org.fairscan.app.data.FileLogger
-import org.fairscan.app.data.ImageRepository
-import org.fairscan.app.data.LogRepository
import org.fairscan.app.data.FileManager
+import org.fairscan.app.data.LogRepository
import org.fairscan.app.data.recentDocumentsDataStore
import org.fairscan.app.domain.ImageSegmentationService
import org.fairscan.app.platform.AndroidPdfWriter
-import org.fairscan.app.platform.OpenCvTransformations
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
@@ -42,15 +39,14 @@ class FairScanApp : Application() {
override fun onCreate() {
super.onCreate()
appContainer = AppContainer(this)
+ appContainer.cleanOrphanSessions()
}
}
const val THUMBNAIL_SIZE_DP = 120
class AppContainer(context: Context) {
- private val density = context.resources.displayMetrics.density
- private val thumbnailSizePx = (THUMBNAIL_SIZE_DP * density).toInt()
- val imageRepository = ImageRepository(context.filesDir, OpenCvTransformations(), thumbnailSizePx)
+ private val cacheDir = context.cacheDir
val preparationDir = File(context.cacheDir, "pdfs")
val fileManager = FileManager(
preparationDir,
@@ -72,10 +68,30 @@ class AppContainer(context: Context) {
}
}
- val mainViewModelFactory = viewModelFactory { MainViewModel(it) }
val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) }
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
- val exportViewModelFactory = viewModelFactory { ExportViewModel(it) }
val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) }
val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) }
+
+ fun cleanOrphanSessions() {
+ val sessionsRoot = sessionsRoot()
+ if (!sessionsRoot.exists()) return
+
+ val now = System.currentTimeMillis()
+
+ sessionsRoot.listFiles()
+ ?.filter { it.isDirectory }
+ ?.forEach { dir ->
+ if (isOldSession(dir, now)) {
+ dir.deleteRecursively()
+ }
+ }
+ }
+
+ fun sessionsRoot(): File = File(cacheDir, "sessions")
+
+ private fun isOldSession(dir: File, now: Long): Boolean {
+ val lastModified = dir.lastModified()
+ return now - lastModified > 24 * 60 * 60 * 1000 // 24h
+ }
}
diff --git a/app/src/main/java/org/fairscan/app/LaunchMode.kt b/app/src/main/java/org/fairscan/app/LaunchMode.kt
new file mode 100644
index 0000000..0000334
--- /dev/null
+++ b/app/src/main/java/org/fairscan/app/LaunchMode.kt
@@ -0,0 +1,20 @@
+/*
+ * 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
+
+enum class LaunchMode {
+ NORMAL,
+ EXTERNAL_SCAN_TO_PDF
+}
diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt
index 1c90299..0ae44af 100644
--- a/app/src/main/java/org/fairscan/app/MainActivity.kt
+++ b/app/src/main/java/org/fairscan/app/MainActivity.kt
@@ -72,17 +72,36 @@ 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
+import java.io.File
+import java.util.UUID
class MainActivity : ComponentActivity() {
+ private lateinit var sessionDir: File
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initLibraries()
val appContainer = (application as FairScanApp).appContainer
- val viewModel: MainViewModel by viewModels { appContainer.mainViewModelFactory }
+ val launchMode = resolveLaunchMode(intent)
+ sessionDir = when (launchMode) {
+ LaunchMode.NORMAL -> filesDir
+ LaunchMode.EXTERNAL_SCAN_TO_PDF ->
+ File(appContainer.sessionsRoot(), UUID.randomUUID().toString()).apply { mkdirs() }
+ }
+ val sessionContainer = ScanSessionContainer(this, sessionDir)
+ val viewModel: MainViewModel by viewModels {
+ appContainer.viewModelFactory {
+ MainViewModel(sessionContainer.imageRepository, launchMode)
+ }
+ }
+ val exportViewModel: ExportViewModel by viewModels {
+ appContainer.viewModelFactory {
+ ExportViewModel(appContainer, sessionContainer.imageRepository)
+ }
+ }
val homeViewModel: HomeViewModel by viewModels { appContainer.homeViewModelFactory }
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 }
@@ -102,7 +121,7 @@ class MainActivity : ComponentActivity() {
CollectAboutEvents(context, aboutViewModel)
FairScanTheme {
- val navigation = navigation(viewModel)
+ val navigation = navigation(viewModel, launchMode)
when (val screen = currentScreen) {
is Screen.Main.Home -> {
val recentDocs by homeViewModel.recentDocuments.collectAsStateWithLifecycle()
@@ -131,6 +150,19 @@ class MainActivity : ComponentActivity() {
document = document,
initialPage = screen.initialPage,
navigation = navigation,
+ onExportClick = if (launchMode == LaunchMode.EXTERNAL_SCAN_TO_PDF) {
+ {
+ lifecycleScope.launch {
+ val result = exportViewModel.generatePdfForExternalCall()
+ sendActivityResult(result)
+ viewModel.startNewDocument()
+ finish()
+ }
+ Unit
+ }
+ } else {
+ navigation.toExportScreen
+ },
onDeleteImage = { id -> viewModel.deletePage(id) },
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) },
onPageReorder = { id, newIndex -> viewModel.movePage(id, newIndex) },
@@ -145,12 +177,12 @@ class MainActivity : ComponentActivity() {
setFilename = exportViewModel::setFilename,
share = { share(exportViewModel.applyRenaming(), exportViewModel) },
save = { exportViewModel.onSaveClicked() },
- open = { item -> openUri(item.uri, item.format.mimeType) }
+ open = { item -> openUri(item.uri, item.format.mimeType) },
),
onCloseScan = {
viewModel.startNewDocument()
viewModel.navigateTo(Screen.Main.Home)
- },
+ }
)
}
is Screen.Overlay.About -> {
@@ -170,6 +202,20 @@ class MainActivity : ComponentActivity() {
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ if (resolveLaunchMode(intent) == LaunchMode.EXTERNAL_SCAN_TO_PDF) {
+ sessionDir.deleteRecursively()
+ }
+ }
+
+ private fun resolveLaunchMode(intent: Intent?): LaunchMode {
+ return when (intent?.action) {
+ "org.fairscan.app.action.SCAN_TO_PDF" -> LaunchMode.EXTERNAL_SCAN_TO_PDF
+ else -> LaunchMode.NORMAL
+ }
+ }
+
@Composable
private fun SettingsScreenWrapper(settingsViewModel: SettingsViewModel, nav: Navigation) {
val launcher = rememberLauncherForActivityResult(
@@ -265,10 +311,7 @@ class MainActivity : ComponentActivity() {
viewModel.setAsShared()
- val authority = "${applicationContext.packageName}.fileprovider"
- val uris = result.files.map { file ->
- FileProvider.getUriForFile(this, authority, file)
- }
+ val uris = result.files.map(::uriForFile)
val intent = Intent().apply {
action = if (uris.size == 1) Intent.ACTION_SEND else Intent.ACTION_SEND_MULTIPLE
type = result.format.mimeType
@@ -293,6 +336,24 @@ class MainActivity : ComponentActivity() {
startActivity(chooser)
}
+ private fun sendActivityResult(result: ExportResult?) {
+ val pdf = result as? ExportResult.Pdf ?: return
+
+ val uri = uriForFile(pdf.file)
+ val resultIntent = Intent().apply {
+ data = uri
+ clipData = ClipData.newRawUri(null, uri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ setResult(RESULT_OK, resultIntent)
+ }
+
+ private fun uriForFile(file: File): Uri {
+ val authority = "${applicationContext.packageName}.fileprovider"
+ return FileProvider.getUriForFile(this, authority, file)
+ }
+
private fun checkPermissionThen(
requestPermissionLauncher: ManagedActivityResultLauncher,
action: () -> Unit
@@ -312,8 +373,7 @@ class MainActivity : ComponentActivity() {
if (fileUri.scheme == ContentResolver.SCHEME_CONTENT) {
fileUri
} else {
- val authority = "${applicationContext.packageName}.fileprovider"
- FileProvider.getUriForFile(this, authority, fileUri.toFile())
+ uriForFile(fileUri.toFile())
}
val openIntent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uriToOpen, mimeType)
@@ -335,15 +395,28 @@ class MainActivity : ComponentActivity() {
Log.d("OpenCV", "Initialization successful")
}
}
+
+ private fun navigation(viewModel: MainViewModel, launchMode: LaunchMode): Navigation = Navigation(
+ toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
+ toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
+ toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
+ toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
+ toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },
+ toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) },
+ toSettingsScreen = if (launchMode == LaunchMode.EXTERNAL_SCAN_TO_PDF) null else {
+ {
+ viewModel.navigateTo(Screen.Overlay.Settings)
+ }
+ },
+ back = {
+ val origin = viewModel.currentScreen.value
+ viewModel.navigateBack()
+ val destination = viewModel.currentScreen.value
+ if (destination == origin && launchMode == LaunchMode.EXTERNAL_SCAN_TO_PDF) {
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+ }
+ )
}
-private fun navigation(viewModel: MainViewModel): Navigation = Navigation(
- toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
- toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
- toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
- 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/MainViewModel.kt b/app/src/main/java/org/fairscan/app/MainViewModel.kt
index e432807..750fac7 100644
--- a/app/src/main/java/org/fairscan/app/MainViewModel.kt
+++ b/app/src/main/java/org/fairscan/app/MainViewModel.kt
@@ -26,15 +26,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import org.fairscan.app.data.ImageRepository
import org.fairscan.app.ui.NavigationState
import org.fairscan.app.ui.Screen
import org.fairscan.app.ui.state.DocumentUiModel
-class MainViewModel(appContainer: AppContainer): ViewModel() {
+class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode): ViewModel() {
- private val imageRepository = appContainer.imageRepository
-
- private val _navigationState = MutableStateFlow(NavigationState.initial())
+ private val _navigationState = MutableStateFlow(NavigationState.initial(launchMode))
val currentScreen: StateFlow = _navigationState.map { it.current }
.stateIn(viewModelScope, SharingStarted.Eagerly, _navigationState.value.current)
diff --git a/app/src/main/java/org/fairscan/app/ScanSessionContainer.kt b/app/src/main/java/org/fairscan/app/ScanSessionContainer.kt
new file mode 100644
index 0000000..8cc4ac2
--- /dev/null
+++ b/app/src/main/java/org/fairscan/app/ScanSessionContainer.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+import android.content.Context
+import org.fairscan.app.data.ImageRepository
+import org.fairscan.app.platform.OpenCvTransformations
+import java.io.File
+
+class ScanSessionContainer(
+ context: Context,
+ scanRootDir: File
+) {
+ private val density = context.resources.displayMetrics.density
+ private val thumbnailSizePx = (THUMBNAIL_SIZE_DP * density).toInt()
+
+ val imageRepository = ImageRepository(
+ scanRootDir,
+ OpenCvTransformations(),
+ thumbnailSizePx
+ )
+}
diff --git a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt
index 5eb1469..101c0ff 100644
--- a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt
+++ b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt
@@ -23,16 +23,16 @@ const val SCAN_DIR_NAME = "scanned_pages"
const val THUMBNAIL_DIR_NAME = "thumbnails"
class ImageRepository(
- appFilesDir: File,
+ scanRootDir: File,
val transformations: ImageTransformations,
private val thumbnailSizePx: Int,
) {
- private val scanDir: File = File(appFilesDir, SCAN_DIR_NAME).apply {
+ private val scanDir: File = File(scanRootDir, SCAN_DIR_NAME).apply {
if (!exists()) mkdirs()
}
- private val thumbnailDir: File = File(appFilesDir, THUMBNAIL_DIR_NAME).apply {
+ private val thumbnailDir: File = File(scanRootDir, THUMBNAIL_DIR_NAME).apply {
if (!exists()) mkdirs()
}
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 9ceb6a4..c4feb06 100644
--- a/app/src/main/java/org/fairscan/app/ui/Navigation.kt
+++ b/app/src/main/java/org/fairscan/app/ui/Navigation.kt
@@ -14,6 +14,8 @@
*/
package org.fairscan.app.ui
+import org.fairscan.app.LaunchMode
+
sealed class Screen {
sealed class Main : Screen() {
object Home : Main()
@@ -35,15 +37,24 @@ data class Navigation(
val toExportScreen: () -> Unit,
val toAboutScreen: () -> Unit,
val toLibrariesScreen: () -> Unit,
- val toSettingsScreen: () -> Unit,
+ val toSettingsScreen: (() -> 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
-data class NavigationState private constructor(val stack: List) {
+data class NavigationState private constructor(val stack: List, val root: Screen.Main) {
companion object {
- fun initial() = NavigationState(listOf(Screen.Main.Home))
+ fun initial(mode: LaunchMode): NavigationState {
+ val root = startScreenFor(mode)
+ return NavigationState(listOf(root), root)
+ }
}
val current: Screen get() = stack.last()
@@ -58,6 +69,7 @@ data class NavigationState private constructor(val stack: List) {
fun navigateBack(): NavigationState {
return when (current) {
+ root -> this // Back handled by system
is Screen.Main.Home -> 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))
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 b593aee..63f9dcc 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
@@ -181,14 +181,16 @@ fun AppOverflowMenu(
.background(MaterialTheme.colorScheme.surface)
) {
- DropdownMenuItem(
- leadingIcon = { Icon(Icons.Default.Settings, contentDescription = null) },
- text = { Text(stringResource(R.string.settings)) },
- onClick = {
- expanded = false
- navigation.toSettingsScreen()
- }
- )
+ navigation.toSettingsScreen?.let { toSettings ->
+ DropdownMenuItem(
+ leadingIcon = { Icon(Icons.Default.Settings, contentDescription = null) },
+ text = { Text(stringResource(R.string.settings)) },
+ onClick = {
+ expanded = false
+ toSettings()
+ }
+ )
+ }
DropdownMenuItem(
leadingIcon = { Icon(Icons.Default.Info, contentDescription = null) },
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 7bc1285..7fd2cad 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
@@ -73,6 +73,7 @@ fun DocumentScreen(
document: DocumentUiModel,
initialPage: Int,
navigation: Navigation,
+ onExportClick: () -> Unit,
onDeleteImage: (String) -> Unit,
onRotateImage: (String, Boolean) -> Unit,
onPageReorder: (String, Int) -> Unit,
@@ -105,7 +106,7 @@ fun DocumentScreen(
),
onBack = navigation.back,
bottomBar = {
- BottomBar(navigation)
+ BottomBar(onExportClick)
},
pageListButton = {
SecondaryActionButton(
@@ -217,7 +218,7 @@ fun RotationButtons(
@Composable
private fun BottomBar(
- navigation: Navigation,
+ onExportClick: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -225,7 +226,7 @@ private fun BottomBar(
horizontalArrangement = Arrangement.End
) {
MainActionButton(
- onClick = navigation.toExportScreen,
+ onClick = onExportClick,
icon = Icons.Default.Description,
text = stringResource(R.string.export),
)
@@ -243,6 +244,7 @@ fun DocumentScreenPreview() {
),
initialPage = 1,
navigation = dummyNavigation(),
+ onExportClick = {},
onDeleteImage = { _ -> },
onRotateImage = { _,_ -> },
onPageReorder = { _,_ -> },
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 1ce858a..c1eb900 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
@@ -75,7 +75,6 @@ import org.fairscan.app.ui.components.NewDocumentDialog
import org.fairscan.app.ui.components.isLandscape
import org.fairscan.app.ui.components.pageCountText
import org.fairscan.app.ui.dummyNavigation
-import org.fairscan.app.ui.screens.settings.ExportFormat
import org.fairscan.app.ui.screens.settings.ExportFormat.PDF
import org.fairscan.app.ui.theme.FairScanTheme
import java.io.File
diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt
index bb6e422..4958848 100644
--- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt
+++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt
@@ -21,7 +21,6 @@ data class ExportUiState(
val format: ExportFormat = ExportFormat.PDF,
val isGenerating: Boolean = false,
val result: ExportResult? = null,
- val desiredFilename: String = "",
val savedBundle: SavedBundle? = null,
val hasShared: Boolean = false,
val errorMessage: String? = null,
diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt
index 5ca040b..e8d7435 100644
--- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt
+++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt
@@ -35,6 +35,7 @@ import kotlinx.coroutines.withContext
import org.fairscan.app.AppContainer
import org.fairscan.app.RecentDocument
import org.fairscan.app.data.FileManager
+import org.fairscan.app.data.ImageRepository
import org.fairscan.app.ui.screens.settings.ExportFormat
import java.io.File
import java.io.FileInputStream
@@ -46,11 +47,10 @@ sealed interface ExportEvent {
data object SaveError : ExportEvent
}
-class ExportViewModel(container: AppContainer): ViewModel() {
+class ExportViewModel(container: AppContainer, val imageRepository: ImageRepository): ViewModel() {
private val preparationDir = container.preparationDir
private val fileManager = container.fileManager
- private val imageRepository = container.imageRepository
private val settingsRepository = container.settingsRepository
private val recentDocumentsDataStore = container.recentDocumentsDataStore
private val logger = container.logger
@@ -66,6 +66,10 @@ class ExportViewModel(container: AppContainer): ViewModel() {
return@withContext ExportResult.Pdf(pdf.file, pdf.sizeInBytes, pdf.pageCount)
}
+ suspend fun generatePdfForExternalCall(): ExportResult.Pdf {
+ return generatePdf()
+ }
+
private val _uiState = MutableStateFlow(ExportUiState())
val uiState: StateFlow = _uiState.asStateFlow()
diff --git a/app/src/test/java/org/fairscan/app/ui/NavigationTest.kt b/app/src/test/java/org/fairscan/app/ui/NavigationTest.kt
index 54f34d5..12d9953 100644
--- a/app/src/test/java/org/fairscan/app/ui/NavigationTest.kt
+++ b/app/src/test/java/org/fairscan/app/ui/NavigationTest.kt
@@ -15,6 +15,7 @@
package org.fairscan.app.ui
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.Document
import org.fairscan.app.ui.Screen.Main.Export
@@ -27,14 +28,14 @@ class NavigationTest {
@Test
fun empty_ScreenStack() {
- val empty = NavigationState.initial()
+ val empty = NavigationState.initial(LaunchMode.NORMAL)
assertThat(empty.current).isEqualTo(Home)
assertThat(empty.navigateBack()).isEqualTo(empty)
}
@Test
fun navigate_between_fixed_screens() {
- val atHome = NavigationState.initial()
+ val atHome = NavigationState.initial(LaunchMode.NORMAL)
val atCamera = atHome.navigateTo(Camera)
val atDocument = atHome.navigateTo(Document())
val atExport = atHome.navigateTo(Export)
@@ -56,7 +57,7 @@ class NavigationTest {
@Test
fun navigate_to_secondary_screens() {
- val atHome = NavigationState.initial()
+ val atHome = NavigationState.initial(LaunchMode.NORMAL)
val atCamera = atHome.navigateTo(Camera)
val atAboutAfterHome = atHome.navigateTo(About)
@@ -71,4 +72,11 @@ class NavigationTest {
assertThat(atLibrariesAfterCameraAbout.current).isEqualTo(Libraries)
assertThat(atLibrariesAfterCameraAbout.navigateBack()).isEqualTo(atAboutAfterCamera)
}
+
+ @Test
+ fun external_call() {
+ val initial = NavigationState.initial(LaunchMode.EXTERNAL_SCAN_TO_PDF)
+ assertThat(initial.current).isEqualTo(Camera)
+ assertThat(initial.navigateBack().current).isEqualTo(Camera)
+ }
}
\ No newline at end of file