Split MainViewModel: extract HomeViewModel

This commit is contained in:
Pierre-Yves Nicolas
2025-11-21 16:27:18 +01:00
committed by pynicolas
parent 7c53dcface
commit d4a3c78c23
4 changed files with 90 additions and 51 deletions

View File

@@ -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(

View File

@@ -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<RecentDocuments>,
private val imageRepository: ImageRepository
): ViewModel() {
companion object {
@@ -48,10 +43,7 @@ class MainViewModel(
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, 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<List<RecentDocumentUiState>> =
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()

View File

@@ -12,7 +12,7 @@
* 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
package org.fairscan.app.ui.screens.home
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<RecentDocuments>): ViewModel() {
companion object {
fun getFactory(context: Context) = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return HomeViewModel(context.recentDocumentsDataStore) as T
}
}
}
val recentDocuments: StateFlow<List<RecentDocumentUiState>> =
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()
}
}
}
}