I'm working on a Compose app where I need a custom view to expand to full screen when dragged up and return to its original size when dragged down. However, my current implementation isn't achieving the desired result.
Problem: Unable to smoothly transition the view to full screen upon dragging.
Approach: Using Modifier.offset and pointerInput to detect drag gestures.
Request: Seeking guidance on achieving a smooth transition for the view to go full screen and back.
Current Behavior: The custom view, located within a bottom app bar and sized around 200dp, does not transition to full screen as intended when dragged up. Expected Behavior: When the user drags the custom view upward, it should smoothly transition to a full-screen view. Conversely, when dragged downward, it should revert to its original size within the bottom app bar.
Any insights or suggestions would be greatly appreciated!
Here's a breakdown of the problem
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostMediaContent(
mediaPaths: List<Pair<Int, String>>,
navController: NavController,
pagerState: PagerState,
viewModel: FilterScreenViewModel,
onTabChange: (index: Int) -> Unit = {},
onMediaSelected: (List<Pair<Int, String>>) -> Unit
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var inSelectionMode by remember { mutableStateOf(false) }
var isVideo by remember { mutableStateOf(false) }
var selectedImageId by remember { mutableStateOf(mediaPaths.firstOrNull()?.first ?: -1) }
val selectedImageIds = remember { mutableStateOf(setOf<Int>()) }
var selectedImagePath by remember { mutableStateOf(mediaPaths.firstOrNull()?.second ?: "") }
val isFullScreen = remember { mutableStateOf(false) }
val topPadding = remember { mutableStateOf(0.dp) }
LaunchedEffect(mediaPaths) {
if (mediaPaths.isNotEmpty()) {
selectedImagePath = mediaPaths.first().second
isVideo = selectedImagePath?.endsWith(".mp4", ignoreCase = true) ?: false
}
}
val offsetY = remember { mutableStateOf(0f) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 56.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.TopCenter)
) {
Column(
verticalArrangement = Arrangement.spacedBy((-60).dp)
) {
if (isVideo) {
val zoomState = rememberZoomState()
Box(
modifier = Modifier
.background(Color.Black)
.height(400.dp)
.fillMaxWidth()
.snapBackZoomable(
zoomState = zoomState,
)
.clickable {
}
) {
VideoPlayer(
videoUrl = selectedImagePath
)
onMediaSelected(
listOf(selectedImageId to selectedImagePath)
)
}
} else {
val zoomState = rememberZoomState()
AsyncImage(
model = ImageRequest
.Builder(LocalContext.current)
.data(selectedImagePath)
.build(),
onSuccess = { state ->
zoomState.setContentSize(state.painter.intrinsicSize)
},
modifier = Modifier
.height(400.dp)
.fillMaxWidth()
.snapBackZoomable(
zoomState = zoomState,
),
contentDescription = null,
contentScale = ContentScale.Crop
)
onMediaSelected(
listOf(selectedImageId to selectedImagePath)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.background(Color.Transparent)
) {
Image(
painter = painterResource(id = R.drawable.core_ui_ic_tab_expand),
contentDescription = stringResource(id = R.string.regular_post_expand_toast),
modifier = Modifier.clickable {
}
)
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.drawable.core_ui_ic_tab_multiselect),
contentDescription = stringResource(id = R.string.regular_post_multi_toast),
modifier = Modifier.clickable {
inSelectionMode = true
}
)
}
}
}
// here is the main code where is the gesture is implemneted
Box(
modifier = Modifier
.offset { IntOffset(0, offsetY.value.toInt()) }
.pointerInput(Unit) {
detectVerticalDragGestures(
onDragStart = { offsetY.value = 0f },
onDragEnd = {
isFullScreen.value = offsetY.value < 1000.dp.toPx()
offsetY.value = 0f
topPadding.value = if (isFullScreen.value) 56.dp else 0.dp
}
) { change, dragAmount ->
offsetY.value += dragAmount
change.consumeAllChanges() // Consume the drag event
}
}
) {
Column(
modifier = Modifier
.fillMaxWidth()
.then(if (isFullScreen.value) Modifier.fillMaxHeight() else
Modifier.height(1000.dp).fillMaxWidth())
.padding(top = topPadding.value)
) {
GalleryTabsComposable(
pagerState = pagerState,
onTabChange = { index ->
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
})
HorizontalPager(
state = pagerState,
modifier = Modifier.background(Color.Black)
) { page ->
when (PostTabs.entries[page]) {
PostTabs.GALLERY -> {
PostPhotoGrid(
navController = navController,
mediaPaths(context),
inSelectionMode,
viewModel,
onImageClick = { imagePath ->
selectedImagePath = imagePath
if (imagePath.endsWith(".mp4", true) ||
imagePath.endsWith("webm", true)
) {
isVideo = true
}
selectedImageId =
mediaPaths.find { it.second == imagePath }?.first ?: -1
if (inSelectionMode) {
if (selectedImageIds.value.contains(selectedImageId)) {
selectedImageIds.value - selectedImageId
} else {
selectedImageIds.value + selectedImageId
}
}
}
)
}
else -> {
DraftPhotoGrid(
navController = navController,
)
}
}
}
}
}
}
}
}
@Preview
@Composable
fun PostMediaContentPreview() {
val pagerState = rememberPagerState(pageCount = { 3 })
val navController = rememberNavController()
val dummyMediaPaths = listOf(
1 to "https://picsum.photos/id/237/200/300",
2 to "https://picsum.photos/id/238/200/300",
3 to "https://picsum.photos/id/239/200/300"
)
PostMediaContent(
mediaPaths = dummyMediaPaths,
navController = navController,
pagerState = pagerState,
viewModel = hiltViewModel(),
onTabChange = {},
onMediaSelected = {}
)
}
Thank you in advance for your assistance!