This commit is contained in:
Eric85710 2026-06-21 14:39:20 +08:00
parent 025afc5b13
commit 2795f32a30

View file

@ -4,9 +4,22 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -19,6 +32,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage import coil.compose.AsyncImage
@ -34,18 +48,20 @@ fun ImageLightbox(
usePlatformDefaultWidth = false usePlatformDefaultWidth = false
) )
) { ) {
// 1. 宣告手勢狀態
var scale by remember { mutableStateOf(1f) } var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) } var offset by remember { mutableStateOf(Offset.Zero) }
// 💡 控制下拉選單顯示/隱藏的狀態
var menuExpanded by remember { mutableStateOf(false) }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color.Black.copy(alpha = 0.9f)) .background(Color.Black.copy(alpha = 0.9f))
// 💡 如果圖片處於放大狀態,點擊背景不觸發關閉,方便使用者操作;縮回原大小時點擊背景才關閉
.clickable(enabled = scale == 1f) { onDismiss() }, .clickable(enabled = scale == 1f) { onDismiss() },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// 1. 底層:大圖檢視元件(保持不變)
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
contentDescription = "放大檢視圖片", contentDescription = "放大檢視圖片",
@ -53,33 +69,70 @@ fun ImageLightbox(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(0.85f) .fillMaxHeight(0.85f)
// 2. 透過 graphicsLayer 將手勢狀態反映到畫面上
.graphicsLayer( .graphicsLayer(
scaleX = scale, scaleX = scale,
scaleY = scale, scaleY = scale,
translationX = offset.x, translationX = offset.x,
translationY = offset.y translationY = offset.y
) )
// 3. 監聽雙指與單指手勢
.pointerInput(Unit) { .pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ -> detectTransformGestures { _, pan, zoom, _ ->
// 計算新的縮放值,並限制在 1倍 到 4倍 之間
val newScale = (scale * zoom).coerceIn(1f, 4f) val newScale = (scale * zoom).coerceIn(1f, 4f)
val newOffset = if (newScale > 1f) offset + pan else Offset.Zero
// 計算新的位移值(只有在放大狀態下才允許平移拖曳)
val newOffset = if (newScale > 1f) {
offset + pan
} else {
Offset.Zero // 縮回 1 倍時自動歸零
}
scale = newScale scale = newScale
offset = newOffset offset = newOffset
} }
} }
// 阻斷點擊圖片本身會關閉 Dialog 的行為
.clickable(enabled = false) { } .clickable(enabled = false) { }
) )
// 2. 上層:右上角「更多」按鈕與下拉選單容器
// 透過 windowInsetsPadding 確保按鈕不會被手機的瀏海或狀態列擋住
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars)
.padding(16.dp),
contentAlignment = Alignment.TopEnd
) {
// 包裹 IconButton 與 DropdownMenu 的小容器
Box(modifier = Modifier.wrapContentSize()) {
IconButton(
onClick = { menuExpanded = true }, // 點擊開啟選單
modifier = Modifier.background(
color = Color.Black.copy(alpha = 0.4f), // 幫按鈕加個微黑底,避免遇到白圖時看不清
shape = CircleShape
)
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "更多選項",
tint = Color.White
)
}
// Material 3 下拉選單
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = { menuExpanded = false } // 點擊選單外部關閉
) {
DropdownMenuItem(
text = { Text("儲存圖片") },
onClick = {
menuExpanded = false
// TODO: 實作下載圖片到相簿的邏輯
}
)
DropdownMenuItem(
text = { Text("分享") },
onClick = {
menuExpanded = false
// TODO: 實作系統分享 Intent 邏輯
}
)
}
}
}
} }
} }
} }