diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8ea3fc1..30f081c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -105,6 +105,9 @@ dependencies { implementation(libs.camera.view) implementation(libs.androidx.exifinterface) + // 日期记录器:详情页灯箱图片缩放/旋转(与 sketch 同作者 panpf,SketchZoomAsyncImage 是 AsyncImage 超集) + implementation(libs.zoomimage.compose.sketch4) + testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${libs.versions.kotlin.get()}") testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.room.testing) diff --git a/core/src/main/kotlin/plus/rua/project/ui/ImageLightbox.kt b/core/src/main/kotlin/plus/rua/project/ui/ImageLightbox.kt new file mode 100644 index 0000000..e9f79a0 --- /dev/null +++ b/core/src/main/kotlin/plus/rua/project/ui/ImageLightbox.kt @@ -0,0 +1,72 @@ +package plus.rua.project.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.github.panpf.sketch.rememberAsyncImageState +import com.github.panpf.zoomimage.SketchZoomAsyncImage +import com.github.panpf.zoomimage.rememberSketchZoomState + +/** + * 全屏图片灯箱,支持双指缩放、平移与旋转查看。 + * + * 在全屏深色遮罩中居中显示图片,底层用 zoomimage 的 [SketchZoomAsyncImage] + * 提供手势:双指缩放、单指平移、双指捻合自由旋转(松手后吸附到最近的 + * 0/90/180/270°)、双击复位。点击图片外的空白区域或按返回键触发 [onDismiss]。 + * + * sketch 的 [rememberAsyncImageState] 负责加载,zoomimage 的 + * [rememberSketchZoomState] 负责缩放/平移/旋转——后者是 sketch `AsyncImage` + * 之外新增的必需参数。 + * + * @param photoUri 图片 URI;为 null 时直接返回不渲染 + * @param contentDescription 无障碍描述,传 null 表示纯装饰 + * @param onDismiss 关闭回调(点击空白区域或按返回键时触发) + * @param modifier 布局修饰符 + */ +@Composable +fun ImageLightbox( + photoUri: String?, + contentDescription: String?, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + if (photoUri == null) return + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties( + decorFitsSystemWindows = false, + usePlatformDefaultWidth = false + ) + ) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.Black) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onDismiss + ), + contentAlignment = Alignment.Center + ) { + val asyncImageState = rememberAsyncImageState() + val zoomState = rememberSketchZoomState() + SketchZoomAsyncImage( + uri = photoUri, + contentDescription = contentDescription, + state = asyncImageState, + zoomState = zoomState, + modifier = Modifier.fillMaxSize() + ) + } + } +} diff --git a/core/src/main/kotlin/plus/rua/project/ui/RecordDetailScreen.kt b/core/src/main/kotlin/plus/rua/project/ui/RecordDetailScreen.kt index 8168e90..c1a232e 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/RecordDetailScreen.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/RecordDetailScreen.kt @@ -1,5 +1,7 @@ package plus.rua.project.ui +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -81,6 +83,7 @@ fun RecordDetailScreen( ) val state by viewModel.uiState.collectAsStateWithLifecycle() var showDeleteDialog by remember { mutableStateOf(false) } + var showLightbox by remember { mutableStateOf(false) } // 删除完成后自动返回 if (state.deleted) { @@ -142,6 +145,7 @@ fun RecordDetailScreen( else -> DetailContent( state = state, + onPhotoClick = { showLightbox = true }, modifier = Modifier .fillMaxSize() .padding(innerPadding) @@ -165,11 +169,20 @@ fun RecordDetailScreen( } ) } + + if (showLightbox) { + ImageLightbox( + photoUri = state.photoUri, + contentDescription = state.record?.title, + onDismiss = { showLightbox = false } + ) + } } @Composable private fun DetailContent( state: DateRecordDetailUiState, + onPhotoClick: () -> Unit, modifier: Modifier = Modifier ) { val record = state.record ?: return @@ -183,6 +196,11 @@ private fun DetailContent( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onPhotoClick + ) ) Column(modifier = Modifier.padding(horizontal = 16.dp)) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a513813..62b1a53 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ sketch = "4.4.0" spotless = "8.8.0" tyme4kt = "1.5.0" versions = "0.54.0" +zoomimage = "1.6.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -57,6 +58,7 @@ room-testing = { module = "androidx.room:room-testing", version.ref = "room" } sketch-animated-webp = { module = "io.github.panpf.sketch4:sketch-animated-webp", version.ref = "sketch" } sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" } tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" } +zoomimage-compose-sketch4 = { module = "io.github.panpf.zoomimage:zoomimage-compose-sketch4", version.ref = "zoomimage" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }