diff --git a/app/src/main/java/org/fairscan/app/FairScanApp.kt b/app/src/main/java/org/fairscan/app/FairScanApp.kt index a072fb3..aa33775 100644 --- a/app/src/main/java/org/fairscan/app/FairScanApp.kt +++ b/app/src/main/java/org/fairscan/app/FairScanApp.kt @@ -72,7 +72,7 @@ class AppContainer(context: Context) { } val mainViewModelFactory = viewModelFactory { MainViewModel(it) } - val homeViewModelFactory = viewModelFactory { HomeViewModel(it) } + val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) } val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) } val exportViewModelFactory = viewModelFactory { ExportViewModel(it) } val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) } diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 608ec95..10c95d4 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -113,7 +113,7 @@ class MainActivity : ComponentActivity() { navigation = navigation, onClearScan = { viewModel.startNewDocument() }, recentDocuments = recentDocs, - onOpenPdf = { file -> openPdf(file.toUri()) } + onOpenPdf = { fileUri -> openPdf(fileUri) } ) } is Screen.Main.Camera -> { 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 efed805..99639ac 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 @@ -145,13 +145,17 @@ class ExportViewModel(container: AppContainer): ViewModel() { val exportDir = settingsRepository.exportDirUri.first() var fileInDownloads: File? = null - val savedUri: Uri = - if (exportDir == null) { - fileInDownloads = pdfFileManager.copyToExternalDir(pdf.file) - fileInDownloads.toUri() - } else { - copyViaSaf(context, pdf.file, exportDir.toUri()) - } + var savedName: String + val savedUri: Uri + if (exportDir == null) { + fileInDownloads = pdfFileManager.copyToExternalDir(pdf.file) + savedUri = fileInDownloads.toUri() + savedName = fileInDownloads.name + } else { + val saved = copyViaSaf(context, pdf.file, exportDir.toUri()) + savedUri = saved.uri + savedName = saved.name?:pdf.file.name + } _pdfUiState.update { it.copy( @@ -162,11 +166,7 @@ class ExportViewModel(container: AppContainer): ViewModel() { fileInDownloads?.let { mediaScan(context, it) } // TODO remove that call: that should be handled through the ExportEvent - homeViewModel.addRecentDocument( - // FIXME This is not a file path - savedUri.toString(), - pdf.pageCount - ) + homeViewModel.addRecentDocument(savedUri, savedName, pdf.pageCount) } catch (e: Exception) { logger.e("FairScan", "Failed to save PDF", e) _events.emit(ExportEvent.SaveError) @@ -187,7 +187,7 @@ class ExportViewModel(container: AppContainer): ViewModel() { context: Context, source: File, exportDirUri: Uri, - ): Uri { + ): DocumentFile { val resolver = context.contentResolver val tree = DocumentFile.fromTreeUri(context, exportDirUri) @@ -203,7 +203,7 @@ class ExportViewModel(container: AppContainer): ViewModel() { } } ?: throw IllegalStateException("Failed to open SAF output stream") - return target.uri + return target } fun cleanUpOldPdfs(thresholdInMillis: Int) { diff --git a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt index 5fbdf75..589cc16 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt @@ -14,6 +14,7 @@ */ 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 @@ -54,6 +55,7 @@ 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 @@ -76,7 +78,7 @@ fun HomeScreen( navigation: Navigation, onClearScan: () -> Unit, recentDocuments: List, - onOpenPdf: (File) -> Unit, + onOpenPdf: (Uri) -> Unit, ) { Scaffold ( topBar = { @@ -227,7 +229,7 @@ fun OngoingScanBanner( @Composable private fun RecentDocumentList( recentDocuments: List, - onOpenPdf: (File) -> Unit + onOpenPdf: (Uri) -> Unit ) { Spacer(Modifier.height(8.dp)) Text( @@ -241,7 +243,7 @@ private fun RecentDocumentList( ListItem( headlineContent = { Text( - doc.file.name, + doc.fileName, style = MaterialTheme.typography.bodyMedium ) }, @@ -260,7 +262,7 @@ private fun RecentDocumentList( tint = MaterialTheme.colorScheme.onSurfaceVariant ) }, - modifier = Modifier.clickable { onOpenPdf(doc.file) } + modifier = Modifier.clickable { onOpenPdf(doc.fileUri) } ) } } @@ -309,8 +311,10 @@ fun HomeScreenPreviewWithLastSavedFiles() { navigation = dummyNavigation(), onClearScan = {}, recentDocuments = listOf( - RecentDocumentUiState(File("/path/my_file.pdf"), 1755971180000, 3), - RecentDocumentUiState(File("/path/scan2.pdf"), 1755000500000, 1) + RecentDocumentUiState( + File("/path/my_file.pdf").toUri(), "my_file.pdf", 1755971180000, 3), + RecentDocumentUiState( + "content:///path/scan2.pdf".toUri(), "scan2.pdf",1755000500000, 1) ), onOpenPdf = {}, ) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeUiState.kt b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeUiState.kt index 00cd021..5097800 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeUiState.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeUiState.kt @@ -14,10 +14,11 @@ */ package org.fairscan.app.ui.screens.home -import java.io.File +import android.net.Uri data class RecentDocumentUiState( - val file: File, + val fileUri: Uri, + val fileName: String, val saveTimestamp: Long, val pageCount: Int, ) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt index 84073f9..a8c9565 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt @@ -14,6 +14,10 @@ */ 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 @@ -25,30 +29,45 @@ import org.fairscan.app.AppContainer import org.fairscan.app.RecentDocument import java.io.File -class HomeViewModel(appContainer: AppContainer): ViewModel() { +class HomeViewModel(appContainer: AppContainer, appContext: Context): ViewModel() { private val recentDocumentsDataStore = appContainer.recentDocumentsDataStore val recentDocuments: StateFlow> = recentDocumentsDataStore.data.map { - it.documentsList.map { - doc -> - RecentDocumentUiState( - file = File(doc.filePath), - saveTimestamp = doc.createdAt, - pageCount = doc.pageCount, - ) - }.filter { doc -> doc.file.exists() } + 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(), ) - fun addRecentDocument(filePath: String, pageCount: Int) { + + fun addRecentDocument(fileUri: Uri, fileName: String, pageCount: Int) { viewModelScope.launch { recentDocumentsDataStore.updateData { current -> val newDoc = RecentDocument.newBuilder() - .setFilePath(filePath) + .setFileUri(fileUri.toString()) + .setFileName(fileName) .setPageCount(pageCount) .setCreatedAt(System.currentTimeMillis()) .build() @@ -64,4 +83,16 @@ class HomeViewModel(appContainer: AppContainer): ViewModel() { } } + 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 + } + } + } + } diff --git a/app/src/main/proto/recent_documents.proto b/app/src/main/proto/recent_documents.proto index 8d4fcdb..f31441f 100644 --- a/app/src/main/proto/recent_documents.proto +++ b/app/src/main/proto/recent_documents.proto @@ -7,6 +7,8 @@ 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 { diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1c62f8e..c697d1d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -27,7 +27,7 @@ Velikost souboru: %1$s Název souboru Povolit přístup - Poslední PDF uložené do stažených: + Poslední PDF uložené v tomto zařízení: Kníhovny Tato aplikace využívá několik open-source knihoven včetně: Open-source knihovny diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6aa6c82..53107d6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -27,7 +27,7 @@ Dateigröße: %1$s Dateiname Berechtigung erteilen - Zuletzt gespeicherte PDFs in Downloads: + Zuletzt auf diesem Gerät gespeicherte PDFs: Bibliotheken Diese Anwendung verwendet mehrere Open-Source-Bibliotheken, darunter: Open-Source-Bibliotheken diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f1c1af6..ad2a319 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -27,7 +27,7 @@ Tamaño del archivo: %1$s Nombre del archivo Conceder permiso - PDF recientes guardados en Descargas: + PDF recientes guardados en este dispositivo: Bibliotecas Esta aplicación utiliza varias bibliotecas de código abierto, incluidas: Bibliotecas de código abierto diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 3d68574..e250870 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -27,7 +27,7 @@ Taille du fichier : %1$s Nom de fichier Autoriser - Derniers PDF enregistrés dans Téléchargements : + Derniers PDF enregistrés sur l’appareil : Bibliothèques Cette application utilise plusieurs bibliothèques open source, notamment : Bibliothèques open source diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c94c3a7..ddf51ff 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -27,7 +27,7 @@ Dimensione file: %1$s Nome file Concendi autorizzazione - PDF recenti salvati in Download: + PDF recenti salvati su questo dispositivo: Librerie Questa app usa diverse librerie open source, incluse: Librerie open source diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6808211..0676630 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -27,7 +27,7 @@ Tamanho do arquivo: %1$s Nome do arquivo Conceder permissão - PDFs recentes salvos em Downloads: + PDFs recentes salvos neste dispositivo: Bibliotecas Este aplicativo usa várias bibliotecas de código aberto, incluindo: Bibliotecas de código aberto diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2779caf..43e5835 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -27,7 +27,7 @@ Размер файла: %1$s Имя файла Предоставить разрешение - Последние PDF сохранены в Download: + Последние PDF, сохранённые на этом устройстве: Библиотеки Это приложение использует ряд библиотек с открытым исходным кодом, включая: Библиотеки с открытым исходным кодом diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 2d9a818..72d389a 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -27,7 +27,7 @@ 文件大小: %1$s 文件名字 授予权限 - 最近保存的PDF: + 最近保存在此设备上的 PDF: 本应用使用的开源库: 开源库 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b07a379..649002f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ File size: %1$s Filename Grant permission - Recent PDFs saved in Downloads: + Recent PDFs saved on this device: Libraries This application uses several open-source libraries, including: Open-source libraries