MainActivity shouldn't do the job of a ViewModel
This commit is contained in:
@@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Build.VERSION_CODES.Q
|
||||
@@ -42,25 +41,23 @@ import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.ui.Navigation
|
||||
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.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
|
||||
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
||||
import org.fairscan.app.ui.screens.export.ExportEvent
|
||||
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.HomeScreen
|
||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
import org.opencv.android.OpenCVLoader
|
||||
@@ -93,17 +90,34 @@ class MainActivity : ComponentActivity() {
|
||||
val liveAnalysisState by cameraViewModel.liveAnalysisState.collectAsStateWithLifecycle()
|
||||
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
|
||||
val cameraPermission = rememberCameraPermissionState()
|
||||
val savePdf = { savePdf(exportViewModel.getFinalPdf(), homeViewModel, exportViewModel) }
|
||||
val storagePermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
savePdf()
|
||||
exportViewModel.onSavePdfClicked()
|
||||
} else {
|
||||
val message = getString(R.string.storage_permission_denied)
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
exportViewModel.events.collect { event ->
|
||||
when (event) {
|
||||
ExportEvent.RequestSavePdf -> {
|
||||
checkPermissionThen(storagePermissionLauncher) {
|
||||
exportViewModel.onRequestPdfSave(context, homeViewModel)
|
||||
}
|
||||
}
|
||||
is ExportEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
ExportEvent.PdfSaved -> {
|
||||
Toast.makeText(context, "PDF saved", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FairScanTheme {
|
||||
val navigation = Navigation(
|
||||
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
|
||||
@@ -155,7 +169,7 @@ class MainActivity : ComponentActivity() {
|
||||
setFilename = exportViewModel::setFilename,
|
||||
uiStateFlow = exportViewModel.pdfUiState,
|
||||
sharePdf = { sharePdf(exportViewModel.getFinalPdf(), exportViewModel) },
|
||||
savePdf = { checkPermissionThen(storagePermissionLauncher, savePdf) },
|
||||
savePdf = { exportViewModel.onSavePdfClicked() },
|
||||
openPdf = { openPdf(exportViewModel.pdfUiState.value.savedFileUri) }
|
||||
),
|
||||
onCloseScan = {
|
||||
@@ -210,37 +224,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePdf(
|
||||
generatedPdf: GeneratedPdf?,
|
||||
homeViewModel: HomeViewModel,
|
||||
exportViewModel: ExportViewModel
|
||||
) {
|
||||
if (generatedPdf == null)
|
||||
return
|
||||
val appScope = CoroutineScope(Dispatchers.IO)
|
||||
val context = this
|
||||
appScope.launch {
|
||||
try {
|
||||
val targetFile = exportViewModel.saveFile(generatedPdf.file)
|
||||
homeViewModel.addRecentDocument(targetFile.absolutePath, generatedPdf.pageCount)
|
||||
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(targetFile.absolutePath),
|
||||
arrayOf(PDF_MIME_TYPE)
|
||||
) { _, _ -> continuation.resume(Unit) {} }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("FairScan", "Failed to save PDF", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.error_save), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPdf(fileUri: Uri?) {
|
||||
if (fileUri == null) return
|
||||
val uri = FileProvider.getUriForFile(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package org.fairscan.app.ui.screens.export
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaScannerConnection
|
||||
import android.util.Log
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -22,20 +23,33 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fairscan.app.FairScanApp
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.data.ImageRepository
|
||||
import org.fairscan.app.data.PdfFileManager
|
||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
||||
import org.fairscan.app.ui.state.PdfGenerationUiState
|
||||
import java.io.File
|
||||
|
||||
private const val PDF_MIME_TYPE = "application/pdf"
|
||||
|
||||
sealed interface ExportEvent {
|
||||
data object RequestSavePdf : ExportEvent
|
||||
data class ShowToast(val message: String) : ExportEvent
|
||||
data object PdfSaved : ExportEvent
|
||||
}
|
||||
|
||||
class ExportViewModel(
|
||||
private val pdfFileManager: PdfFileManager,
|
||||
private val imageRepository: ImageRepository,
|
||||
@@ -53,6 +67,9 @@ class ExportViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private val _events = MutableSharedFlow<ExportEvent>()
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
private suspend fun generatePdf(): GeneratedPdf = withContext(Dispatchers.IO) {
|
||||
val imageIds = imageRepository.imageIds()
|
||||
val jpegs = imageIds.asSequence()
|
||||
@@ -127,6 +144,49 @@ class ExportViewModel(
|
||||
return copiedFile
|
||||
}
|
||||
|
||||
fun onSavePdfClicked() {
|
||||
viewModelScope.launch {
|
||||
_events.emit(ExportEvent.RequestSavePdf)
|
||||
}
|
||||
}
|
||||
|
||||
fun onRequestPdfSave(context: Context, homeViewModel: HomeViewModel) {
|
||||
viewModelScope.launch {
|
||||
performPdfSave(context, homeViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performPdfSave(context: Context, homeViewModel: HomeViewModel) {
|
||||
try {
|
||||
val pdf = getFinalPdf() ?: return
|
||||
val targetFile = saveFile(pdf.file)
|
||||
|
||||
mediaScan(context, targetFile)
|
||||
|
||||
// TODO remove that call: that should be handled through the ExportEvent
|
||||
homeViewModel.addRecentDocument(
|
||||
targetFile.absolutePath,
|
||||
pdf.pageCount
|
||||
)
|
||||
|
||||
_events.emit(ExportEvent.PdfSaved)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("FairScan", "Failed to save PDF", e)
|
||||
_events.emit(ExportEvent.ShowToast("Error while saving PDF"))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun mediaScan(context: Context, file: File) =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(file.absolutePath),
|
||||
arrayOf(PDF_MIME_TYPE)
|
||||
) { _, _ -> continuation.resume(Unit) {} }
|
||||
}
|
||||
|
||||
fun cleanUpOldPdfs(thresholdInMillis: Int) {
|
||||
pdfFileManager.cleanUpOldFiles(thresholdInMillis)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user