MainViewModel: avoid running IO operations on the main thread

This commit is contained in:
Pierre-Yves Nicolas
2026-03-24 13:35:46 +01:00
parent 40314def4e
commit fa619da867

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.fairscan.app.data.ImageRepository import org.fairscan.app.data.ImageRepository
import org.fairscan.app.domain.CapturedPage import org.fairscan.app.domain.CapturedPage
import org.fairscan.app.domain.PageViewKey import org.fairscan.app.domain.PageViewKey
@@ -37,9 +39,13 @@ import org.fairscan.imageprocessing.encodeJpeg
import org.opencv.android.Utils import org.opencv.android.Utils
import org.opencv.core.Mat import org.opencv.core.Mat
import org.opencv.imgproc.Imgproc import org.opencv.imgproc.Imgproc
import java.util.concurrent.Executors
class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode): ViewModel() { class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode): ViewModel() {
// TODO ImageRepository should be made thread-safe
private val repositoryDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val _navigationState = MutableStateFlow(NavigationState.initial(launchMode)) private val _navigationState = MutableStateFlow(NavigationState.initial(launchMode))
val currentScreen: StateFlow<Screen> = _navigationState.map { it.current } val currentScreen: StateFlow<Screen> = _navigationState.map { it.current }
.stateIn(viewModelScope, SharingStarted.Eagerly, _navigationState.value.current) .stateIn(viewModelScope, SharingStarted.Eagerly, _navigationState.value.current)
@@ -70,31 +76,42 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
fun rotateImage(id: String, clockwise: Boolean) { fun rotateImage(id: String, clockwise: Boolean) {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(repositoryDispatcher) {
imageRepository.rotate(id, clockwise) imageRepository.rotate(id, clockwise)
_pages.value = imageRepository.pages() imageRepository.pages()
}
_pages.value = pages
} }
} }
fun movePage(id: String, newIndex: Int) { fun movePage(id: String, newIndex: Int) {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(repositoryDispatcher) {
imageRepository.movePage(id, newIndex) imageRepository.movePage(id, newIndex)
_pages.value = imageRepository.pages() imageRepository.pages()
}
_pages.value = pages
} }
} }
fun deletePage(id: String) { fun deletePage(id: String) {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(repositoryDispatcher) {
imageRepository.delete(id) imageRepository.delete(id)
_pages.value = imageRepository.pages() imageRepository.pages()
}
_pages.value = pages
} }
} }
fun startNewDocument() { fun startNewDocument() {
_pages.value = persistentListOf() _pages.value = persistentListOf()
viewModelScope.launch { viewModelScope.launch {
withContext(repositoryDispatcher) {
imageRepository.clear() imageRepository.clear()
} }
} }
}
fun getBitmap(key: PageViewKey): Bitmap? { fun getBitmap(key: PageViewKey): Bitmap? {
val bytes = imageRepository.jpegBytes(key) val bytes = imageRepository.jpegBytes(key)
@@ -108,12 +125,15 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
fun handleImageCaptured(capturedPage: CapturedPage) { fun handleImageCaptured(capturedPage: CapturedPage) {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(repositoryDispatcher) {
imageRepository.add( imageRepository.add(
capturedPage.pageJpeg, capturedPage.pageJpeg,
compressJpeg(capturedPage.source, 90), compressJpeg(capturedPage.source, 90),
capturedPage.metadata, capturedPage.metadata,
) )
_pages.value = imageRepository.pages() imageRepository.pages()
}
_pages.value = pages
} }
} }