DocumentScreen: Landscape mode
This commit is contained in:
@@ -74,8 +74,8 @@ fun SecondaryActionButton(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackButton(onClick: () -> Unit) {
|
||||
IconButton(onClick = onClick) {
|
||||
fun BackButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
IconButton(onClick = onClick, modifier = modifier) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back)
|
||||
|
||||
@@ -29,29 +29,21 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Article
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -193,37 +185,31 @@ private fun CameraScreenScaffold(
|
||||
thumbnailCoords: MutableState<Offset>,
|
||||
toAboutScreen: () -> Unit,
|
||||
) {
|
||||
val documentBar : @Composable () -> Unit = {
|
||||
DocumentBar(
|
||||
pageListState = pageListState,
|
||||
pageCount = cameraUiState.pageCount,
|
||||
onFinalizePressed = onFinalizePressed,
|
||||
onDebugModeSwitched = onDebugModeSwitched,
|
||||
isLandscape = cameraUiState.isLandscape
|
||||
)
|
||||
}
|
||||
Box {
|
||||
if (!cameraUiState.isLandscape) {
|
||||
Scaffold(
|
||||
bottomBar = documentBar
|
||||
) { padding ->
|
||||
val modifier = Modifier.padding(bottom = padding.calculateBottomPadding()).fillMaxSize()
|
||||
CameraPreviewBox(cameraPreview, cameraUiState, onCapture, modifier)
|
||||
var tapCount by remember { mutableStateOf(0) }
|
||||
var lastTapTime by remember { mutableStateOf(0L) }
|
||||
val tapThreshold = 500L
|
||||
val onPageCountClick = {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastTapTime < tapThreshold) {
|
||||
tapCount++
|
||||
if (tapCount >= 3) {
|
||||
onDebugModeSwitched()
|
||||
tapCount = 0
|
||||
}
|
||||
} else {
|
||||
Scaffold { innerPadding ->
|
||||
Row(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
tapCount = 1
|
||||
}
|
||||
lastTapTime = currentTime
|
||||
}
|
||||
|
||||
Box {
|
||||
MyScaffold(
|
||||
toAboutScreen = toAboutScreen,
|
||||
pageListState = pageListState,
|
||||
bottomBar = { Bar(cameraUiState.pageCount, onPageCountClick, onFinalizePressed) }
|
||||
) {
|
||||
CameraPreviewBox(cameraPreview, cameraUiState, onCapture, Modifier)
|
||||
documentBar()
|
||||
modifier -> CameraPreviewBox(cameraPreview, cameraUiState, onCapture, modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
AboutScreenNavButton(
|
||||
onClick = toAboutScreen,
|
||||
modifier = Modifier.align(Alignment.TopEnd).windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
)
|
||||
if (cameraUiState.captureState is CaptureState.CapturePreview) {
|
||||
CapturedImage(cameraUiState.captureState.processed.asImageBitmap(), thumbnailCoords)
|
||||
}
|
||||
@@ -410,61 +396,6 @@ fun MessageBox(inferenceTime: Long) {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DocumentBar(
|
||||
pageListState: CommonPageListState,
|
||||
pageCount: Int,
|
||||
onFinalizePressed: () -> Unit,
|
||||
onDebugModeSwitched: () -> Unit,
|
||||
isLandscape: Boolean,
|
||||
) {
|
||||
var tapCount by remember { mutableStateOf(0) }
|
||||
var lastTapTime by remember { mutableStateOf(0L) }
|
||||
val tapThreshold = 500L
|
||||
val onPageCountClick = {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastTapTime < tapThreshold) {
|
||||
tapCount++
|
||||
if (tapCount >= 3) {
|
||||
onDebugModeSwitched()
|
||||
tapCount = 0
|
||||
}
|
||||
} else {
|
||||
tapCount = 1
|
||||
}
|
||||
lastTapTime = currentTime
|
||||
}
|
||||
|
||||
Column (
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
CommonPageList(pageListState, Modifier.weight(1f))
|
||||
BottomAppBar(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
if (isLandscape) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Bar(pageCount, onPageCountClick, onFinalizePressed)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 1.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Bar(pageCount, onPageCountClick, onFinalizePressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Bar(
|
||||
pageCount: Int,
|
||||
@@ -479,7 +410,7 @@ private fun Bar(
|
||||
MainActionButton(
|
||||
onClick = onFinalizePressed,
|
||||
enabled = pageCount > 0,
|
||||
text = "Document",
|
||||
text = stringResource(R.string.document),
|
||||
icon = Icons.AutoMirrored.Filled.Article,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -40,11 +39,8 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableIntState
|
||||
@@ -95,28 +91,29 @@ fun DocumentScreen(
|
||||
BackHandler {
|
||||
navigation.back()
|
||||
}
|
||||
Scaffold (
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
titleContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
|
||||
MyScaffold(
|
||||
toAboutScreen = navigation.toAboutScreen,
|
||||
pageListState = CommonPageListState(
|
||||
pageIds,
|
||||
imageLoader,
|
||||
onPageClick = { index -> currentPageIndex.intValue = index },
|
||||
currentPageIndex = currentPageIndex.intValue,
|
||||
listState = rememberLazyListState(),
|
||||
),
|
||||
title = { Text(stringResource(R.string.document)) },
|
||||
navigationIcon = { BackButton(navigation.back) },
|
||||
actions = {
|
||||
AboutScreenNavButton(onClick = navigation.toAboutScreen)
|
||||
}
|
||||
onBack = navigation.back,
|
||||
bottomBar = {
|
||||
BottomBar(showPdfDialog, showNewDocDialog)
|
||||
},
|
||||
pageListButton = {
|
||||
SecondaryActionButton(
|
||||
icon = Icons.Default.Add,
|
||||
onClick = navigation.toCameraScreen,
|
||||
contentDescription = stringResource(R.string.add_page),
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
Column {
|
||||
PageList(pageIds, imageLoader, currentPageIndex, navigation.toCameraScreen)
|
||||
BottomBar(showPdfDialog, showNewDocDialog)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
DocumentPreview(pageIds, imageLoader, currentPageIndex, onDeleteImage, padding)
|
||||
) { modifier ->
|
||||
DocumentPreview(pageIds, imageLoader, currentPageIndex, onDeleteImage, modifier)
|
||||
if (showNewDocDialog.value) {
|
||||
NewDocumentDialog(onConfirm = onStartNew, showNewDocDialog)
|
||||
}
|
||||
@@ -135,14 +132,12 @@ private fun DocumentPreview(
|
||||
imageLoader: (String) -> Bitmap?,
|
||||
currentPageIndex: MutableIntState,
|
||||
onDeleteImage: (String) -> Unit,
|
||||
padding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val imageId = pageIds[currentPageIndex.intValue]
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||
.padding(padding)
|
||||
) {
|
||||
Box (
|
||||
modifier = Modifier.fillMaxSize()
|
||||
@@ -175,13 +170,13 @@ private fun DocumentPreview(
|
||||
contentDescription = stringResource(R.string.delete_page),
|
||||
onClick = { onDeleteImage(imageId) },
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(8.dp)
|
||||
)
|
||||
Text("${currentPageIndex.intValue + 1} / ${pageIds.size}",
|
||||
color = MaterialTheme.colorScheme.inverseOnSurface,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(all = 16.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.5f),
|
||||
@@ -193,34 +188,6 @@ private fun DocumentPreview(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PageList(
|
||||
pageIds: List<String>,
|
||||
imageLoader: (String) -> Bitmap?,
|
||||
currentPageIndex: MutableState<Int>,
|
||||
toCameraScreen: () -> Unit
|
||||
) {
|
||||
Box {
|
||||
CommonPageList(
|
||||
CommonPageListState(
|
||||
pageIds,
|
||||
imageLoader,
|
||||
onPageClick = { index -> currentPageIndex.value = index },
|
||||
currentPageIndex = currentPageIndex.value,
|
||||
listState = rememberLazyListState()
|
||||
)
|
||||
)
|
||||
SecondaryActionButton(
|
||||
icon = Icons.Default.Add,
|
||||
onClick = toCameraScreen,
|
||||
contentDescription = stringResource(R.string.add_page),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(
|
||||
showPdfDialog: MutableState<Boolean>,
|
||||
@@ -296,3 +263,10 @@ fun DocumentScreenPreview() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 640, heightDp = 320)
|
||||
@Composable
|
||||
fun DocumentScreenPreviewInLandscapeMode() {
|
||||
DocumentScreenPreview()
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ fun CommonPageList(
|
||||
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
if (isLandscape) {
|
||||
LazyColumn (
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
) {
|
||||
itemsIndexed(state.pageIds) { index, id ->
|
||||
|
||||
132
app/src/main/java/org/mydomain/myscan/view/Scaffold.kt
Normal file
132
app/src/main/java/org/mydomain/myscan/view/Scaffold.kt
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun MyScaffold(
|
||||
toAboutScreen: () -> Unit,
|
||||
pageListState: CommonPageListState,
|
||||
pageListButton: (@Composable () -> Unit)? = null,
|
||||
bottomBar: @Composable () -> Unit,
|
||||
onBack: (() -> Unit)? = null,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
Box {
|
||||
if (!isLandscape(LocalConfiguration.current)) {
|
||||
Scaffold(
|
||||
bottomBar = { DocumentBar(pageListState, bottomBar, Modifier, pageListButton) }
|
||||
) { innerPadding ->
|
||||
content(Modifier.padding(innerPadding).fillMaxSize())
|
||||
}
|
||||
} else {
|
||||
Scaffold { innerPadding ->
|
||||
Row(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
content(Modifier.weight(2f))
|
||||
DocumentBar(pageListState, bottomBar, Modifier.weight(1f), pageListButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (onBack != null) {
|
||||
BackButton(
|
||||
onBack,
|
||||
modifier = Modifier.align(Alignment.TopStart).windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
)
|
||||
}
|
||||
AboutScreenNavButton(
|
||||
onClick = toAboutScreen,
|
||||
modifier = Modifier.align(Alignment.TopEnd).windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DocumentBar(
|
||||
pageListState: CommonPageListState,
|
||||
buttonBar: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
pageListButton: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
Column (
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
var modifier: Modifier = Modifier
|
||||
if (isLandscape(LocalConfiguration.current)) {
|
||||
modifier = modifier.weight(1f).fillMaxWidth()
|
||||
}
|
||||
Box (modifier) {
|
||||
CommonPageList(pageListState, modifier = Modifier.fillMaxWidth())
|
||||
|
||||
if (pageListButton != null) {
|
||||
Box (Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(horizontal = 8.dp, vertical = 16.dp)
|
||||
) {
|
||||
pageListButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BottomAppBar(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
if (isLandscape(LocalConfiguration.current)) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
buttonBar()
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 1.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
buttonBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isLandscape(configuration: Configuration): Boolean {
|
||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
}
|
||||
Reference in New Issue
Block a user