From d4a3c78c237afbee20398a0ed80ef66dcd5a5625 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:27:18 +0100 Subject: [PATCH] Split MainViewModel: extract HomeViewModel --- .../java/org/fairscan/app/MainActivity.kt | 12 +-- .../java/org/fairscan/app/MainViewModel.kt | 47 +---------- .../app/ui/screens/{ => home}/HomeScreen.kt | 2 +- .../app/ui/screens/home/HomeViewModel.kt | 80 +++++++++++++++++++ 4 files changed, 90 insertions(+), 51 deletions(-) rename app/src/main/java/org/fairscan/app/ui/screens/{ => home}/HomeScreen.kt (99%) create mode 100644 app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 45fdc66..06c0d33 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -53,7 +53,7 @@ import org.fairscan.app.ui.Screen import org.fairscan.app.ui.components.rememberCameraPermissionState import org.fairscan.app.ui.screens.AboutScreen import org.fairscan.app.ui.screens.DocumentScreen -import org.fairscan.app.ui.screens.HomeScreen +import org.fairscan.app.ui.screens.home.HomeScreen import org.fairscan.app.ui.screens.LibrariesScreen import org.fairscan.app.ui.screens.camera.CameraEvent import org.fairscan.app.ui.screens.camera.CameraScreen @@ -61,6 +61,7 @@ import org.fairscan.app.ui.screens.camera.CameraViewModel import org.fairscan.app.ui.screens.export.ExportScreenWrapper import org.fairscan.app.ui.screens.export.ExportViewModel import org.fairscan.app.ui.screens.export.PdfGenerationActions +import org.fairscan.app.ui.screens.home.HomeViewModel import org.fairscan.app.ui.theme.FairScanTheme import org.opencv.android.OpenCVLoader @@ -72,6 +73,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) initLibraries() val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) } + val homeViewModel: HomeViewModel by viewModels { HomeViewModel.getFactory(this) } val cameraViewModel: CameraViewModel by viewModels { CameraViewModel.getFactory(this) } val exportViewModel: ExportViewModel by viewModels { ExportViewModel.getFactory(this) } lifecycleScope.launch(Dispatchers.IO) { @@ -91,7 +93,7 @@ class MainActivity : ComponentActivity() { val liveAnalysisState by cameraViewModel.liveAnalysisState.collectAsStateWithLifecycle() val document by viewModel.documentUiModel.collectAsStateWithLifecycle() val cameraPermission = rememberCameraPermissionState() - val savePdf = { savePdf(exportViewModel.getFinalPdf(), viewModel, exportViewModel) } + val savePdf = { savePdf(exportViewModel.getFinalPdf(), homeViewModel, exportViewModel) } val storagePermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted -> @@ -114,7 +116,7 @@ class MainActivity : ComponentActivity() { ) when (val screen = currentScreen) { is Screen.Main.Home -> { - val recentDocs by viewModel.recentDocuments.collectAsStateWithLifecycle() + val recentDocs by homeViewModel.recentDocuments.collectAsStateWithLifecycle() HomeScreen( cameraPermission = cameraPermission, currentDocument = document, @@ -210,7 +212,7 @@ class MainActivity : ComponentActivity() { private fun savePdf( generatedPdf: GeneratedPdf?, - viewModel: MainViewModel, + homeViewModel: HomeViewModel, exportViewModel: ExportViewModel ) { if (generatedPdf == null) @@ -220,7 +222,7 @@ class MainActivity : ComponentActivity() { appScope.launch { try { val targetFile = exportViewModel.saveFile(generatedPdf.file) - viewModel.addRecentDocument(targetFile.absolutePath, generatedPdf.pageCount) + homeViewModel.addRecentDocument(targetFile.absolutePath, generatedPdf.pageCount) suspendCancellableCoroutine { continuation -> MediaScannerConnection.scanFile( diff --git a/app/src/main/java/org/fairscan/app/MainViewModel.kt b/app/src/main/java/org/fairscan/app/MainViewModel.kt index d7f9bd1..400f9ed 100644 --- a/app/src/main/java/org/fairscan/app/MainViewModel.kt +++ b/app/src/main/java/org/fairscan/app/MainViewModel.kt @@ -17,7 +17,6 @@ package org.fairscan.app import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -31,16 +30,12 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.fairscan.app.data.ImageRepository -import org.fairscan.app.data.recentDocumentsDataStore import org.fairscan.app.ui.NavigationState import org.fairscan.app.ui.Screen import org.fairscan.app.ui.state.DocumentUiModel -import org.fairscan.app.ui.state.RecentDocumentUiState -import java.io.File class MainViewModel( - private val imageRepository: ImageRepository, - private val recentDocumentsDataStore: DataStore, + private val imageRepository: ImageRepository ): ViewModel() { companion object { @@ -48,10 +43,7 @@ class MainViewModel( @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class, extras: CreationExtras): T { val app = context.applicationContext as FairScanApp - return MainViewModel( - app.appContainer.imageRepository, - context.recentDocumentsDataStore, - ) as T + return MainViewModel(app.appContainer.imageRepository) as T } } } @@ -116,41 +108,6 @@ class MainViewModel( return bytes?.let { BitmapFactory.decodeByteArray(it, 0, it.size) } } - 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() } - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = emptyList(), - ) - fun addRecentDocument(filePath: String, pageCount: Int) { - viewModelScope.launch { - recentDocumentsDataStore.updateData { current -> - val newDoc = RecentDocument.newBuilder() - .setFilePath(filePath) - .setPageCount(pageCount) - .setCreatedAt(System.currentTimeMillis()) - .build() - current.toBuilder() - .addDocuments(0, newDoc) - .also { builder -> - while (builder.documentsCount > 3) { - builder.removeDocuments(builder.documentsCount - 1) - } - } - .build() - } - } - } - fun handleImageCaptured(jpegBytes: ByteArray) { imageRepository.add(jpegBytes) _pageIds.value = imageRepository.imageIds() diff --git a/app/src/main/java/org/fairscan/app/ui/screens/HomeScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt similarity index 99% rename from app/src/main/java/org/fairscan/app/ui/screens/HomeScreen.kt rename to app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt index b2e1801..57d1401 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/HomeScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeScreen.kt @@ -12,7 +12,7 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -package org.fairscan.app.ui.screens +package org.fairscan.app.ui.screens.home import androidx.compose.foundation.Image import androidx.compose.foundation.clickable 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 new file mode 100644 index 0000000..ab03980 --- /dev/null +++ b/app/src/main/java/org/fairscan/app/ui/screens/home/HomeViewModel.kt @@ -0,0 +1,80 @@ +/* + * 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.ui.screens.home + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import org.fairscan.app.RecentDocument +import org.fairscan.app.RecentDocuments +import org.fairscan.app.data.recentDocumentsDataStore +import org.fairscan.app.ui.state.RecentDocumentUiState +import java.io.File + +class HomeViewModel(private val recentDocumentsDataStore: DataStore): ViewModel() { + + companion object { + fun getFactory(context: Context) = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class, extras: CreationExtras): T { + return HomeViewModel(context.recentDocumentsDataStore) as T + } + } + } + + 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() } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList(), + ) + fun addRecentDocument(filePath: String, pageCount: Int) { + viewModelScope.launch { + recentDocumentsDataStore.updateData { current -> + val newDoc = RecentDocument.newBuilder() + .setFilePath(filePath) + .setPageCount(pageCount) + .setCreatedAt(System.currentTimeMillis()) + .build() + current.toBuilder() + .addDocuments(0, newDoc) + .also { builder -> + while (builder.documentsCount > 3) { + builder.removeDocuments(builder.documentsCount - 1) + } + } + .build() + } + } + } + +}