Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/src/main/java/org/mydomain/myscan/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class MainActivity : ComponentActivity() {
setContent {
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
val liveAnalysisState by viewModel.liveAnalysisState.collectAsStateWithLifecycle()
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
MyScanTheme {
val navigation = Navigation(
toCameraScreen = { viewModel.navigateTo(Screen.Camera) },
Expand All @@ -78,9 +78,8 @@ class MainActivity : ComponentActivity() {
}
is Screen.Document -> {
DocumentScreen (
pageIds,
document = document,
initialPage = screen.initialPage,
imageLoader = { id -> viewModel.getBitmap(id) },
navigation = navigation,
pdfActions = PdfGenerationActions(
startGeneration = viewModel::startPdfGeneration,
Expand Down
15 changes: 13 additions & 2 deletions app/src/main/java/org/mydomain/myscan/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mydomain.myscan.ui.PdfGenerationUiState
import org.mydomain.myscan.view.DocumentUiModel
import java.io.ByteArrayOutputStream
import java.io.File

Expand Down Expand Up @@ -71,8 +72,18 @@ class MainViewModel(
val currentScreen: StateFlow<Screen> = _screenStack.map { it.last() }
.stateIn(viewModelScope, SharingStarted.Eagerly, Screen.Camera)

private val _pageIds = MutableStateFlow<List<String>>(imageRepository.imageIds())
val pageIds: StateFlow<List<String>> = _pageIds
private val _pageIds = MutableStateFlow(imageRepository.imageIds())
val documentUiModel: StateFlow<DocumentUiModel> =
_pageIds.map { ids ->
DocumentUiModel(
pageIds = ids,
imageLoader = ::getBitmap
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = DocumentUiModel(emptyList(), ::getBitmap)
)

private val _captureState = MutableStateFlow<CaptureState>(CaptureState.Idle)
val captureState: StateFlow<CaptureState> = _captureState
Expand Down
26 changes: 13 additions & 13 deletions app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fun CameraScreen(
onFinalizePressed: () -> Unit,
) {
var previewView by remember { mutableStateOf<PreviewView?>(null) }
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
var isDebugMode by remember { mutableStateOf(false) }

Expand Down Expand Up @@ -129,9 +129,9 @@ fun CameraScreen(
}

val listState = rememberLazyListState()
LaunchedEffect(pageIds.size) {
if (pageIds.isNotEmpty()) {
listState.animateScrollToItem(pageIds.lastIndex)
LaunchedEffect(document.pageCount()) {
if (!document.isEmpty()) {
listState.animateScrollToItem(document.lastIndex())
}
}
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
Expand All @@ -145,14 +145,13 @@ fun CameraScreen(
},
pageListState =
CommonPageListState(
pageIds = pageIds,
imageLoader = { id -> viewModel.getBitmap(id) },
document = document,
onPageClick = { index -> viewModel.navigateTo(Screen.Document(index)) },
listState = listState,
onLastItemPosition = { offset -> thumbnailCoords.value = offset },
),
cameraUiState = CameraUiState(
pageIds.size,
document.pageCount(),
liveAnalysisState,
captureState,
showDetectionError,
Expand Down Expand Up @@ -457,12 +456,13 @@ private fun ScreenPreview(captureState: CaptureState, rotationDegrees: Float = 0
},
pageListState =
CommonPageListState(
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
imageLoader = { id ->
context.assets.open(id).use { input ->
BitmapFactory.decodeStream(input)
}
},
document = DocumentUiModel(
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
imageLoader = { id ->
context.assets.open(id).use { input ->
BitmapFactory.decodeStream(input)
}
}),
onPageClick = {},
listState = LazyListState(),
),
Expand Down
36 changes: 17 additions & 19 deletions app/src/main/java/org/mydomain/myscan/view/DocumentScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/
package org.mydomain.myscan.view

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
Expand Down Expand Up @@ -68,9 +67,8 @@ import org.mydomain.myscan.ui.theme.MyScanTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DocumentScreen(
pageIds: List<String>,
document: DocumentUiModel,
initialPage: Int,
imageLoader: (String) -> Bitmap?,
navigation: Navigation,
pdfActions: PdfGenerationActions,
onStartNew: () -> Unit,
Expand All @@ -80,8 +78,8 @@ fun DocumentScreen(
val showNewDocDialog = rememberSaveable { mutableStateOf(false) }
val showPdfDialog = rememberSaveable { mutableStateOf(false) }
val currentPageIndex = rememberSaveable { mutableIntStateOf(initialPage) }
if (currentPageIndex.intValue >= pageIds.size) {
currentPageIndex.intValue = pageIds.size - 1
if (currentPageIndex.intValue >= document.pageCount()) {
currentPageIndex.intValue = document.pageCount() - 1
}
if (currentPageIndex.intValue < 0) {
navigation.toCameraScreen()
Expand All @@ -97,8 +95,7 @@ fun DocumentScreen(
MyScaffold(
toAboutScreen = navigation.toAboutScreen,
pageListState = CommonPageListState(
pageIds,
imageLoader,
document,
onPageClick = { index -> currentPageIndex.intValue = index },
currentPageIndex = currentPageIndex.intValue,
listState = listState,
Expand All @@ -115,7 +112,7 @@ fun DocumentScreen(
)
},
) { modifier ->
DocumentPreview(pageIds, imageLoader, currentPageIndex, onDeleteImage, modifier)
DocumentPreview(document, currentPageIndex, onDeleteImage, modifier)
if (showNewDocDialog.value) {
NewDocumentDialog(onConfirm = onStartNew, showNewDocDialog)
}
Expand All @@ -130,21 +127,20 @@ fun DocumentScreen(

@Composable
private fun DocumentPreview(
pageIds: List<String>,
imageLoader: (String) -> Bitmap?,
document: DocumentUiModel,
currentPageIndex: MutableIntState,
onDeleteImage: (String) -> Unit,
modifier: Modifier,
) {
val imageId = pageIds[currentPageIndex.intValue]
val imageId = document.pageId(currentPageIndex.intValue)
Column (
modifier = modifier
.background(MaterialTheme.colorScheme.surfaceContainerLow)
) {
Box (
modifier = Modifier.fillMaxSize()
) {
val bitmap = imageLoader(imageId)
val bitmap = document.load(currentPageIndex.intValue)
if (bitmap != null) {
val imageBitmap = bitmap.asImageBitmap()
val zoomState = rememberZoomState(
Expand Down Expand Up @@ -175,7 +171,7 @@ private fun DocumentPreview(
.align(Alignment.BottomEnd)
.padding(8.dp)
)
Text("${currentPageIndex.intValue + 1} / ${pageIds.size}",
Text("${currentPageIndex.intValue + 1} / ${document.pageCount()}",
color = MaterialTheme.colorScheme.inverseOnSurface,
modifier = Modifier
.align(Alignment.BottomStart)
Expand Down Expand Up @@ -243,13 +239,15 @@ fun DocumentScreenPreview() {
val context = LocalContext.current
MyScanTheme {
DocumentScreen(
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
initialPage = 1,
imageLoader = { id ->
context.assets.open(id).use { input ->
BitmapFactory.decodeStream(input)
DocumentUiModel(
listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
{ id ->
context.assets.open(id).use { input ->
BitmapFactory.decodeStream(input)
}
}
},
),
initialPage = 1,
navigation = Navigation(
{}, {}, {}, {}, {}),
pdfActions = PdfGenerationActions(
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/java/org/mydomain/myscan/view/DocumentUiModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.mydomain.myscan.view

import android.graphics.Bitmap

data class DocumentUiModel(
private val pageIds: List<String>,
private val imageLoader: (String) -> Bitmap?
) {
fun pageCount(): Int {
return pageIds.size
}
fun pageId(index: Int): String {
return pageIds[index]
}
fun isEmpty(): Boolean {
return pageIds.isEmpty()
}
fun lastIndex(): Int {
return pageIds.lastIndex
}
fun load(index: Int): Bitmap? {
return imageLoader(pageIds[index])
}
}
44 changes: 19 additions & 25 deletions app/src/main/java/org/mydomain/myscan/view/PageList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -51,8 +51,7 @@ import androidx.compose.ui.unit.sp
const val PAGE_LIST_ELEMENT_SIZE_DP = 120

data class CommonPageListState(
val pageIds: List<String>,
val imageLoader: (String) -> Bitmap?,
val document: DocumentUiModel,
val onPageClick: (Int) -> Unit,
val listState: LazyListState,
val currentPageIndex: Int? = null,
Expand All @@ -65,37 +64,32 @@ fun CommonPageList(
modifier: Modifier = Modifier,
) {
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
val content: LazyListScope.() -> Unit = {
items(state.document.pageCount()) { index ->
// TODO Use small images rather than big ones
val image = state.document.load(index)
if (image != null) {
PageThumbnail(image, index, state)
}
}
}
if (isLandscape) {
LazyColumn (
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
itemsIndexed(state.pageIds) { index, id ->
// TODO Use small images rather than big ones
val image = state.imageLoader(id)
if (image != null) {
PageThumbnail(image, index, state)
}
}
}
modifier = modifier,
content = content,
)
} else {
LazyRow (
state = state.listState,
contentPadding = PaddingValues(4.dp),
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceContainer),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
itemsIndexed(state.pageIds) { index, id ->
// TODO Use small images rather than big ones
val image = state.imageLoader(id)
if (image != null) {
PageThumbnail(image, index, state)
}
}
}
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
if (state.pageIds.isEmpty()) {
if (state.document.isEmpty()) {
Box(
modifier = Modifier
.height(120.dp)
Expand All @@ -120,7 +114,7 @@ private fun PageThumbnail(
Modifier.height(maxImageSize)
else
Modifier.width(maxImageSize)
if (index == state.pageIds.lastIndex) {
if (index == state.document.lastIndex()) {
val density = LocalDensity.current
modifier = modifier.addPositionCallback(state.onLastItemPosition, density, 1.0f)
}
Expand Down