Fix list of recent documents after introduction of the custom export dir

This commit is contained in:
Pierre-Yves Nicolas
2025-11-28 13:28:24 +01:00
committed by pynicolas
parent 5d7011614b
commit 2d15cd129e
16 changed files with 82 additions and 44 deletions

View File

@@ -72,7 +72,7 @@ class AppContainer(context: Context) {
} }
val mainViewModelFactory = viewModelFactory { MainViewModel(it) } val mainViewModelFactory = viewModelFactory { MainViewModel(it) }
val homeViewModelFactory = viewModelFactory { HomeViewModel(it) } val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) }
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) } val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
val exportViewModelFactory = viewModelFactory { ExportViewModel(it) } val exportViewModelFactory = viewModelFactory { ExportViewModel(it) }
val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) } val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) }

View File

@@ -113,7 +113,7 @@ class MainActivity : ComponentActivity() {
navigation = navigation, navigation = navigation,
onClearScan = { viewModel.startNewDocument() }, onClearScan = { viewModel.startNewDocument() },
recentDocuments = recentDocs, recentDocuments = recentDocs,
onOpenPdf = { file -> openPdf(file.toUri()) } onOpenPdf = { fileUri -> openPdf(fileUri) }
) )
} }
is Screen.Main.Camera -> { is Screen.Main.Camera -> {

View File

@@ -145,12 +145,16 @@ class ExportViewModel(container: AppContainer): ViewModel() {
val exportDir = settingsRepository.exportDirUri.first() val exportDir = settingsRepository.exportDirUri.first()
var fileInDownloads: File? = null var fileInDownloads: File? = null
val savedUri: Uri = var savedName: String
val savedUri: Uri
if (exportDir == null) { if (exportDir == null) {
fileInDownloads = pdfFileManager.copyToExternalDir(pdf.file) fileInDownloads = pdfFileManager.copyToExternalDir(pdf.file)
fileInDownloads.toUri() savedUri = fileInDownloads.toUri()
savedName = fileInDownloads.name
} else { } else {
copyViaSaf(context, pdf.file, exportDir.toUri()) val saved = copyViaSaf(context, pdf.file, exportDir.toUri())
savedUri = saved.uri
savedName = saved.name?:pdf.file.name
} }
_pdfUiState.update { _pdfUiState.update {
@@ -162,11 +166,7 @@ class ExportViewModel(container: AppContainer): ViewModel() {
fileInDownloads?.let { mediaScan(context, it) } fileInDownloads?.let { mediaScan(context, it) }
// TODO remove that call: that should be handled through the ExportEvent // TODO remove that call: that should be handled through the ExportEvent
homeViewModel.addRecentDocument( homeViewModel.addRecentDocument(savedUri, savedName, pdf.pageCount)
// FIXME This is not a file path
savedUri.toString(),
pdf.pageCount
)
} catch (e: Exception) { } catch (e: Exception) {
logger.e("FairScan", "Failed to save PDF", e) logger.e("FairScan", "Failed to save PDF", e)
_events.emit(ExportEvent.SaveError) _events.emit(ExportEvent.SaveError)
@@ -187,7 +187,7 @@ class ExportViewModel(container: AppContainer): ViewModel() {
context: Context, context: Context,
source: File, source: File,
exportDirUri: Uri, exportDirUri: Uri,
): Uri { ): DocumentFile {
val resolver = context.contentResolver val resolver = context.contentResolver
val tree = DocumentFile.fromTreeUri(context, exportDirUri) val tree = DocumentFile.fromTreeUri(context, exportDirUri)
@@ -203,7 +203,7 @@ class ExportViewModel(container: AppContainer): ViewModel() {
} }
} ?: throw IllegalStateException("Failed to open SAF output stream") } ?: throw IllegalStateException("Failed to open SAF output stream")
return target.uri return target
} }
fun cleanUpOldPdfs(thresholdInMillis: Int) { fun cleanUpOldPdfs(thresholdInMillis: Int) {

View File

@@ -14,6 +14,7 @@
*/ */
package org.fairscan.app.ui.screens.home package org.fairscan.app.ui.screens.home
import android.net.Uri
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column 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.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import org.fairscan.app.R import org.fairscan.app.R
import org.fairscan.app.ui.Navigation import org.fairscan.app.ui.Navigation
@@ -76,7 +78,7 @@ fun HomeScreen(
navigation: Navigation, navigation: Navigation,
onClearScan: () -> Unit, onClearScan: () -> Unit,
recentDocuments: List<RecentDocumentUiState>, recentDocuments: List<RecentDocumentUiState>,
onOpenPdf: (File) -> Unit, onOpenPdf: (Uri) -> Unit,
) { ) {
Scaffold ( Scaffold (
topBar = { topBar = {
@@ -227,7 +229,7 @@ fun OngoingScanBanner(
@Composable @Composable
private fun RecentDocumentList( private fun RecentDocumentList(
recentDocuments: List<RecentDocumentUiState>, recentDocuments: List<RecentDocumentUiState>,
onOpenPdf: (File) -> Unit onOpenPdf: (Uri) -> Unit
) { ) {
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( Text(
@@ -241,7 +243,7 @@ private fun RecentDocumentList(
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
doc.file.name, doc.fileName,
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
}, },
@@ -260,7 +262,7 @@ private fun RecentDocumentList(
tint = MaterialTheme.colorScheme.onSurfaceVariant tint = MaterialTheme.colorScheme.onSurfaceVariant
) )
}, },
modifier = Modifier.clickable { onOpenPdf(doc.file) } modifier = Modifier.clickable { onOpenPdf(doc.fileUri) }
) )
} }
} }
@@ -309,8 +311,10 @@ fun HomeScreenPreviewWithLastSavedFiles() {
navigation = dummyNavigation(), navigation = dummyNavigation(),
onClearScan = {}, onClearScan = {},
recentDocuments = listOf( recentDocuments = listOf(
RecentDocumentUiState(File("/path/my_file.pdf"), 1755971180000, 3), RecentDocumentUiState(
RecentDocumentUiState(File("/path/scan2.pdf"), 1755000500000, 1) File("/path/my_file.pdf").toUri(), "my_file.pdf", 1755971180000, 3),
RecentDocumentUiState(
"content:///path/scan2.pdf".toUri(), "scan2.pdf",1755000500000, 1)
), ),
onOpenPdf = {}, onOpenPdf = {},
) )

View File

@@ -14,10 +14,11 @@
*/ */
package org.fairscan.app.ui.screens.home package org.fairscan.app.ui.screens.home
import java.io.File import android.net.Uri
data class RecentDocumentUiState( data class RecentDocumentUiState(
val file: File, val fileUri: Uri,
val fileName: String,
val saveTimestamp: Long, val saveTimestamp: Long,
val pageCount: Int, val pageCount: Int,
) )

View File

@@ -14,6 +14,10 @@
*/ */
package org.fairscan.app.ui.screens.home 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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -25,30 +29,45 @@ import org.fairscan.app.AppContainer
import org.fairscan.app.RecentDocument import org.fairscan.app.RecentDocument
import java.io.File import java.io.File
class HomeViewModel(appContainer: AppContainer): ViewModel() { class HomeViewModel(appContainer: AppContainer, appContext: Context): ViewModel() {
private val recentDocumentsDataStore = appContainer.recentDocumentsDataStore private val recentDocumentsDataStore = appContainer.recentDocumentsDataStore
val recentDocuments: StateFlow<List<RecentDocumentUiState>> = val recentDocuments: StateFlow<List<RecentDocumentUiState>> =
recentDocumentsDataStore.data.map { recentDocumentsDataStore.data.map {
it.documentsList.map { it.documentsList.mapNotNull { doc ->
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( RecentDocumentUiState(
file = File(doc.filePath), fileUri = uri,
fileName = fileName,
saveTimestamp = doc.createdAt, saveTimestamp = doc.createdAt,
pageCount = doc.pageCount, pageCount = doc.pageCount,
) )
}.filter { doc -> doc.file.exists() } } else null
}.filter { item -> uriExists(appContext, item.fileUri) }
}.stateIn( }.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000), started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(), initialValue = emptyList(),
) )
fun addRecentDocument(filePath: String, pageCount: Int) {
fun addRecentDocument(fileUri: Uri, fileName: String, pageCount: Int) {
viewModelScope.launch { viewModelScope.launch {
recentDocumentsDataStore.updateData { current -> recentDocumentsDataStore.updateData { current ->
val newDoc = RecentDocument.newBuilder() val newDoc = RecentDocument.newBuilder()
.setFilePath(filePath) .setFileUri(fileUri.toString())
.setFileName(fileName)
.setPageCount(pageCount) .setPageCount(pageCount)
.setCreatedAt(System.currentTimeMillis()) .setCreatedAt(System.currentTimeMillis())
.build() .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
}
}
}
} }

View File

@@ -7,6 +7,8 @@ message RecentDocument {
string file_path = 1; string file_path = 1;
int64 created_at = 2; // timestamp in ms int64 created_at = 2; // timestamp in ms
int32 page_count = 3; int32 page_count = 3;
string file_uri = 4;
string file_name = 5;
} }
message RecentDocuments { message RecentDocuments {

View File

@@ -27,7 +27,7 @@
<string name="file_size">Velikost souboru: %1$s</string> <string name="file_size">Velikost souboru: %1$s</string>
<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="last_saved_pdf_files">Poslední PDF uložené do stažených:</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_intro">Tato aplikace využívá několik open-source knihoven včetně:</string> <string name="libraries_intro">Tato aplikace využívá několik open-source knihoven včetně:</string>
<string name="libraries_open_source">Open-source knihovny</string> <string name="libraries_open_source">Open-source knihovny</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Dateigröße: %1$s</string> <string name="file_size">Dateigröße: %1$s</string>
<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="last_saved_pdf_files">Zuletzt gespeicherte PDFs in Downloads:</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_intro">Diese Anwendung verwendet mehrere Open-Source-Bibliotheken, darunter:</string> <string name="libraries_intro">Diese Anwendung verwendet mehrere Open-Source-Bibliotheken, darunter:</string>
<string name="libraries_open_source">Open-Source-Bibliotheken</string> <string name="libraries_open_source">Open-Source-Bibliotheken</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Tamaño del archivo: %1$s</string> <string name="file_size">Tamaño del archivo: %1$s</string>
<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="last_saved_pdf_files">PDF recientes guardados en Descargas:</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_intro">Esta aplicación utiliza varias bibliotecas de código abierto, incluidas:</string> <string name="libraries_intro">Esta aplicación utiliza varias bibliotecas de código abierto, incluidas:</string>
<string name="libraries_open_source">Bibliotecas de código abierto</string> <string name="libraries_open_source">Bibliotecas de código abierto</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Taille du fichier : %1$s</string> <string name="file_size">Taille du fichier : %1$s</string>
<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="last_saved_pdf_files">Derniers PDF enregistrés dans Téléchargements :</string> <string name="last_saved_pdf_files">Derniers PDF enregistrés sur lappareil :</string>
<string name="libraries">Bibliothèques</string> <string name="libraries">Bibliothèques</string>
<string name="libraries_intro">Cette application utilise plusieurs bibliothèques open source, notamment :</string> <string name="libraries_intro">Cette application utilise plusieurs bibliothèques open source, notamment :</string>
<string name="libraries_open_source">Bibliothèques open source</string> <string name="libraries_open_source">Bibliothèques open source</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Dimensione file: %1$s</string> <string name="file_size">Dimensione file: %1$s</string>
<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="last_saved_pdf_files">PDF recenti salvati in Download:</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_intro">Questa app usa diverse librerie open source, incluse:</string> <string name="libraries_intro">Questa app usa diverse librerie open source, incluse:</string>
<string name="libraries_open_source">Librerie open source</string> <string name="libraries_open_source">Librerie open source</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Tamanho do arquivo: %1$s</string> <string name="file_size">Tamanho do arquivo: %1$s</string>
<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="last_saved_pdf_files">PDFs recentes salvos em Downloads:</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_intro">Este aplicativo usa várias bibliotecas de código aberto, incluindo:</string> <string name="libraries_intro">Este aplicativo usa várias bibliotecas de código aberto, incluindo:</string>
<string name="libraries_open_source">Bibliotecas de código aberto</string> <string name="libraries_open_source">Bibliotecas de código aberto</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">Размер файла: %1$s</string> <string name="file_size">Размер файла: %1$s</string>
<string name="filename">Имя файла</string> <string name="filename">Имя файла</string>
<string name="grant_permission">Предоставить разрешение</string> <string name="grant_permission">Предоставить разрешение</string>
<string name="last_saved_pdf_files">Последние PDF сохранены в Download:</string> <string name="last_saved_pdf_files">Последние PDF, сохранённые на этом устройстве:</string>
<string name="libraries">Библиотеки</string> <string name="libraries">Библиотеки</string>
<string name="libraries_intro">Это приложение использует ряд библиотек с открытым исходным кодом, включая:</string> <string name="libraries_intro">Это приложение использует ряд библиотек с открытым исходным кодом, включая:</string>
<string name="libraries_open_source">Библиотеки с открытым исходным кодом</string> <string name="libraries_open_source">Библиотеки с открытым исходным кодом</string>

View File

@@ -27,7 +27,7 @@
<string name="file_size">文件大小: %1$s</string> <string name="file_size">文件大小: %1$s</string>
<string name="filename">文件名字</string> <string name="filename">文件名字</string>
<string name="grant_permission">授予权限</string> <string name="grant_permission">授予权限</string>
<string name="last_saved_pdf_files">最近保存PDF:</string> <string name="last_saved_pdf_files">最近保存在此设备上的 PDF</string>
<string name="libraries"></string> <string name="libraries"></string>
<string name="libraries_intro">本应用使用的开源库:</string> <string name="libraries_intro">本应用使用的开源库:</string>
<string name="libraries_open_source">开源库</string> <string name="libraries_open_source">开源库</string>

View File

@@ -28,7 +28,7 @@
<string name="file_size">File size: %1$s</string> <string name="file_size">File size: %1$s</string>
<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="last_saved_pdf_files">Recent PDFs saved in Downloads:</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_intro">This application uses several open-source libraries, including:</string> <string name="libraries_intro">This application uses several open-source libraries, including:</string>
<string name="libraries_open_source">Open-source libraries</string> <string name="libraries_open_source">Open-source libraries</string>