Introduce MediaViewerFlickToDismiss and extract to its own file
This commit is contained in:
parent
2267a4b787
commit
f55da9027b
2 changed files with 95 additions and 85 deletions
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.mediaviewer.impl.viewer
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import me.saket.telephoto.flick.FlickToDismiss
|
||||||
|
import me.saket.telephoto.flick.FlickToDismissState
|
||||||
|
import me.saket.telephoto.flick.rememberFlickToDismissState
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MediaViewerFlickToDismiss(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onDragging: () -> Unit = {},
|
||||||
|
content: @Composable BoxScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false)
|
||||||
|
DismissFlickEffects(
|
||||||
|
flickState = flickState,
|
||||||
|
onDismissing = { animationDuration ->
|
||||||
|
delay(animationDuration / 3)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
onDragging = onDragging,
|
||||||
|
)
|
||||||
|
FlickToDismiss(
|
||||||
|
state = flickState,
|
||||||
|
modifier = modifier.background(backgroundColorFor(flickState)),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DismissFlickEffects(
|
||||||
|
flickState: FlickToDismissState,
|
||||||
|
onDismissing: suspend (Duration) -> Unit,
|
||||||
|
onDragging: suspend () -> Unit,
|
||||||
|
) {
|
||||||
|
val currentOnDismissing by rememberUpdatedState(onDismissing)
|
||||||
|
val currentOnDragging by rememberUpdatedState(onDragging)
|
||||||
|
|
||||||
|
when (val gestureState = flickState.gestureState) {
|
||||||
|
is FlickToDismissState.GestureState.Dismissing -> {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
currentOnDismissing(gestureState.animationDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FlickToDismissState.GestureState.Dragging -> {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
currentOnDragging()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun backgroundColorFor(flickState: FlickToDismissState): Color {
|
||||||
|
val animatedAlpha by animateFloatAsState(
|
||||||
|
targetValue = when (flickState.gestureState) {
|
||||||
|
is FlickToDismissState.GestureState.Dismissed,
|
||||||
|
is FlickToDismissState.GestureState.Dismissing -> 0f
|
||||||
|
is FlickToDismissState.GestureState.Dragging,
|
||||||
|
is FlickToDismissState.GestureState.Idle,
|
||||||
|
is FlickToDismissState.GestureState.Resetting -> 1f - flickState.offsetFraction
|
||||||
|
},
|
||||||
|
label = "Background alpha",
|
||||||
|
)
|
||||||
|
return Color.Black.copy(alpha = animatedAlpha)
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ package io.element.android.libraries.mediaviewer.impl.viewer
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -81,13 +80,9 @@ import io.element.android.libraries.mediaviewer.impl.local.PlayableState
|
||||||
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import me.saket.telephoto.flick.FlickToDismiss
|
|
||||||
import me.saket.telephoto.flick.FlickToDismissState
|
|
||||||
import me.saket.telephoto.flick.rememberFlickToDismissState
|
|
||||||
import me.saket.telephoto.zoomable.ZoomSpec
|
import me.saket.telephoto.zoomable.ZoomSpec
|
||||||
import me.saket.telephoto.zoomable.rememberZoomableState
|
import me.saket.telephoto.zoomable.rememberZoomableState
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MediaViewerView(
|
fun MediaViewerView(
|
||||||
|
|
@ -289,22 +284,13 @@ private fun MediaViewerPage(
|
||||||
) {
|
) {
|
||||||
val currentShowOverlay by rememberUpdatedState(showOverlay)
|
val currentShowOverlay by rememberUpdatedState(showOverlay)
|
||||||
val currentOnShowOverlayChange by rememberUpdatedState(onShowOverlayChange)
|
val currentOnShowOverlayChange by rememberUpdatedState(onShowOverlayChange)
|
||||||
val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false)
|
|
||||||
|
|
||||||
DismissFlickEffects(
|
MediaViewerFlickToDismiss(
|
||||||
flickState = flickState,
|
onDismiss = onDismiss,
|
||||||
onDismissing = { animationDuration ->
|
|
||||||
delay(animationDuration / 3)
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
onDragging = {
|
onDragging = {
|
||||||
currentOnShowOverlayChange(false)
|
currentOnShowOverlayChange(false)
|
||||||
}
|
},
|
||||||
)
|
modifier = modifier,
|
||||||
|
|
||||||
FlickToDismiss(
|
|
||||||
state = flickState,
|
|
||||||
modifier = modifier.background(backgroundColorFor(flickState))
|
|
||||||
) {
|
) {
|
||||||
val downloadedMedia by data.downloadedMedia
|
val downloadedMedia by data.downloadedMedia
|
||||||
val showProgress = rememberShowProgress(downloadedMedia)
|
val showProgress = rememberShowProgress(downloadedMedia)
|
||||||
|
|
@ -371,20 +357,9 @@ private fun MediaViewerLoadingPage(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false)
|
MediaViewerFlickToDismiss(
|
||||||
|
onDismiss = onDismiss,
|
||||||
DismissFlickEffects(
|
modifier = modifier,
|
||||||
flickState = flickState,
|
|
||||||
onDismissing = { animationDuration ->
|
|
||||||
delay(animationDuration / 3)
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
onDragging = {},
|
|
||||||
)
|
|
||||||
|
|
||||||
FlickToDismiss(
|
|
||||||
state = flickState,
|
|
||||||
modifier = modifier.background(backgroundColorFor(flickState))
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -403,20 +378,9 @@ private fun MediaViewerErrorPage(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false)
|
MediaViewerFlickToDismiss(
|
||||||
|
onDismiss = onDismiss,
|
||||||
DismissFlickEffects(
|
modifier = modifier,
|
||||||
flickState = flickState,
|
|
||||||
onDismissing = { animationDuration ->
|
|
||||||
delay(animationDuration / 3)
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
onDragging = {},
|
|
||||||
)
|
|
||||||
|
|
||||||
FlickToDismiss(
|
|
||||||
state = flickState,
|
|
||||||
modifier = modifier.background(backgroundColorFor(flickState))
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -432,30 +396,6 @@ private fun MediaViewerErrorPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DismissFlickEffects(
|
|
||||||
flickState: FlickToDismissState,
|
|
||||||
onDismissing: suspend (Duration) -> Unit,
|
|
||||||
onDragging: suspend () -> Unit,
|
|
||||||
) {
|
|
||||||
val currentOnDismissing by rememberUpdatedState(onDismissing)
|
|
||||||
val currentOnDragging by rememberUpdatedState(onDragging)
|
|
||||||
|
|
||||||
when (val gestureState = flickState.gestureState) {
|
|
||||||
is FlickToDismissState.GestureState.Dismissing -> {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
currentOnDismissing(gestureState.animationDuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is FlickToDismissState.GestureState.Dragging -> {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
currentOnDragging()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun rememberShowProgress(downloadedMedia: AsyncData<LocalMedia>): Boolean {
|
private fun rememberShowProgress(downloadedMedia: AsyncData<LocalMedia>): Boolean {
|
||||||
var showProgress by remember {
|
var showProgress by remember {
|
||||||
|
|
@ -623,21 +563,6 @@ private fun ErrorView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun backgroundColorFor(flickState: FlickToDismissState): Color {
|
|
||||||
val animatedAlpha by animateFloatAsState(
|
|
||||||
targetValue = when (flickState.gestureState) {
|
|
||||||
is FlickToDismissState.GestureState.Dismissed,
|
|
||||||
is FlickToDismissState.GestureState.Dismissing -> 0f
|
|
||||||
is FlickToDismissState.GestureState.Dragging,
|
|
||||||
is FlickToDismissState.GestureState.Idle,
|
|
||||||
is FlickToDismissState.GestureState.Resetting -> 1f - flickState.offsetFraction
|
|
||||||
},
|
|
||||||
label = "Background alpha",
|
|
||||||
)
|
|
||||||
return Color.Black.copy(alpha = animatedAlpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only preview in dark, dark theme is forced on the Node.
|
// Only preview in dark, dark theme is forced on the Node.
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue