@@ -4,6 +4,8 @@ import com.getcode.theme.CodeTheme
44import android.content.Context
55import android.content.Intent
66import androidx.compose.animation.core.Animatable
7+ import androidx.compose.animation.core.animateFloatAsState
8+ import androidx.compose.animation.core.snap
79import androidx.compose.animation.core.tween
810import androidx.compose.foundation.Image
911import androidx.compose.foundation.background
@@ -28,6 +30,8 @@ import androidx.compose.runtime.Composable
2830import androidx.compose.runtime.LaunchedEffect
2931import androidx.compose.runtime.SideEffect
3032import androidx.compose.runtime.getValue
33+ import androidx.compose.runtime.mutableStateOf
34+ import androidx.compose.runtime.setValue
3135import androidx.compose.runtime.remember
3236import androidx.compose.runtime.rememberCoroutineScope
3337import androidx.compose.runtime.rememberUpdatedState
@@ -76,7 +80,8 @@ private const val MIN_BACKGROUND_ALPHA = 0.2f
7680 * A full-screen photo.
7781 *
7882 * Pinch zooms from 1× to 4× with the pan held inside the image's edges; a double tap goes to 2.5×
79- * about the tapped point, or back to fit when already zoomed. A one-finger drag that starts while
83+ * about the tapped point, or back to fit when already zoomed. A single tap hides or shows the
84+ * close and share buttons. A one-finger drag that starts while
8085 * the image is at fit pulls it away; when zoomed the same drag pans instead.
8186 *
8287 * On its own the pull moves the image down, fading the black behind it, and past a fifth of the
@@ -123,6 +128,12 @@ fun ChatMediaViewer(
123128 val currentOnPull by rememberUpdatedState(onPull)
124129 val currentOnPullEnd by rememberUpdatedState(onPullEnd)
125130 val touchSlop = LocalViewConfiguration .current.touchSlop
131+ var chromeVisible by remember { mutableStateOf(true ) }
132+ val chromeShown by animateFloatAsState(
133+ targetValue = if (chromeVisible) 1f else 0f ,
134+ animationSpec = if (reduceMotion) snap() else tween(200 ),
135+ label = " chrome" ,
136+ )
126137
127138 val painter = rememberAsyncImagePainter(
128139 model = model,
@@ -158,6 +169,7 @@ fun ChatMediaViewer(
158169 .testTag(VIEWER_IMAGE_TAG )
159170 .pointerInput(zoomState, reduceMotion) {
160171 detectTapGestures(
172+ onTap = { if (currentInteractive) chromeVisible = ! chromeVisible },
161173 onDoubleTap = { tap ->
162174 if (currentInteractive) {
163175 scope.launch { zoomState.toggle(tap, animated = ! reduceMotion) }
@@ -291,11 +303,12 @@ fun ChatMediaViewer(
291303 }
292304 }
293305
294- val chrome = Modifier .graphicsLayer { alpha = frame?.invoke()?.chromeAlpha ? : 1f }
306+ val chrome = Modifier .graphicsLayer { alpha = ( frame?.invoke()?.chromeAlpha ? : 1f ) * chromeShown }
295307 ViewerButton (
296308 icon = Icons .Filled .Close ,
297309 description = stringResource(R .string.description_chatMediaClose),
298310 enabled = interactive,
311+ visible = chromeVisible,
299312 tag = VIEWER_CLOSE_TAG ,
300313 onClick = onDismiss,
301314 modifier = chrome.align(Alignment .TopStart ),
@@ -304,6 +317,7 @@ fun ChatMediaViewer(
304317 icon = Icons .Outlined .IosShare ,
305318 description = stringResource(R .string.description_chatMediaShare),
306319 enabled = fullImageLoaded && interactive,
320+ visible = chromeVisible,
307321 tag = VIEWER_SHARE_TAG ,
308322 onClick = onShare,
309323 modifier = chrome.align(Alignment .TopEnd ),
@@ -323,13 +337,15 @@ private fun ViewerButton(
323337 icon : androidx.compose.ui.graphics.vector.ImageVector ,
324338 description : String ,
325339 enabled : Boolean ,
340+ visible : Boolean ,
326341 tag : String ,
327342 onClick : () -> Unit ,
328343 modifier : Modifier = Modifier ,
329344) {
330345 IconButton (
331346 onClick = onClick,
332- enabled = enabled,
347+ // Hidden buttons fade out at full tint but stop taking taps straight away.
348+ enabled = enabled && visible,
333349 modifier = modifier
334350 .statusBarsPadding()
335351 .padding(CodeTheme .dimens.staticGrid.x2)
0 commit comments