Animation: compute the real destination coordinates
This commit is contained in:
@@ -52,6 +52,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -59,10 +60,14 @@ 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.scale
|
import androidx.compose.ui.draw.scale
|
||||||
|
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.boundsInWindow
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -91,6 +96,7 @@ fun CameraScreen(
|
|||||||
) {
|
) {
|
||||||
var previewView by remember { mutableStateOf<PreviewView?>(null) }
|
var previewView by remember { mutableStateOf<PreviewView?>(null) }
|
||||||
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
|
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
|
||||||
|
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
|
||||||
|
|
||||||
val captureController = remember { CameraCaptureController() }
|
val captureController = remember { CameraCaptureController() }
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
@@ -124,7 +130,9 @@ fun CameraScreen(
|
|||||||
pageIds = pageIds,
|
pageIds = pageIds,
|
||||||
imageLoader = { id -> viewModel.getBitmap(id) },
|
imageLoader = { id -> viewModel.getBitmap(id) },
|
||||||
onPageClick = { index -> viewModel.navigateTo(Screen.FinalizeDocument(index)) },
|
onPageClick = { index -> viewModel.navigateTo(Screen.FinalizeDocument(index)) },
|
||||||
listState = listState
|
listState = listState,
|
||||||
|
onLastItemPosition =
|
||||||
|
{ coords -> thumbnailCoords.value = coords.localToWindow(Offset.Zero) }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cameraUiState = CameraUiState(pageIds.size, liveAnalysisState, captureState),
|
cameraUiState = CameraUiState(pageIds.size, liveAnalysisState, captureState),
|
||||||
@@ -136,6 +144,7 @@ fun CameraScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onFinalizePressed = onFinalizePressed,
|
onFinalizePressed = onFinalizePressed,
|
||||||
|
thumbnailCoords = thumbnailCoords,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +155,7 @@ private fun CameraScreenScaffold(
|
|||||||
cameraUiState: CameraUiState,
|
cameraUiState: CameraUiState,
|
||||||
onCapture: () -> Unit,
|
onCapture: () -> Unit,
|
||||||
onFinalizePressed: () -> Unit,
|
onFinalizePressed: () -> Unit,
|
||||||
|
thumbnailCoords: MutableState<Offset>,
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -172,12 +182,12 @@ private fun CameraScreenScaffold(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CapturedImage(cameraUiState)
|
CapturedImage(cameraUiState, thumbnailCoords)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CapturedImage(cameraUiState: CameraUiState) {
|
private fun CapturedImage(cameraUiState: CameraUiState, thumbnailCoords: MutableState<Offset>) {
|
||||||
cameraUiState.captureState.processedImage?.let { image ->
|
cameraUiState.captureState.processedImage?.let { image ->
|
||||||
Surface(
|
Surface(
|
||||||
color = Color.Black.copy(alpha = 0.3f),
|
color = Color.Black.copy(alpha = 0.3f),
|
||||||
@@ -190,8 +200,9 @@ private fun CapturedImage(cameraUiState: CameraUiState) {
|
|||||||
isAnimating = true
|
isAnimating = true
|
||||||
}
|
}
|
||||||
val transition = updateTransition(targetState = isAnimating, label = "captureAnimation")
|
val transition = updateTransition(targetState = isAnimating, label = "captureAnimation")
|
||||||
val targetOffsetX = 0.dp // TODO real value
|
val density = LocalDensity.current
|
||||||
val targetOffsetY = 200.dp // TODO real value
|
var targetOffsetX by remember { mutableStateOf(0.dp) }
|
||||||
|
var targetOffsetY by remember { mutableStateOf(0.dp) }
|
||||||
|
|
||||||
val offsetX by transition.animateDp(
|
val offsetX by transition.animateDp(
|
||||||
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
|
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
|
||||||
@@ -206,17 +217,31 @@ private fun CapturedImage(cameraUiState: CameraUiState) {
|
|||||||
label = "scale"
|
label = "scale"
|
||||||
) { if (it) 0.3f else 1f }
|
) { if (it) 0.3f else 1f }
|
||||||
|
|
||||||
|
|
||||||
|
val justABitToTheTop = 100.dp
|
||||||
|
Box (modifier = Modifier
|
||||||
|
.onGloballyPositioned { coordinates ->
|
||||||
|
val bounds = coordinates.boundsInWindow()
|
||||||
|
val centerX = bounds.left + bounds.width / 2
|
||||||
|
val centerY = bounds.top + bounds.height / 2
|
||||||
|
with(density) {
|
||||||
|
targetOffsetX = thumbnailCoords.value.x.toDp() - centerX.toDp() + PAGE_LIST_ELEMENT_SIZE_DP.dp
|
||||||
|
targetOffsetY = thumbnailCoords.value.y.toDp() - centerY.toDp() + justABitToTheTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
Image(
|
Image(
|
||||||
bitmap = image.asImageBitmap(),
|
bitmap = image.asImageBitmap(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(24.dp)
|
.padding(24.dp)
|
||||||
.offset(x = offsetX, y = offsetY - 100.dp)
|
.offset(x = offsetX, y = offsetY - justABitToTheTop)
|
||||||
.scale(scale)
|
.scale(scale)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CaptureButton(onClick: () -> Unit, modifier: Modifier) {
|
fun CaptureButton(onClick: () -> Unit, modifier: Modifier) {
|
||||||
@@ -333,6 +358,7 @@ fun CameraScreenPreviewWithProcessedImage() {
|
|||||||
private fun ScreenPreview(captureState: CaptureState) {
|
private fun ScreenPreview(captureState: CaptureState) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
MyScanTheme {
|
MyScanTheme {
|
||||||
|
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
|
||||||
CameraScreenScaffold(
|
CameraScreenScaffold(
|
||||||
cameraPreview = {
|
cameraPreview = {
|
||||||
Box(
|
Box(
|
||||||
@@ -356,12 +382,13 @@ private fun ScreenPreview(captureState: CaptureState) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPageClick = {},
|
onPageClick = {},
|
||||||
listState = LazyListState()
|
listState = LazyListState(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState),
|
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState),
|
||||||
onCapture = {},
|
onCapture = {},
|
||||||
onFinalizePressed = {},
|
onFinalizePressed = {},
|
||||||
|
thumbnailCoords = thumbnailCoords,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.LayoutCoordinates
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
const val PAGE_LIST_ELEMENT_SIZE_DP = 120
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommonPageList(
|
fun CommonPageList(
|
||||||
pageIds: List<String>,
|
pageIds: List<String>,
|
||||||
@@ -45,6 +49,7 @@ fun CommonPageList(
|
|||||||
onPageClick: (Int) -> Unit,
|
onPageClick: (Int) -> Unit,
|
||||||
listState: LazyListState = rememberLazyListState(),
|
listState: LazyListState = rememberLazyListState(),
|
||||||
currentPageIndex: Int? = null,
|
currentPageIndex: Int? = null,
|
||||||
|
onLastItemPosition: ((LayoutCoordinates) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
LazyRow (
|
LazyRow (
|
||||||
state = listState,
|
state = listState,
|
||||||
@@ -60,15 +65,20 @@ fun CommonPageList(
|
|||||||
val image = imageLoader(id)
|
val image = imageLoader(id)
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
val bitmap = image.asImageBitmap()
|
val bitmap = image.asImageBitmap()
|
||||||
|
|
||||||
val isSelected = index == currentPageIndex
|
val isSelected = index == currentPageIndex
|
||||||
val borderColor =
|
val borderColor =
|
||||||
if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
|
if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
|
||||||
val maxImageSize = 120.dp
|
val maxImageSize = PAGE_LIST_ELEMENT_SIZE_DP.dp
|
||||||
val modifier =
|
var modifier =
|
||||||
if (bitmap.height > bitmap.width)
|
if (bitmap.height > bitmap.width)
|
||||||
Modifier.height(maxImageSize)
|
Modifier.height(maxImageSize)
|
||||||
else
|
else
|
||||||
Modifier.width(maxImageSize)
|
Modifier.width(maxImageSize)
|
||||||
|
val isLastItem = index == pageIds.lastIndex
|
||||||
|
if (isLastItem && onLastItemPosition != null) {
|
||||||
|
modifier = modifier.onGloballyPositioned(onLastItemPosition)
|
||||||
|
}
|
||||||
Image(
|
Image(
|
||||||
bitmap = bitmap,
|
bitmap = bitmap,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|||||||
Reference in New Issue
Block a user