Avoid resolution of current page in DocumentScreen

This commit is contained in:
Pierre-Yves Nicolas
2026-04-01 10:17:57 +02:00
parent b258082ce1
commit 215f57bb74
5 changed files with 49 additions and 45 deletions

View File

@@ -62,7 +62,6 @@ import org.fairscan.app.ui.screens.about.createEmailWithImageIntent
import org.fairscan.app.ui.screens.camera.CameraEvent import org.fairscan.app.ui.screens.camera.CameraEvent
import org.fairscan.app.ui.screens.camera.CameraScreen import org.fairscan.app.ui.screens.camera.CameraScreen
import org.fairscan.app.ui.screens.camera.CameraViewModel import org.fairscan.app.ui.screens.camera.CameraViewModel
import org.fairscan.app.ui.screens.document.DocumentUiState
import org.fairscan.app.ui.screens.export.ExportActions import org.fairscan.app.ui.screens.export.ExportActions
import org.fairscan.app.ui.screens.export.ExportEvent import org.fairscan.app.ui.screens.export.ExportEvent
import org.fairscan.app.ui.screens.export.ExportResult import org.fairscan.app.ui.screens.export.ExportResult
@@ -182,9 +181,9 @@ class MainActivity : ComponentActivity() {
uiState = documentUiState, uiState = documentUiState,
navigation = navigation, navigation = navigation,
onExportClick = onExportClick, onExportClick = onExportClick,
onDeleteImage = { id -> viewModel.deletePage(id) }, onDeleteImage = { viewModel.deleteCurrentPage() },
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }, onRotateImage = { clockwise -> viewModel.rotateCurrentPage(clockwise) },
onToggleColorMode = { id -> viewModel.togglePageColorMode(id) }, onToggleColorMode = { viewModel.toggleCurrentPageColorMode() },
onPageReorder = { id, newIndex -> viewModel.movePage(id, newIndex) }, onPageReorder = { id, newIndex -> viewModel.movePage(id, newIndex) },
onPageSelected = viewModel::onPageSelected onPageSelected = viewModel::onPageSelected
) )

View File

@@ -14,7 +14,6 @@
*/ */
package org.fairscan.app package org.fairscan.app
import android.graphics.Bitmap
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -43,6 +42,7 @@ import org.fairscan.app.ui.screens.document.DocumentUiState
import org.fairscan.app.ui.state.DocumentUiModel import org.fairscan.app.ui.state.DocumentUiModel
import org.fairscan.app.ui.state.PageThumbnail import org.fairscan.app.ui.state.PageThumbnail
import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.ColorMode
import java.lang.IllegalStateException
import kotlin.math.min import kotlin.math.min
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -77,15 +77,16 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
private val _currentPageIndex = MutableStateFlow(0) private val _currentPageIndex = MutableStateFlow(0)
val currentPageUiState: Flow<CurrentPageUiState> = private val currentPageUiState: Flow<CurrentPageUiState?> =
combine(_currentPageIndex, _pages) { index, pages -> pages.getOrNull(index) } combine(_currentPageIndex, _pages) { index, pages -> pages.getOrNull(index) }
.mapLatest { page -> .mapLatest { page ->
page?.let { page?.let {
CurrentPageUiState( CurrentPageUiState(
page.id,
imageRepository.jpegBytes(it.key())?.toBitmap(), imageRepository.jpegBytes(it.key())?.toBitmap(),
page.colorMode page.colorMode
) )
} ?: CurrentPageUiState() }
} }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
@@ -95,7 +96,7 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
} }
.stateIn( .stateIn(
viewModelScope, SharingStarted.Eagerly, viewModelScope, SharingStarted.Eagerly,
DocumentUiState(0, CurrentPageUiState(), DocumentUiModel()) DocumentUiState(0, null, DocumentUiModel())
) )
fun onPageSelected(index: Int) { fun onPageSelected(index: Int) {
@@ -116,10 +117,10 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
_navigationState.update { stack -> stack.navigateBack() } _navigationState.update { stack -> stack.navigateBack() }
} }
fun rotateImage(id: String, clockwise: Boolean) { fun rotateCurrentPage(clockwise: Boolean) {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(Dispatchers.IO) { val pages = withContext(Dispatchers.IO) {
imageRepository.rotate(id, clockwise) imageRepository.rotate(currentPage().id, clockwise)
imageRepository.pages() imageRepository.pages()
} }
_pages.value = pages _pages.value = pages
@@ -136,10 +137,10 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
} }
} }
fun deletePage(id: String) { fun deleteCurrentPage() {
viewModelScope.launch { viewModelScope.launch {
val pages = withContext(Dispatchers.IO) { val pages = withContext(Dispatchers.IO) {
imageRepository.delete(id) imageRepository.delete(currentPage().id)
imageRepository.pages() imageRepository.pages()
} }
@@ -153,14 +154,14 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
} }
} }
fun togglePageColorMode(id: String) { fun toggleCurrentPageColorMode() {
viewModelScope.launch { viewModelScope.launch {
val currentColorMode = _pages.value.find { p -> p.id == id }?.colorMode val currentPage = currentPage()
currentColorMode?.let { currentPage.colorMode?.let {
val newColorMode = val newColorMode =
if (it == ColorMode.COLOR) ColorMode.GRAYSCALE else ColorMode.COLOR if (it == ColorMode.COLOR) ColorMode.GRAYSCALE else ColorMode.COLOR
val pages = withContext(Dispatchers.IO) { val pages = withContext(Dispatchers.IO) {
imageRepository.setColorMode(id, newColorMode) imageRepository.setColorMode(currentPage.id, newColorMode)
imageRepository.pages() imageRepository.pages()
} }
_pages.value = pages _pages.value = pages
@@ -168,6 +169,13 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
} }
} }
private fun currentPage(): ScanPage {
val index = _currentPageIndex.value
val pages = _pages.value
return pages.getOrNull(index) ?: throw IllegalStateException(
"No current page for index $index (${pages.size} pages)")
}
fun startNewDocument() { fun startNewDocument() {
_pages.value = persistentListOf() _pages.value = persistentListOf()
viewModelScope.launch { viewModelScope.launch {

View File

@@ -86,9 +86,9 @@ fun DocumentScreen(
uiState: DocumentUiState, uiState: DocumentUiState,
navigation: Navigation, navigation: Navigation,
onExportClick: () -> Unit, onExportClick: () -> Unit,
onDeleteImage: (String) -> Unit, onDeleteImage: () -> Unit,
onRotateImage: (String, Boolean) -> Unit, onRotateImage: (Boolean) -> Unit,
onToggleColorMode: (String) -> Unit, onToggleColorMode: () -> Unit,
onPageReorder: (String, Int) -> Unit, onPageReorder: (String, Int) -> Unit,
onPageSelected: (Int) -> Unit, onPageSelected: (Int) -> Unit,
) { ) {
@@ -130,7 +130,7 @@ fun DocumentScreen(
title = stringResource(R.string.delete_page), title = stringResource(R.string.delete_page),
message = stringResource(R.string.delete_page_warning), message = stringResource(R.string.delete_page_warning),
showDialog = showDeletePageDialog showDialog = showDeletePageDialog
) { onDeleteImage(document.pageId(currentPageIndex)) } ) { onDeleteImage() }
} }
} }
} }
@@ -138,14 +138,13 @@ fun DocumentScreen(
@Composable @Composable
private fun DocumentPreview( private fun DocumentPreview(
uiState: DocumentUiState, uiState: DocumentUiState,
onDeleteImage: (String) -> Unit, onDeleteImage: () -> Unit,
onRotateImage: (String, Boolean) -> Unit, onRotateImage: (Boolean) -> Unit,
onToggleColorMode: (String) -> Unit, onToggleColorMode: () -> Unit,
modifier: Modifier, modifier: Modifier,
) { ) {
val currentPageIndex = uiState.currentPageIndex val currentPageIndex = uiState.currentPageIndex
val document = uiState.document val document = uiState.document
val imageId = document.pageId(currentPageIndex)
Column ( Column (
modifier = modifier modifier = modifier
.background(MaterialTheme.colorScheme.surfaceContainerLow) .background(MaterialTheme.colorScheme.surfaceContainerLow)
@@ -153,10 +152,11 @@ private fun DocumentPreview(
Box ( Box (
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
val bitmap = uiState.currentPage.bitmap val bitmap = uiState.currentPage?.bitmap
if (bitmap != null) { val pageId = uiState.currentPage?.id
if (bitmap != null && pageId != null) {
val imageBitmap = bitmap.asImageBitmap() val imageBitmap = bitmap.asImageBitmap()
val zoomState = remember(imageId) { val zoomState = remember(pageId) {
ZoomState( ZoomState(
contentSize = Size(bitmap.width.toFloat(), bitmap.height.toFloat()) contentSize = Size(bitmap.width.toFloat(), bitmap.height.toFloat())
) )
@@ -175,20 +175,20 @@ private fun DocumentPreview(
) )
} }
} }
uiState.currentPage.colorMode?.let { uiState.currentPage?.colorMode?.let {
ColorModeButton( ColorModeButton(
currentColorMode = it, currentColorMode = it,
onToggle = { onToggleColorMode(imageId) }, onToggle = { onToggleColorMode() },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
.padding(8.dp) .padding(8.dp)
) )
} }
RotationButtons(imageId, onRotateImage, Modifier.align(Alignment.BottomCenter)) RotationButtons(onRotateImage, Modifier.align(Alignment.BottomCenter))
SecondaryActionButton( SecondaryActionButton(
Icons.Outlined.Delete, Icons.Outlined.Delete,
contentDescription = stringResource(R.string.delete_page), contentDescription = stringResource(R.string.delete_page),
onClick = { onDeleteImage(imageId) }, onClick = { onDeleteImage() },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.padding(8.dp) .padding(8.dp)
@@ -210,8 +210,7 @@ private fun DocumentPreview(
@Composable @Composable
fun RotationButtons( fun RotationButtons(
imageId: String, onRotateImage: (Boolean) -> Unit,
onRotateImage: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
// RotateLeft on the left, RotateRight on the right: for both LTR and RTL languages // RotateLeft on the left, RotateRight on the right: for both LTR and RTL languages
@@ -222,14 +221,14 @@ fun RotationButtons(
SecondaryActionButton( SecondaryActionButton(
icon = Icons.Default.RotateLeft, icon = Icons.Default.RotateLeft,
contentDescription = stringResource(R.string.rotate_left), contentDescription = stringResource(R.string.rotate_left),
onClick = { onRotateImage(imageId, false) } onClick = { onRotateImage(false) }
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
SecondaryActionButton( SecondaryActionButton(
icon = Icons.Default.RotateRight, icon = Icons.Default.RotateRight,
contentDescription = stringResource(R.string.rotate_right), contentDescription = stringResource(R.string.rotate_right),
onClick = { onRotateImage(imageId, true) } onClick = { onRotateImage(true) }
) )
} }
} }
@@ -307,12 +306,12 @@ fun DocumentScreenPreview() {
LocalContext.current LocalContext.current
) )
DocumentScreen( DocumentScreen(
uiState = DocumentUiState(1, CurrentPageUiState(image, COLOR), document), uiState = DocumentUiState(1, CurrentPageUiState("123",image, COLOR), document),
navigation = dummyNavigation(), navigation = dummyNavigation(),
onExportClick = {}, onExportClick = {},
onDeleteImage = { _ -> }, onDeleteImage = { },
onRotateImage = { _,_ -> }, onRotateImage = { _ -> },
onToggleColorMode = { _ -> }, onToggleColorMode = { },
onPageReorder = { _,_ -> }, onPageReorder = { _,_ -> },
onPageSelected = { _ -> }, onPageSelected = { _ -> },
) )

View File

@@ -20,11 +20,12 @@ import org.fairscan.imageprocessing.ColorMode
data class DocumentUiState( data class DocumentUiState(
val currentPageIndex: Int, val currentPageIndex: Int,
val currentPage: CurrentPageUiState, val currentPage: CurrentPageUiState?,
val document: DocumentUiModel, val document: DocumentUiModel,
) )
data class CurrentPageUiState( data class CurrentPageUiState(
val bitmap: Bitmap? = null, val id: String,
val colorMode: ColorMode? = null, val bitmap: Bitmap?,
val colorMode: ColorMode?,
) )

View File

@@ -25,9 +25,6 @@ data class DocumentUiModel(
fun pageCount(): Int { fun pageCount(): Int {
return pages.size return pages.size
} }
fun pageId(index: Int): String {
return pages[index].key.pageId
}
fun isEmpty(): Boolean { fun isEmpty(): Boolean {
return pages.isEmpty() return pages.isEmpty()
} }