1414 */
1515package org.mydomain.myscan.view
1616
17+ import android.content.res.Configuration
1718import android.graphics.Bitmap
1819import android.graphics.BitmapFactory
1920import android.util.Log
@@ -32,13 +33,16 @@ import androidx.compose.foundation.layout.Arrangement
3233import androidx.compose.foundation.layout.Box
3334import androidx.compose.foundation.layout.Column
3435import androidx.compose.foundation.layout.Row
36+ import androidx.compose.foundation.layout.WindowInsets
3537import androidx.compose.foundation.layout.fillMaxHeight
3638import androidx.compose.foundation.layout.fillMaxSize
3739import androidx.compose.foundation.layout.fillMaxWidth
3840import androidx.compose.foundation.layout.height
3941import androidx.compose.foundation.layout.padding
42+ import androidx.compose.foundation.layout.safeDrawing
4043import androidx.compose.foundation.layout.size
4144import androidx.compose.foundation.layout.width
45+ import androidx.compose.foundation.layout.windowInsetsPadding
4246import androidx.compose.foundation.lazy.LazyListState
4347import androidx.compose.foundation.lazy.rememberLazyListState
4448import androidx.compose.foundation.shape.CircleShape
@@ -61,6 +65,7 @@ import androidx.compose.runtime.remember
6165import androidx.compose.runtime.setValue
6266import androidx.compose.ui.Alignment
6367import androidx.compose.ui.Modifier
68+ import androidx.compose.ui.draw.rotate
6469import androidx.compose.ui.geometry.Offset
6570import androidx.compose.ui.graphics.Color
6671import androidx.compose.ui.graphics.ImageBitmap
@@ -89,6 +94,7 @@ data class CameraUiState(
8994 val liveAnalysisState : LiveAnalysisState ,
9095 val captureState : CaptureState ,
9196 val showDetectionError : Boolean ,
97+ val isLandscape : Boolean ,
9298 val isDebugMode : Boolean
9399)
94100
@@ -136,6 +142,7 @@ fun CameraScreen(
136142 listState.animateScrollToItem(pageIds.lastIndex)
137143 }
138144 }
145+ val isLandscape = LocalConfiguration .current.orientation == Configuration .ORIENTATION_LANDSCAPE
139146 CameraScreenScaffold (
140147 cameraPreview = {
141148 CameraPreview (
@@ -144,21 +151,20 @@ fun CameraScreen(
144151 onPreviewViewReady = { view -> previewView = view }
145152 )
146153 },
147- pageList = {
148- CommonPageList (
154+ pageListState =
155+ CommonPageListState (
149156 pageIds = pageIds,
150157 imageLoader = { id -> viewModel.getBitmap(id) },
151158 onPageClick = { index -> viewModel.navigateTo(Screen .Document (index)) },
152159 listState = listState,
153- onLastItemPosition =
154- { offset -> thumbnailCoords.value = offset }
155- )
156- },
160+ onLastItemPosition = { offset -> thumbnailCoords.value = offset },
161+ ),
157162 cameraUiState = CameraUiState (
158163 pageIds.size,
159164 liveAnalysisState,
160165 captureState,
161166 showDetectionError,
167+ isLandscape = isLandscape,
162168 isDebugMode),
163169 onCapture = {
164170 previewView?.bitmap?.let {
@@ -179,58 +185,78 @@ fun CameraScreen(
179185@Composable
180186private fun CameraScreenScaffold (
181187 cameraPreview : @Composable () -> Unit ,
182- pageList : @Composable () -> Unit ,
188+ pageListState : CommonPageListState ,
183189 cameraUiState : CameraUiState ,
184190 onCapture : () -> Unit ,
185191 onFinalizePressed : () -> Unit ,
186192 onDebugModeSwitched : () -> Unit ,
187193 thumbnailCoords : MutableState <Offset >,
188194 toAboutScreen : () -> Unit ,
189195) {
196+ val documentBar : @Composable () -> Unit = {
197+ DocumentBar (
198+ pageListState = pageListState,
199+ pageCount = cameraUiState.pageCount,
200+ onFinalizePressed = onFinalizePressed,
201+ onDebugModeSwitched = onDebugModeSwitched,
202+ isLandscape = cameraUiState.isLandscape
203+ )
204+ }
190205 Box {
191- Scaffold (
192- bottomBar = {
193- CameraScreenFooter (
194- pageList = pageList,
195- pageCount = cameraUiState.pageCount,
196- onFinalizePressed = onFinalizePressed,
197- onDebugModeSwitched = onDebugModeSwitched,
198- )
206+ if (! cameraUiState.isLandscape) {
207+ Scaffold (
208+ bottomBar = documentBar
209+ ) { padding ->
210+ val modifier = Modifier .padding(bottom = padding.calculateBottomPadding()).fillMaxSize()
211+ CameraPreviewBox (cameraPreview, cameraUiState, onCapture, modifier)
199212 }
200- ) { innerPadding ->
201- Box (
202- modifier = Modifier
203- .padding(bottom = innerPadding.calculateBottomPadding())
204- .fillMaxSize()
205- ) {
206- CameraPreviewWithOverlay (cameraPreview, cameraUiState, Modifier .align(Alignment .BottomCenter ))
207- Box (
208- modifier = Modifier
209- .fillMaxSize()
210- .padding(innerPadding)
213+ } else {
214+ Scaffold { innerPadding ->
215+ Row (
216+ modifier = Modifier .padding(innerPadding).fillMaxSize()
211217 ) {
212- AboutScreenNavButton (
213- onClick = toAboutScreen,
214- modifier = Modifier .align(Alignment .TopEnd )
215- )
216- }
217- if (cameraUiState.isDebugMode) {
218- MessageBox (cameraUiState.liveAnalysisState.inferenceTime)
218+ CameraPreviewBox (cameraPreview, cameraUiState, onCapture, Modifier )
219+ documentBar()
219220 }
220- CaptureButton (
221- onClick = onCapture,
222- modifier = Modifier
223- .align(Alignment .BottomCenter )
224- .padding(16 .dp)
225- )
226221 }
227222 }
223+ AboutScreenNavButton (
224+ onClick = toAboutScreen,
225+ modifier = Modifier .align(Alignment .TopEnd ).windowInsetsPadding(WindowInsets .safeDrawing)
226+ )
228227 if (cameraUiState.captureState is CaptureState .CapturePreview ) {
229228 CapturedImage (cameraUiState.captureState.processed.asImageBitmap(), thumbnailCoords)
230229 }
231230 }
232231}
233232
233+ @Composable
234+ private fun CameraPreviewBox (
235+ cameraPreview : @Composable (() -> Unit ),
236+ cameraUiState : CameraUiState ,
237+ onCapture : () -> Unit ,
238+ modifier : Modifier ,
239+ ) {
240+ Box (
241+ modifier = modifier
242+ ) {
243+ CameraPreviewWithOverlay (
244+ cameraPreview,
245+ cameraUiState,
246+ Modifier .align(Alignment .BottomCenter )
247+ )
248+ if (cameraUiState.isDebugMode) {
249+ MessageBox (cameraUiState.liveAnalysisState.inferenceTime)
250+ }
251+ CaptureButton (
252+ onClick = onCapture,
253+ modifier = Modifier
254+ .align(Alignment .BottomCenter )
255+ .padding(16 .dp)
256+ )
257+ }
258+ }
259+
234260@Composable
235261private fun CapturedImage (image : ImageBitmap , thumbnailCoords : MutableState <Offset >) {
236262 Surface (
@@ -319,8 +345,12 @@ private fun CameraPreviewWithOverlay(
319345 modifier : Modifier ,
320346) {
321347 val captureState = cameraUiState.captureState
322- val width = LocalConfiguration .current.screenWidthDp
323- val height = width / 3 * 4
348+ var width = LocalConfiguration .current.screenWidthDp
349+ var height = width * 4 / 3
350+ if (cameraUiState.isLandscape) {
351+ height = LocalConfiguration .current.screenHeightDp
352+ width = height * 4 / 3
353+ }
324354
325355 var showShutter by remember { mutableStateOf(false ) }
326356 LaunchedEffect (captureState.frozenImage) {
@@ -366,7 +396,6 @@ private fun CameraPreviewWithOverlay(
366396 )
367397 }
368398 }
369-
370399 }
371400}
372401
@@ -382,11 +411,12 @@ fun MessageBox(inferenceTime: Long) {
382411}
383412
384413@Composable
385- fun CameraScreenFooter (
386- pageList : @Composable () -> Unit ,
414+ fun DocumentBar (
415+ pageListState : CommonPageListState ,
387416 pageCount : Int ,
388417 onFinalizePressed : () -> Unit ,
389418 onDebugModeSwitched : () -> Unit ,
419+ isLandscape : Boolean ,
390420) {
391421 var tapCount by remember { mutableStateOf(0 ) }
392422 var lastTapTime by remember { mutableStateOf(0L ) }
@@ -405,34 +435,55 @@ fun CameraScreenFooter(
405435 lastTapTime = currentTime
406436 }
407437
408- Column (modifier = Modifier .background(MaterialTheme .colorScheme.surfaceContainer)) {
409- pageList()
438+ Column (
439+ horizontalAlignment = Alignment .CenterHorizontally ,
440+ modifier = Modifier .background(MaterialTheme .colorScheme.surfaceContainer)
441+ ) {
442+ CommonPageList (pageListState, Modifier .weight(1f ))
410443 BottomAppBar (
411444 containerColor = MaterialTheme .colorScheme.surfaceContainerHigh,
412445 ) {
413- Row (
414- modifier = Modifier
415- .padding(horizontal = 16 .dp, vertical = 1 .dp)
416- .fillMaxWidth(),
417- verticalAlignment = Alignment .CenterVertically ,
418- horizontalArrangement = Arrangement .SpaceBetween
419- ) {
420- Text (
421- text = pageCountText(pageCount),
422- style = MaterialTheme .typography.bodyMedium,
423- modifier = Modifier .clickable(onClick = onPageCountClick)
424- )
425- MainActionButton (
426- onClick = onFinalizePressed,
427- enabled = pageCount > 0 ,
428- text = " Document" ,
429- icon = Icons .AutoMirrored .Filled .Article ,
430- )
446+ if (isLandscape) {
447+ Column (
448+ horizontalAlignment = Alignment .CenterHorizontally ,
449+ modifier = Modifier .fillMaxWidth()
450+ ) {
451+ Bar (pageCount, onPageCountClick, onFinalizePressed)
452+ }
453+ } else {
454+ Row (
455+ modifier = Modifier
456+ .padding(horizontal = 16 .dp, vertical = 1 .dp)
457+ .fillMaxWidth(),
458+ verticalAlignment = Alignment .CenterVertically ,
459+ horizontalArrangement = Arrangement .SpaceBetween
460+ ) {
461+ Bar (pageCount, onPageCountClick, onFinalizePressed)
462+ }
431463 }
432464 }
433465 }
434466}
435467
468+ @Composable
469+ private fun Bar (
470+ pageCount : Int ,
471+ onPageCountClick : () -> Unit ,
472+ onFinalizePressed : () -> Unit ,
473+ ) {
474+ Text (
475+ text = pageCountText(pageCount),
476+ style = MaterialTheme .typography.bodyMedium,
477+ modifier = Modifier .clickable(onClick = onPageCountClick)
478+ )
479+ MainActionButton (
480+ onClick = onFinalizePressed,
481+ enabled = pageCount > 0 ,
482+ text = " Document" ,
483+ icon = Icons .AutoMirrored .Filled .Article ,
484+ )
485+ }
486+
436487@Preview(showBackground = true )
437488@Composable
438489fun CameraScreenPreview () {
@@ -447,8 +498,14 @@ fun CameraScreenPreviewWithProcessedImage() {
447498 debugImage(" gallica.bnf.fr-bpt6k5530456s-1.jpg" )))
448499}
449500
501+ @Preview(showBackground = true , widthDp = 640 , heightDp = 320 )
450502@Composable
451- private fun ScreenPreview (captureState : CaptureState ) {
503+ fun CameraScreenPreviewInLandscapeMode () {
504+ ScreenPreview (CaptureState .Idle , rotationDegrees = 90f )
505+ }
506+
507+ @Composable
508+ private fun ScreenPreview (captureState : CaptureState , rotationDegrees : Float = 0f) {
452509 val context = LocalContext .current
453510 MyScanTheme {
454511 val thumbnailCoords = remember { mutableStateOf(Offset .Zero ) }
@@ -462,12 +519,13 @@ private fun ScreenPreview(captureState: CaptureState) {
462519 ) {
463520 Image (
464521 debugImage(" uncropped/img01.jpg" ).asImageBitmap(),
522+ modifier= Modifier .rotate(rotationDegrees),
465523 contentDescription = null
466524 )
467525 }
468526 },
469- pageList = {
470- CommonPageList (
527+ pageListState =
528+ CommonPageListState (
471529 pageIds = listOf (1 , 2 , 2 , 2 ).map { " gallica.bnf.fr-bpt6k5530456s-$it .jpg" },
472530 imageLoader = { id ->
473531 context.assets.open(id).use { input ->
@@ -476,9 +534,9 @@ private fun ScreenPreview(captureState: CaptureState) {
476534 },
477535 onPageClick = {},
478536 listState = LazyListState (),
479- )
480- } ,
481- cameraUiState = CameraUiState (pageCount = 4 , LiveAnalysisState (), captureState, false , false ),
537+ ),
538+ cameraUiState = CameraUiState (pageCount = 4 , LiveAnalysisState (), captureState ,
539+ false , rotationDegrees > 0 , false ),
482540 onCapture = {},
483541 onFinalizePressed = {},
484542 onDebugModeSwitched = {},
0 commit comments