CameraScreen: Landscape mode
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.mydomain.myscan.view
|
package org.mydomain.myscan.view
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -32,13 +33,16 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -61,6 +65,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
@@ -89,6 +94,7 @@ data class CameraUiState(
|
|||||||
val liveAnalysisState: LiveAnalysisState,
|
val liveAnalysisState: LiveAnalysisState,
|
||||||
val captureState: CaptureState,
|
val captureState: CaptureState,
|
||||||
val showDetectionError: Boolean,
|
val showDetectionError: Boolean,
|
||||||
|
val isLandscape: Boolean,
|
||||||
val isDebugMode: Boolean
|
val isDebugMode: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,6 +142,7 @@ fun CameraScreen(
|
|||||||
listState.animateScrollToItem(pageIds.lastIndex)
|
listState.animateScrollToItem(pageIds.lastIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
CameraScreenScaffold(
|
CameraScreenScaffold(
|
||||||
cameraPreview = {
|
cameraPreview = {
|
||||||
CameraPreview(
|
CameraPreview(
|
||||||
@@ -144,21 +151,20 @@ fun CameraScreen(
|
|||||||
onPreviewViewReady = { view -> previewView = view }
|
onPreviewViewReady = { view -> previewView = view }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pageList = {
|
pageListState =
|
||||||
CommonPageList(
|
CommonPageListState(
|
||||||
pageIds = pageIds,
|
pageIds = pageIds,
|
||||||
imageLoader = { id -> viewModel.getBitmap(id) },
|
imageLoader = { id -> viewModel.getBitmap(id) },
|
||||||
onPageClick = { index -> viewModel.navigateTo(Screen.Document(index)) },
|
onPageClick = { index -> viewModel.navigateTo(Screen.Document(index)) },
|
||||||
listState = listState,
|
listState = listState,
|
||||||
onLastItemPosition =
|
onLastItemPosition = { offset -> thumbnailCoords.value = offset },
|
||||||
{ offset -> thumbnailCoords.value = offset }
|
),
|
||||||
)
|
|
||||||
},
|
|
||||||
cameraUiState = CameraUiState(
|
cameraUiState = CameraUiState(
|
||||||
pageIds.size,
|
pageIds.size,
|
||||||
liveAnalysisState,
|
liveAnalysisState,
|
||||||
captureState,
|
captureState,
|
||||||
showDetectionError,
|
showDetectionError,
|
||||||
|
isLandscape = isLandscape,
|
||||||
isDebugMode),
|
isDebugMode),
|
||||||
onCapture = {
|
onCapture = {
|
||||||
previewView?.bitmap?.let {
|
previewView?.bitmap?.let {
|
||||||
@@ -179,7 +185,7 @@ fun CameraScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun CameraScreenScaffold(
|
private fun CameraScreenScaffold(
|
||||||
cameraPreview: @Composable () -> Unit,
|
cameraPreview: @Composable () -> Unit,
|
||||||
pageList: @Composable () -> Unit,
|
pageListState: CommonPageListState,
|
||||||
cameraUiState: CameraUiState,
|
cameraUiState: CameraUiState,
|
||||||
onCapture: () -> Unit,
|
onCapture: () -> Unit,
|
||||||
onFinalizePressed: () -> Unit,
|
onFinalizePressed: () -> Unit,
|
||||||
@@ -187,50 +193,70 @@ private fun CameraScreenScaffold(
|
|||||||
thumbnailCoords: MutableState<Offset>,
|
thumbnailCoords: MutableState<Offset>,
|
||||||
toAboutScreen: () -> Unit,
|
toAboutScreen: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val documentBar : @Composable () -> Unit = {
|
||||||
|
DocumentBar(
|
||||||
|
pageListState = pageListState,
|
||||||
|
pageCount = cameraUiState.pageCount,
|
||||||
|
onFinalizePressed = onFinalizePressed,
|
||||||
|
onDebugModeSwitched = onDebugModeSwitched,
|
||||||
|
isLandscape = cameraUiState.isLandscape
|
||||||
|
)
|
||||||
|
}
|
||||||
Box {
|
Box {
|
||||||
Scaffold(
|
if (!cameraUiState.isLandscape) {
|
||||||
bottomBar = {
|
Scaffold(
|
||||||
CameraScreenFooter(
|
bottomBar = documentBar
|
||||||
pageList = pageList,
|
) { padding ->
|
||||||
pageCount = cameraUiState.pageCount,
|
val modifier = Modifier.padding(bottom = padding.calculateBottomPadding()).fillMaxSize()
|
||||||
onFinalizePressed = onFinalizePressed,
|
CameraPreviewBox(cameraPreview, cameraUiState, onCapture, modifier)
|
||||||
onDebugModeSwitched = onDebugModeSwitched,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
} else {
|
||||||
Box(
|
Scaffold { innerPadding ->
|
||||||
modifier = Modifier
|
Row(
|
||||||
.padding(bottom = innerPadding.calculateBottomPadding())
|
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
CameraPreviewWithOverlay(cameraPreview, cameraUiState, Modifier.align(Alignment.BottomCenter))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(innerPadding)
|
|
||||||
) {
|
) {
|
||||||
AboutScreenNavButton(
|
CameraPreviewBox(cameraPreview, cameraUiState, onCapture, Modifier)
|
||||||
onClick = toAboutScreen,
|
documentBar()
|
||||||
modifier = Modifier.align(Alignment.TopEnd)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (cameraUiState.isDebugMode) {
|
|
||||||
MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
|
|
||||||
}
|
|
||||||
CaptureButton(
|
|
||||||
onClick = onCapture,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AboutScreenNavButton(
|
||||||
|
onClick = toAboutScreen,
|
||||||
|
modifier = Modifier.align(Alignment.TopEnd).windowInsetsPadding(WindowInsets.safeDrawing)
|
||||||
|
)
|
||||||
if (cameraUiState.captureState is CaptureState.CapturePreview) {
|
if (cameraUiState.captureState is CaptureState.CapturePreview) {
|
||||||
CapturedImage(cameraUiState.captureState.processed.asImageBitmap(), thumbnailCoords)
|
CapturedImage(cameraUiState.captureState.processed.asImageBitmap(), thumbnailCoords)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CameraPreviewBox(
|
||||||
|
cameraPreview: @Composable (() -> Unit),
|
||||||
|
cameraUiState: CameraUiState,
|
||||||
|
onCapture: () -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
CameraPreviewWithOverlay(
|
||||||
|
cameraPreview,
|
||||||
|
cameraUiState,
|
||||||
|
Modifier.align(Alignment.BottomCenter)
|
||||||
|
)
|
||||||
|
if (cameraUiState.isDebugMode) {
|
||||||
|
MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
|
||||||
|
}
|
||||||
|
CaptureButton(
|
||||||
|
onClick = onCapture,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CapturedImage(image: ImageBitmap, thumbnailCoords: MutableState<Offset>) {
|
private fun CapturedImage(image: ImageBitmap, thumbnailCoords: MutableState<Offset>) {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -319,8 +345,12 @@ private fun CameraPreviewWithOverlay(
|
|||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
) {
|
) {
|
||||||
val captureState = cameraUiState.captureState
|
val captureState = cameraUiState.captureState
|
||||||
val width = LocalConfiguration.current.screenWidthDp
|
var width = LocalConfiguration.current.screenWidthDp
|
||||||
val height = width / 3 * 4
|
var height = width * 4 / 3
|
||||||
|
if (cameraUiState.isLandscape) {
|
||||||
|
height = LocalConfiguration.current.screenHeightDp
|
||||||
|
width = height * 4 / 3
|
||||||
|
}
|
||||||
|
|
||||||
var showShutter by remember { mutableStateOf(false) }
|
var showShutter by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(captureState.frozenImage) {
|
LaunchedEffect(captureState.frozenImage) {
|
||||||
@@ -366,7 +396,6 @@ private fun CameraPreviewWithOverlay(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,11 +411,12 @@ fun MessageBox(inferenceTime: Long) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CameraScreenFooter(
|
fun DocumentBar(
|
||||||
pageList: @Composable () -> Unit,
|
pageListState: CommonPageListState,
|
||||||
pageCount: Int,
|
pageCount: Int,
|
||||||
onFinalizePressed: () -> Unit,
|
onFinalizePressed: () -> Unit,
|
||||||
onDebugModeSwitched: () -> Unit,
|
onDebugModeSwitched: () -> Unit,
|
||||||
|
isLandscape: Boolean,
|
||||||
) {
|
) {
|
||||||
var tapCount by remember { mutableStateOf(0) }
|
var tapCount by remember { mutableStateOf(0) }
|
||||||
var lastTapTime by remember { mutableStateOf(0L) }
|
var lastTapTime by remember { mutableStateOf(0L) }
|
||||||
@@ -405,34 +435,55 @@ fun CameraScreenFooter(
|
|||||||
lastTapTime = currentTime
|
lastTapTime = currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
Column (modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
|
Column (
|
||||||
pageList()
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
) {
|
||||||
|
CommonPageList(pageListState, Modifier.weight(1f))
|
||||||
BottomAppBar(
|
BottomAppBar(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
) {
|
) {
|
||||||
Row (
|
if (isLandscape) {
|
||||||
modifier = Modifier
|
Column(
|
||||||
.padding(horizontal = 16.dp, vertical = 1.dp)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
) {
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
Bar(pageCount, onPageCountClick, onFinalizePressed)
|
||||||
) {
|
}
|
||||||
Text(
|
} else {
|
||||||
text = pageCountText(pageCount),
|
Row(
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
modifier = Modifier
|
||||||
modifier = Modifier.clickable(onClick = onPageCountClick)
|
.padding(horizontal = 16.dp, vertical = 1.dp)
|
||||||
)
|
.fillMaxWidth(),
|
||||||
MainActionButton(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onClick = onFinalizePressed,
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
enabled = pageCount > 0,
|
) {
|
||||||
text = "Document",
|
Bar(pageCount, onPageCountClick, onFinalizePressed)
|
||||||
icon = Icons.AutoMirrored.Filled.Article,
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Bar(
|
||||||
|
pageCount: Int,
|
||||||
|
onPageCountClick: () -> Unit,
|
||||||
|
onFinalizePressed: () -> Unit,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = pageCountText(pageCount),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.clickable(onClick = onPageCountClick)
|
||||||
|
)
|
||||||
|
MainActionButton(
|
||||||
|
onClick = onFinalizePressed,
|
||||||
|
enabled = pageCount > 0,
|
||||||
|
text = "Document",
|
||||||
|
icon = Icons.AutoMirrored.Filled.Article,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun CameraScreenPreview() {
|
fun CameraScreenPreview() {
|
||||||
@@ -447,8 +498,14 @@ fun CameraScreenPreviewWithProcessedImage() {
|
|||||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg")))
|
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, widthDp = 640, heightDp = 320)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ScreenPreview(captureState: CaptureState) {
|
fun CameraScreenPreviewInLandscapeMode() {
|
||||||
|
ScreenPreview(CaptureState.Idle, rotationDegrees = 90f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenPreview(captureState: CaptureState, rotationDegrees: Float = 0f) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
MyScanTheme {
|
MyScanTheme {
|
||||||
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
|
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
|
||||||
@@ -462,12 +519,13 @@ private fun ScreenPreview(captureState: CaptureState) {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
debugImage("uncropped/img01.jpg").asImageBitmap(),
|
debugImage("uncropped/img01.jpg").asImageBitmap(),
|
||||||
|
modifier=Modifier.rotate(rotationDegrees),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pageList = {
|
pageListState =
|
||||||
CommonPageList(
|
CommonPageListState(
|
||||||
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
|
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
|
||||||
imageLoader = { id ->
|
imageLoader = { id ->
|
||||||
context.assets.open(id).use { input ->
|
context.assets.open(id).use { input ->
|
||||||
@@ -476,9 +534,9 @@ private fun ScreenPreview(captureState: CaptureState) {
|
|||||||
},
|
},
|
||||||
onPageClick = {},
|
onPageClick = {},
|
||||||
listState = LazyListState(),
|
listState = LazyListState(),
|
||||||
)
|
),
|
||||||
},
|
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState,
|
||||||
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState, false, false),
|
false, rotationDegrees > 0, false),
|
||||||
onCapture = {},
|
onCapture = {},
|
||||||
onFinalizePressed = {},
|
onFinalizePressed = {},
|
||||||
onDebugModeSwitched = {},
|
onDebugModeSwitched = {},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
@@ -201,10 +202,13 @@ private fun PageList(
|
|||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
CommonPageList(
|
CommonPageList(
|
||||||
pageIds,
|
CommonPageListState(
|
||||||
imageLoader,
|
pageIds,
|
||||||
onPageClick = { index -> currentPageIndex.value = index },
|
imageLoader,
|
||||||
currentPageIndex = currentPageIndex.value,
|
onPageClick = { index -> currentPageIndex.value = index },
|
||||||
|
currentPageIndex = currentPageIndex.value,
|
||||||
|
listState = rememberLazyListState()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
SecondaryActionButton(
|
SecondaryActionButton(
|
||||||
icon = Icons.Default.Add,
|
icon = Icons.Default.Add,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.mydomain.myscan.view
|
package org.mydomain.myscan.view
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -26,10 +27,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -40,6 +41,7 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
@@ -48,42 +50,55 @@ import androidx.compose.ui.unit.sp
|
|||||||
|
|
||||||
const val PAGE_LIST_ELEMENT_SIZE_DP = 120
|
const val PAGE_LIST_ELEMENT_SIZE_DP = 120
|
||||||
|
|
||||||
|
data class CommonPageListState(
|
||||||
|
val pageIds: List<String>,
|
||||||
|
val imageLoader: (String) -> Bitmap?,
|
||||||
|
val onPageClick: (Int) -> Unit,
|
||||||
|
val listState: LazyListState,
|
||||||
|
val currentPageIndex: Int? = null,
|
||||||
|
val onLastItemPosition: ((Offset) -> Unit)? = null,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommonPageList(
|
fun CommonPageList(
|
||||||
pageIds: List<String>,
|
state: CommonPageListState,
|
||||||
imageLoader: (String) -> Bitmap?,
|
modifier: Modifier = Modifier,
|
||||||
onPageClick: (Int) -> Unit,
|
|
||||||
listState: LazyListState = rememberLazyListState(),
|
|
||||||
currentPageIndex: Int? = null,
|
|
||||||
onLastItemPosition: ((Offset) -> Unit)? = null,
|
|
||||||
) {
|
) {
|
||||||
LazyRow (
|
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
state = listState,
|
if (isLandscape) {
|
||||||
contentPadding = PaddingValues(4.dp),
|
LazyColumn (
|
||||||
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceContainer),
|
modifier = modifier
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
) {
|
||||||
verticalAlignment = Alignment.CenterVertically
|
itemsIndexed(state.pageIds) { index, id ->
|
||||||
) {
|
// TODO Use small images rather than big ones
|
||||||
itemsIndexed(pageIds) { index, id ->
|
val image = state.imageLoader(id)
|
||||||
// TODO Use small images rather than big ones
|
if (image != null) {
|
||||||
val image = imageLoader(id)
|
PageThumbnail(image, index, state)
|
||||||
if (image != null) {
|
}
|
||||||
PageThumbnail(
|
}
|
||||||
image,
|
}
|
||||||
index,
|
} else {
|
||||||
currentPageIndex,
|
LazyRow (
|
||||||
pageIds.lastIndex,
|
state = state.listState,
|
||||||
onLastItemPosition,
|
contentPadding = PaddingValues(4.dp),
|
||||||
onPageClick
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pageIds.isEmpty()) {
|
if (state.pageIds.isEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(120.dp)
|
.height(120.dp)
|
||||||
.addPositionCallback(onLastItemPosition, LocalDensity.current, 0.5f)
|
.addPositionCallback(state.onLastItemPosition, LocalDensity.current, 0.5f)
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,13 +107,10 @@ fun CommonPageList(
|
|||||||
private fun PageThumbnail(
|
private fun PageThumbnail(
|
||||||
image: Bitmap,
|
image: Bitmap,
|
||||||
index: Int,
|
index: Int,
|
||||||
currentPageIndex: Int?,
|
state: CommonPageListState,
|
||||||
lastIndex: Int,
|
|
||||||
onLastItemPosition: ((Offset) -> Unit)?,
|
|
||||||
onPageClick: (Int) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val bitmap = image.asImageBitmap()
|
val bitmap = image.asImageBitmap()
|
||||||
val isSelected = index == currentPageIndex
|
val isSelected = index == state.currentPageIndex
|
||||||
val borderColor =
|
val borderColor =
|
||||||
if (isSelected) MaterialTheme.colorScheme.secondary else Color.Transparent
|
if (isSelected) MaterialTheme.colorScheme.secondary else Color.Transparent
|
||||||
val maxImageSize = PAGE_LIST_ELEMENT_SIZE_DP.dp
|
val maxImageSize = PAGE_LIST_ELEMENT_SIZE_DP.dp
|
||||||
@@ -107,9 +119,9 @@ private fun PageThumbnail(
|
|||||||
Modifier.height(maxImageSize)
|
Modifier.height(maxImageSize)
|
||||||
else
|
else
|
||||||
Modifier.width(maxImageSize)
|
Modifier.width(maxImageSize)
|
||||||
if (index == lastIndex) {
|
if (index == state.pageIds.lastIndex) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
modifier = modifier.addPositionCallback(onLastItemPosition, density, 1.0f)
|
modifier = modifier.addPositionCallback(state.onLastItemPosition, density, 1.0f)
|
||||||
}
|
}
|
||||||
Box (modifier = Modifier.height(PAGE_LIST_ELEMENT_SIZE_DP.dp)) {
|
Box (modifier = Modifier.height(PAGE_LIST_ELEMENT_SIZE_DP.dp)) {
|
||||||
Image(
|
Image(
|
||||||
@@ -119,12 +131,11 @@ private fun PageThumbnail(
|
|||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.border(2.dp, borderColor)
|
.border(2.dp, borderColor)
|
||||||
.clickable { onPageClick(index) }
|
.clickable { state.onPageClick(index) }
|
||||||
)
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.background(Color.Black.copy(alpha = 0.5f), shape = RoundedCornerShape(4.dp))
|
.background(Color.Black.copy(alpha = 0.5f), shape = RoundedCornerShape(4.dp))
|
||||||
.padding(vertical = 0.dp, horizontal = 8.dp)
|
.padding(vertical = 0.dp, horizontal = 8.dp)
|
||||||
@@ -134,7 +145,6 @@ private fun PageThumbnail(
|
|||||||
color = Color.White,
|
color = Color.White,
|
||||||
fontSize = 10.sp,
|
fontSize = 10.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user