Переработан fullscreen фото из медиапикера под поведение Telegram
This commit is contained in:
@@ -75,6 +75,7 @@ import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -162,6 +163,9 @@ fun ChatDetailScreen(
|
||||
) {
|
||||
val viewModel: ChatViewModel = viewModel(key = "chat_${user.publicKey}")
|
||||
val context = LocalContext.current
|
||||
val hasNativeNavigationBar = remember(context) {
|
||||
com.rosetta.messenger.ui.utils.NavigationModeUtils.hasNativeNavigationBar(context)
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
@@ -290,6 +294,8 @@ fun ChatDetailScreen(
|
||||
var imageViewerInitialIndex by remember { mutableStateOf(0) }
|
||||
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
|
||||
var imageViewerImages by remember { mutableStateOf<List<ViewableImage>>(emptyList()) }
|
||||
var simplePickerPreviewUri by remember { mutableStateOf<Uri?>(null) }
|
||||
var simplePickerPreviewSourceThumb by remember { mutableStateOf<ThumbnailPosition?>(null) }
|
||||
|
||||
// 🎨 Управление статус баром — ВСЕГДА чёрные иконки в светлой теме
|
||||
if (!view.isInEditMode) {
|
||||
@@ -364,7 +370,8 @@ fun ChatDetailScreen(
|
||||
showEmojiPicker,
|
||||
pendingCameraPhotoUri,
|
||||
pendingGalleryImages,
|
||||
showInAppCamera
|
||||
showInAppCamera,
|
||||
simplePickerPreviewUri
|
||||
) {
|
||||
derivedStateOf {
|
||||
showImageViewer ||
|
||||
@@ -372,7 +379,8 @@ fun ChatDetailScreen(
|
||||
showEmojiPicker ||
|
||||
pendingCameraPhotoUri != null ||
|
||||
pendingGalleryImages.isNotEmpty() ||
|
||||
showInAppCamera
|
||||
showInAppCamera ||
|
||||
simplePickerPreviewUri != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1801,7 +1809,10 @@ fun ChatDetailScreen(
|
||||
bottom =
|
||||
16.dp
|
||||
)
|
||||
.navigationBarsPadding()
|
||||
.then(
|
||||
if (hasNativeNavigationBar) Modifier.navigationBarsPadding()
|
||||
else Modifier
|
||||
)
|
||||
.graphicsLayer {
|
||||
scaleX =
|
||||
buttonScale
|
||||
@@ -2168,7 +2179,9 @@ fun ChatDetailScreen(
|
||||
inputFocusTrigger =
|
||||
inputFocusTrigger,
|
||||
suppressKeyboard =
|
||||
showInAppCamera
|
||||
showInAppCamera,
|
||||
hasNativeNavigationBar =
|
||||
hasNativeNavigationBar
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3005,7 +3018,13 @@ fun ChatDetailScreen(
|
||||
onAvatarClick = {
|
||||
viewModel.sendAvatarMessage()
|
||||
},
|
||||
recipientName = user.title
|
||||
recipientName = user.title,
|
||||
onPhotoPreviewRequested = { uri, sourceThumb ->
|
||||
hideInputOverlays()
|
||||
showMediaPicker = false
|
||||
simplePickerPreviewSourceThumb = sourceThumb
|
||||
simplePickerPreviewUri = uri
|
||||
}
|
||||
)
|
||||
} else {
|
||||
MediaPickerBottomSheet(
|
||||
@@ -3049,7 +3068,13 @@ fun ChatDetailScreen(
|
||||
onAvatarClick = {
|
||||
viewModel.sendAvatarMessage()
|
||||
},
|
||||
recipientName = user.title
|
||||
recipientName = user.title,
|
||||
onPhotoPreviewRequested = { uri, sourceThumb ->
|
||||
hideInputOverlays()
|
||||
showMediaPicker = false
|
||||
simplePickerPreviewSourceThumb = sourceThumb
|
||||
simplePickerPreviewUri = uri
|
||||
}
|
||||
)
|
||||
}
|
||||
} // Закрытие Box wrapper для Scaffold content
|
||||
@@ -3314,6 +3339,18 @@ fun ChatDetailScreen(
|
||||
|
||||
} // Закрытие Scaffold content lambda
|
||||
|
||||
simplePickerPreviewUri?.let { previewUri ->
|
||||
SimpleFullscreenPhotoOverlay(
|
||||
imageUri = previewUri,
|
||||
sourceThumbnail = simplePickerPreviewSourceThumb,
|
||||
modifier = Modifier.fillMaxSize().zIndex(100f),
|
||||
onDismiss = {
|
||||
simplePickerPreviewUri = null
|
||||
simplePickerPreviewSourceThumb = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// <20> Image Viewer Overlay — FULLSCREEN поверх Scaffold
|
||||
if (showImageViewer && imageViewerImages.isNotEmpty()) {
|
||||
ImageViewerScreen(
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.rosetta.messenger.ui.chats.components.ImageEditorScreen
|
||||
import com.rosetta.messenger.ui.chats.components.PhotoPreviewWithCaptionScreen
|
||||
import com.rosetta.messenger.ui.chats.components.SimpleFullscreenPhotoViewer
|
||||
import com.rosetta.messenger.ui.chats.components.ThumbnailPosition
|
||||
import com.rosetta.messenger.ui.components.OptimizedEmojiPicker
|
||||
import com.rosetta.messenger.ui.components.KeyboardHeightProvider
|
||||
@@ -146,6 +147,7 @@ fun ChatAttachAlert(
|
||||
currentUserPublicKey: String = "",
|
||||
maxSelection: Int = 10,
|
||||
recipientName: String? = null,
|
||||
onPhotoPreviewRequested: ((Uri, ThumbnailPosition?) -> Unit)? = null,
|
||||
viewModel: AttachAlertViewModel = viewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -679,8 +681,8 @@ fun ChatAttachAlert(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldShow) {
|
||||
if (!shouldShow) return@LaunchedEffect
|
||||
LaunchedEffect(shouldShow, state.editingItem) {
|
||||
if (!shouldShow || state.editingItem != null) return@LaunchedEffect
|
||||
val window = (view.context as? Activity)?.window ?: return@LaunchedEffect
|
||||
snapshotFlow { Triple(scrimAlpha, isPickerFullScreen, isDarkTheme) }
|
||||
.collect { (alpha, fullScreen, dark) ->
|
||||
@@ -710,7 +712,7 @@ fun ChatAttachAlert(
|
||||
// POPUP RENDERING
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
if (shouldShow) {
|
||||
if (shouldShow && state.editingItem == null) {
|
||||
Popup(
|
||||
alignment = Alignment.TopStart,
|
||||
onDismissRequest = {
|
||||
@@ -976,8 +978,13 @@ fun ChatAttachAlert(
|
||||
},
|
||||
onItemClick = { item, position ->
|
||||
if (!item.isVideo) {
|
||||
hideKeyboard()
|
||||
if (onPhotoPreviewRequested != null) {
|
||||
onPhotoPreviewRequested(item.uri, position)
|
||||
} else {
|
||||
thumbnailPosition = position
|
||||
viewModel.setEditingItem(item)
|
||||
}
|
||||
} else {
|
||||
viewModel.toggleSelection(item.id, maxSelection)
|
||||
}
|
||||
@@ -1173,45 +1180,14 @@ fun ChatAttachAlert(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
state.editingItem?.let { item ->
|
||||
ImageEditorScreen(
|
||||
SimpleFullscreenPhotoViewer(
|
||||
imageUri = item.uri,
|
||||
sourceThumbnail = thumbnailPosition,
|
||||
onDismiss = {
|
||||
viewModel.setEditingItem(null)
|
||||
thumbnailPosition = null
|
||||
shouldShow = true
|
||||
},
|
||||
onSave = { editedUri ->
|
||||
viewModel.setEditingItem(null)
|
||||
thumbnailPosition = null
|
||||
if (onMediaSelectedWithCaption == null) {
|
||||
previewPhotoUri = editedUri
|
||||
} else {
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelected(listOf(mediaItem), "")
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
onSaveWithCaption = if (onMediaSelectedWithCaption != null) { editedUri, caption ->
|
||||
viewModel.setEditingItem(null)
|
||||
thumbnailPosition = null
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelectedWithCaption(mediaItem, caption)
|
||||
onDismiss()
|
||||
} else null,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showCaptionInput = onMediaSelectedWithCaption != null,
|
||||
recipientName = recipientName,
|
||||
thumbnailPosition = thumbnailPosition
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,8 @@ fun MediaPickerBottomSheet(
|
||||
onAvatarClick: () -> Unit = {},
|
||||
currentUserPublicKey: String = "",
|
||||
maxSelection: Int = 10,
|
||||
recipientName: String? = null
|
||||
recipientName: String? = null,
|
||||
onPhotoPreviewRequested: ((Uri, ThumbnailPosition?) -> Unit)? = null
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -567,8 +568,8 @@ fun MediaPickerBottomSheet(
|
||||
}
|
||||
|
||||
// Reactive updates — single snapshotFlow drives ALL system bar colors
|
||||
LaunchedEffect(shouldShow) {
|
||||
if (!shouldShow) return@LaunchedEffect
|
||||
LaunchedEffect(shouldShow, editingItem) {
|
||||
if (!shouldShow || editingItem != null) return@LaunchedEffect
|
||||
val window = (view.context as? android.app.Activity)?.window ?: return@LaunchedEffect
|
||||
|
||||
snapshotFlow { Triple(scrimAlpha, isPickerFullScreen, isDarkTheme) }
|
||||
@@ -593,7 +594,7 @@ fun MediaPickerBottomSheet(
|
||||
}
|
||||
|
||||
// Используем Popup для показа поверх клавиатуры
|
||||
if (shouldShow) {
|
||||
if (shouldShow && editingItem == null) {
|
||||
// BackHandler для закрытия по back
|
||||
BackHandler {
|
||||
if (isExpanded) {
|
||||
@@ -986,8 +987,13 @@ fun MediaPickerBottomSheet(
|
||||
},
|
||||
onItemClick = { item, position ->
|
||||
if (!item.isVideo) {
|
||||
hideKeyboard()
|
||||
if (onPhotoPreviewRequested != null) {
|
||||
onPhotoPreviewRequested(item.uri, position)
|
||||
} else {
|
||||
thumbnailPosition = position
|
||||
editingItem = item
|
||||
}
|
||||
} else {
|
||||
// Videos don't have photo editor in this flow.
|
||||
toggleSelection(item.id)
|
||||
@@ -1279,48 +1285,16 @@ fun MediaPickerBottomSheet(
|
||||
)
|
||||
}
|
||||
|
||||
// Image Editor FULLSCREEN overlay для фото из галереи
|
||||
// ImageEditorScreen wraps itself in a Dialog internally — no external wrapper needed
|
||||
// Fullscreen preview для выбранной фото из галереи (чистый экран без тулбаров).
|
||||
editingItem?.let { item ->
|
||||
ImageEditorScreen(
|
||||
SimpleFullscreenPhotoViewer(
|
||||
imageUri = item.uri,
|
||||
sourceThumbnail = thumbnailPosition,
|
||||
onDismiss = {
|
||||
editingItem = null
|
||||
thumbnailPosition = null
|
||||
shouldShow = true
|
||||
},
|
||||
onSave = { editedUri ->
|
||||
editingItem = null
|
||||
thumbnailPosition = null
|
||||
if (onMediaSelectedWithCaption == null) {
|
||||
previewPhotoUri = editedUri
|
||||
} else {
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelected(listOf(mediaItem), "")
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
onSaveWithCaption = if (onMediaSelectedWithCaption != null) { editedUri, caption ->
|
||||
editingItem = null
|
||||
thumbnailPosition = null
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelectedWithCaption(mediaItem, caption)
|
||||
onDismiss()
|
||||
} else null,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showCaptionInput = onMediaSelectedWithCaption != null,
|
||||
recipientName = recipientName,
|
||||
thumbnailPosition = thumbnailPosition
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package com.rosetta.messenger.ui.chats.components
|
||||
|
||||
import android.graphics.Color as AndroidColor
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import coil.compose.AsyncImage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private val ViewerExpandEasing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1.0f)
|
||||
|
||||
private data class SimpleViewerTransform(
|
||||
val scaleX: Float,
|
||||
val scaleY: Float,
|
||||
val translationX: Float,
|
||||
val translationY: Float,
|
||||
val cornerRadiusDp: Float
|
||||
)
|
||||
|
||||
private fun lerpFloat(start: Float, stop: Float, fraction: Float): Float {
|
||||
return start + (stop - start) * fraction
|
||||
}
|
||||
|
||||
private fun setupFullscreenWindow(window: Window?) {
|
||||
window ?: return
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
window.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
window.setBackgroundDrawable(ColorDrawable(AndroidColor.TRANSPARENT))
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
window.decorView.setPadding(0, 0, 0, 0)
|
||||
|
||||
val attrs = window.attributes
|
||||
attrs.width = WindowManager.LayoutParams.MATCH_PARENT
|
||||
attrs.height = WindowManager.LayoutParams.MATCH_PARENT
|
||||
attrs.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
window.attributes = attrs
|
||||
|
||||
val decorView = window.decorView
|
||||
val telegramLikeFlags =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
if (decorView.systemUiVisibility != telegramLikeFlags) {
|
||||
decorView.systemUiVisibility = telegramLikeFlags
|
||||
}
|
||||
|
||||
window.statusBarColor = AndroidColor.TRANSPARENT
|
||||
window.navigationBarColor = AndroidColor.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isStatusBarContrastEnforced = false
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
val controller = WindowCompat.getInsetsController(window, decorView)
|
||||
controller.isAppearanceLightStatusBars = false
|
||||
controller.isAppearanceLightNavigationBars = false
|
||||
controller.show(WindowInsetsCompat.Type.statusBars())
|
||||
controller.show(WindowInsetsCompat.Type.navigationBars())
|
||||
window.setWindowAnimations(0)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleFullscreenPhotoViewer(
|
||||
imageUri: android.net.Uri,
|
||||
onDismiss: () -> Unit,
|
||||
sourceThumbnail: ThumbnailPosition? = null
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties =
|
||||
DialogProperties(
|
||||
dismissOnBackPress = false,
|
||||
dismissOnClickOutside = false,
|
||||
usePlatformDefaultWidth = false,
|
||||
decorFitsSystemWindows = false
|
||||
)
|
||||
) {
|
||||
val view = LocalView.current
|
||||
SideEffect {
|
||||
val dialogWindow = (view.parent as? DialogWindowProvider)?.window
|
||||
setupFullscreenWindow(dialogWindow)
|
||||
}
|
||||
SimpleFullscreenPhotoContent(
|
||||
imageUri = imageUri,
|
||||
onDismiss = onDismiss,
|
||||
sourceThumbnail = sourceThumbnail
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleFullscreenPhotoOverlay(
|
||||
imageUri: android.net.Uri,
|
||||
onDismiss: () -> Unit,
|
||||
sourceThumbnail: ThumbnailPosition? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
SimpleFullscreenPhotoContent(
|
||||
imageUri = imageUri,
|
||||
onDismiss = onDismiss,
|
||||
sourceThumbnail = sourceThumbnail,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SimpleFullscreenPhotoContent(
|
||||
imageUri: android.net.Uri,
|
||||
onDismiss: () -> Unit,
|
||||
sourceThumbnail: ThumbnailPosition? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var isClosing by remember { mutableStateOf(false) }
|
||||
var screenSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
val progress = remember(imageUri, sourceThumbnail) {
|
||||
Animatable(if (sourceThumbnail != null) 0f else 1f)
|
||||
}
|
||||
|
||||
LaunchedEffect(imageUri, sourceThumbnail) {
|
||||
if (progress.value < 1f) {
|
||||
progress.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(durationMillis = 230, easing = ViewerExpandEasing)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun closeViewer() {
|
||||
if (isClosing) return
|
||||
isClosing = true
|
||||
scope.launch {
|
||||
progress.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = 210, easing = ViewerExpandEasing)
|
||||
)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler { closeViewer() }
|
||||
|
||||
val transform by remember(sourceThumbnail, screenSize, progress.value) {
|
||||
derivedStateOf {
|
||||
val p = progress.value
|
||||
if (sourceThumbnail != null && screenSize != IntSize.Zero) {
|
||||
val screenW = screenSize.width.toFloat().coerceAtLeast(1f)
|
||||
val screenH = screenSize.height.toFloat().coerceAtLeast(1f)
|
||||
val srcW = sourceThumbnail.width.coerceAtLeast(1f)
|
||||
val srcH = sourceThumbnail.height.coerceAtLeast(1f)
|
||||
|
||||
val sourceScaleX = srcW / screenW
|
||||
val sourceScaleY = srcH / screenH
|
||||
|
||||
val targetCenterX = screenW / 2f
|
||||
val targetCenterY = screenH / 2f
|
||||
val sourceCenterX = sourceThumbnail.x + srcW / 2f
|
||||
val sourceCenterY = sourceThumbnail.y + srcH / 2f
|
||||
|
||||
SimpleViewerTransform(
|
||||
scaleX = lerpFloat(sourceScaleX, 1f, p),
|
||||
scaleY = lerpFloat(sourceScaleY, 1f, p),
|
||||
translationX = lerpFloat(sourceCenterX - targetCenterX, 0f, p),
|
||||
translationY = lerpFloat(sourceCenterY - targetCenterY, 0f, p),
|
||||
cornerRadiusDp = lerpFloat(sourceThumbnail.cornerRadius, 0f, p)
|
||||
)
|
||||
} else {
|
||||
SimpleViewerTransform(
|
||||
scaleX = lerpFloat(0.94f, 1f, p),
|
||||
scaleY = lerpFloat(0.94f, 1f, p),
|
||||
translationX = 0f,
|
||||
translationY = 0f,
|
||||
cornerRadiusDp = 0f
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
modifier.fillMaxSize()
|
||||
.onSizeChanged { screenSize = it }
|
||||
.background(Color.Black)
|
||||
.pointerInput(imageUri) { detectTapGestures(onTap = { closeViewer() }) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AsyncImage(
|
||||
model = imageUri,
|
||||
contentDescription = "Photo",
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
scaleX = transform.scaleX
|
||||
scaleY = transform.scaleY
|
||||
translationX = transform.translationX
|
||||
translationY = transform.translationY
|
||||
}
|
||||
.then(
|
||||
if (transform.cornerRadiusDp > 0f) {
|
||||
Modifier.clip(RoundedCornerShape(transform.cornerRadiusDp.dp))
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user