feat: Implement image detail view and text/image mode switching
This commit introduces a dedicated image detail screen and integrates it with the post detail view, allowing users to switch between a text-focused layout and an immersive, image-centric one.
**Key Changes:**
* **feat(ImageDetailScreen):**
* Added a new `ImageDetailScreen` for a full-screen, immersive image viewing experience.
* Implemented a `HorizontalPager` to allow swiping between multiple images.
* Included zoom (pinch-to-zoom) and double-tap-to-zoom functionality for images.
* Overlaid post information (author, content snippet) on a blurred background, which can be swiped up to switch back to the text detail view.
* The background is a blurred version of the current image, creating an ambient effect.
* **feat(Navigation):**
* Created a `PostDetailContainer` to manage the animated transition between `PostDetailScreen` (text mode) and `ImageDetailScreen` (image mode).
* Updated the navigation route for `PostDetail` to accept a `mode` parameter (`text` or `image`) to handle deep linking directly into the image view.
* Clicking an image in the feed or post details now navigates to the image mode, preserving the shared element transition.
* **refactor(PostDetail):**
* Modified `PostCardItem` and `PostDetailScreen` to pass both `postId` and the image `index` on image clicks.
* Refactored the image click handler to trigger the navigation to the new image detail mode.
This commit is contained in:
parent
6a1bc7ad97
commit
93f95bf9c3
@ -6,8 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
@ -21,6 +20,7 @@ import com.qingshuige.tangyuan.ui.components.PageLevel
|
|||||||
import com.qingshuige.tangyuan.ui.components.TangyuanBottomAppBar
|
import com.qingshuige.tangyuan.ui.components.TangyuanBottomAppBar
|
||||||
import com.qingshuige.tangyuan.ui.components.TangyuanTopBar
|
import com.qingshuige.tangyuan.ui.components.TangyuanTopBar
|
||||||
import com.qingshuige.tangyuan.ui.screens.PostDetailScreen
|
import com.qingshuige.tangyuan.ui.screens.PostDetailScreen
|
||||||
|
import com.qingshuige.tangyuan.ui.screens.ImageDetailScreen
|
||||||
import com.qingshuige.tangyuan.ui.screens.TalkScreen
|
import com.qingshuige.tangyuan.ui.screens.TalkScreen
|
||||||
import com.qingshuige.tangyuan.ui.screens.LoginScreen
|
import com.qingshuige.tangyuan.ui.screens.LoginScreen
|
||||||
|
|
||||||
@ -40,6 +40,9 @@ fun App() {
|
|||||||
onPostClick = { postId ->
|
onPostClick = { postId ->
|
||||||
navController.navigate(Screen.PostDetail.createRoute(postId))
|
navController.navigate(Screen.PostDetail.createRoute(postId))
|
||||||
},
|
},
|
||||||
|
onImageClick = { postId, imageIndex ->
|
||||||
|
navController.navigate(Screen.PostDetail.createRoute(postId, "image") + "?imageIndex=$imageIndex")
|
||||||
|
},
|
||||||
sharedTransitionScope = this@SharedTransitionLayout,
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
animatedContentScope = this@composable
|
animatedContentScope = this@composable
|
||||||
)
|
)
|
||||||
@ -78,21 +81,24 @@ fun App() {
|
|||||||
LoginScreen(navController = navController)
|
LoginScreen(navController = navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 帖子详情页 - 只有共享元素动画,无页面切换动画
|
// 帖子详情页 - 统一容器管理两种模式
|
||||||
composable(
|
composable(
|
||||||
route = Screen.PostDetail.route,
|
route = Screen.PostDetail.route,
|
||||||
arguments = listOf(navArgument("postId") { type = NavType.IntType })
|
arguments = listOf(
|
||||||
|
navArgument("postId") { type = NavType.IntType },
|
||||||
|
navArgument("mode") { type = NavType.StringType; defaultValue = "text" }
|
||||||
|
)
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
val postId = backStackEntry.arguments?.getInt("postId") ?: 0
|
val postId = backStackEntry.arguments?.getInt("postId") ?: 0
|
||||||
PostDetailScreen(
|
val initialMode = backStackEntry.arguments?.getString("mode") ?: "text"
|
||||||
|
|
||||||
|
PostDetailContainer(
|
||||||
postId = postId,
|
postId = postId,
|
||||||
|
initialMode = initialMode,
|
||||||
onBackClick = { navController.popBackStack() },
|
onBackClick = { navController.popBackStack() },
|
||||||
onAuthorClick = { authorId ->
|
onAuthorClick = { authorId ->
|
||||||
// TODO: 导航到用户详情页
|
// TODO: 导航到用户详情页
|
||||||
},
|
},
|
||||||
onImageClick = { imageUuid ->
|
|
||||||
// TODO: 导航到图片查看页
|
|
||||||
},
|
|
||||||
sharedTransitionScope = this@SharedTransitionLayout,
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
animatedContentScope = this@composable
|
animatedContentScope = this@composable
|
||||||
)
|
)
|
||||||
@ -101,11 +107,99 @@ fun App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帖子详情容器 - 统一管理文字和图片两种模式
|
||||||
|
* 保持与PostCard的共享元素动画,同时支持内部模式切换动画
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
|
@Composable
|
||||||
|
fun PostDetailContainer(
|
||||||
|
postId: Int,
|
||||||
|
initialMode: String,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
onAuthorClick: (Int) -> Unit,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope
|
||||||
|
) {
|
||||||
|
// 本地状态管理模式切换
|
||||||
|
var currentMode by remember { mutableStateOf(initialMode) }
|
||||||
|
var imageIndex by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
// 为模式切换创建内部AnimatedContent
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = currentMode,
|
||||||
|
transitionSpec = {
|
||||||
|
// 使用滑动动画让切换更自然
|
||||||
|
when {
|
||||||
|
targetState == "image" && initialState == "text" -> {
|
||||||
|
slideInVertically(
|
||||||
|
initialOffsetY = { it },
|
||||||
|
animationSpec = tween(400)
|
||||||
|
) togetherWith slideOutVertically(
|
||||||
|
targetOffsetY = { -it },
|
||||||
|
animationSpec = tween(400)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
targetState == "text" && initialState == "image" -> {
|
||||||
|
slideInVertically(
|
||||||
|
initialOffsetY = { -it },
|
||||||
|
animationSpec = tween(400)
|
||||||
|
) togetherWith slideOutVertically(
|
||||||
|
targetOffsetY = { it },
|
||||||
|
animationSpec = tween(400)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = "detail_mode_switch"
|
||||||
|
) { mode ->
|
||||||
|
when (mode) {
|
||||||
|
"image" -> {
|
||||||
|
ImageDetailScreen(
|
||||||
|
postId = postId,
|
||||||
|
initialImageIndex = imageIndex,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
onAuthorClick = onAuthorClick,
|
||||||
|
onSwitchToTextMode = {
|
||||||
|
currentMode = "text"
|
||||||
|
},
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = this@AnimatedContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
PostDetailScreen(
|
||||||
|
postId = postId,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
onAuthorClick = onAuthorClick,
|
||||||
|
onImageClick = { _, selectedImageIndex ->
|
||||||
|
imageIndex = selectedImageIndex
|
||||||
|
currentMode = "image"
|
||||||
|
},
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = if (mode == initialMode) {
|
||||||
|
// 如果是初始模式,使用外部的animatedContentScope来保持与PostCard的共享动画
|
||||||
|
animatedContentScope
|
||||||
|
} else {
|
||||||
|
// 如果是切换后的模式,使用内部的AnimatedContent scope
|
||||||
|
this@AnimatedContent
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainFlow(
|
fun MainFlow(
|
||||||
onLoginClick: () -> Unit,
|
onLoginClick: () -> Unit,
|
||||||
onPostClick: (Int) -> Unit,
|
onPostClick: (Int) -> Unit,
|
||||||
|
onImageClick: (Int, Int) -> Unit = { _, _ -> },
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
) {
|
) {
|
||||||
@ -151,6 +245,7 @@ fun MainFlow(
|
|||||||
onAuthorClick = { authorId ->
|
onAuthorClick = { authorId ->
|
||||||
// TODO: 导航到用户详情页
|
// TODO: 导航到用户详情页
|
||||||
},
|
},
|
||||||
|
onImageClick = onImageClick,
|
||||||
sharedTransitionScope = sharedTransitionScope,
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
animatedContentScope = animatedContentScope
|
animatedContentScope = animatedContentScope
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,7 +6,10 @@ sealed class Screen(val route: String, val title: String) {
|
|||||||
object Topic : Screen("topic", "侃一侃")
|
object Topic : Screen("topic", "侃一侃")
|
||||||
object Message : Screen("message", "消息")
|
object Message : Screen("message", "消息")
|
||||||
object User : Screen("settings", "我的")
|
object User : Screen("settings", "我的")
|
||||||
object PostDetail : Screen("post_detail/{postId}", "帖子详情") {
|
object PostDetail : Screen("post_detail/{postId}/{mode}", "帖子详情") {
|
||||||
fun createRoute(postId: Int) = "post_detail/$postId"
|
fun createRoute(postId: Int, mode: String = "text") = "post_detail/$postId/$mode"
|
||||||
|
}
|
||||||
|
object ImageDetail : Screen("image_detail/{postId}/{imageIndex}", "图片详情") {
|
||||||
|
fun createRoute(postId: Int, imageIndex: Int) = "image_detail/$postId/$imageIndex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,6 +132,7 @@ fun PostCardItem(
|
|||||||
onShareClick: (Int) -> Unit = {},
|
onShareClick: (Int) -> Unit = {},
|
||||||
onBookmarkClick: (Int) -> Unit = {},
|
onBookmarkClick: (Int) -> Unit = {},
|
||||||
onMoreClick: (Int) -> Unit = {},
|
onMoreClick: (Int) -> Unit = {},
|
||||||
|
onImageClick: (Int, Int) -> Unit = { _, _ -> },
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
@ -185,7 +186,8 @@ fun PostCardItem(
|
|||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
PostCardImages(
|
PostCardImages(
|
||||||
imageUUIDs = postCard.imageUUIDs,
|
imageUUIDs = postCard.imageUUIDs,
|
||||||
onImageClick = { /* TODO: 查看大图 */ }
|
postId = postCard.postId,
|
||||||
|
onImageClick = onImageClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +301,8 @@ private fun PostCardContent(postCard: PostCard) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun PostCardImages(
|
private fun PostCardImages(
|
||||||
imageUUIDs: List<String>,
|
imageUUIDs: List<String>,
|
||||||
onImageClick: (String) -> Unit
|
postId: Int,
|
||||||
|
onImageClick: (Int, Int) -> Unit
|
||||||
) {
|
) {
|
||||||
when (imageUUIDs.size) {
|
when (imageUUIDs.size) {
|
||||||
1 -> {
|
1 -> {
|
||||||
@ -312,7 +315,7 @@ private fun PostCardImages(
|
|||||||
.height(200.dp)
|
.height(200.dp)
|
||||||
.clip(MaterialTheme.shapes.medium),
|
.clip(MaterialTheme.shapes.medium),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
onClick = { onImageClick(imageUUIDs[0]) }
|
onClick = { onImageClick(postId, 0) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +324,7 @@ private fun PostCardImages(
|
|||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
imageUUIDs.forEach { uuid ->
|
imageUUIDs.forEachIndexed { index, uuid ->
|
||||||
ShimmerAsyncImage(
|
ShimmerAsyncImage(
|
||||||
imageUrl = "${TangyuanApplication.instance.bizDomain}images/$uuid.jpg",
|
imageUrl = "${TangyuanApplication.instance.bizDomain}images/$uuid.jpg",
|
||||||
contentDescription = "文章图片",
|
contentDescription = "文章图片",
|
||||||
@ -330,7 +333,7 @@ private fun PostCardImages(
|
|||||||
.height(120.dp)
|
.height(120.dp)
|
||||||
.clip(MaterialTheme.shapes.medium),
|
.clip(MaterialTheme.shapes.medium),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
onClick = { onImageClick(uuid) }
|
onClick = { onImageClick(postId, index) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,7 +344,7 @@ private fun PostCardImages(
|
|||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
imageUUIDs.forEach { uuid ->
|
imageUUIDs.forEachIndexed { index, uuid ->
|
||||||
ShimmerAsyncImage(
|
ShimmerAsyncImage(
|
||||||
imageUrl = "${TangyuanApplication.instance.bizDomain}images/$uuid.jpg",
|
imageUrl = "${TangyuanApplication.instance.bizDomain}images/$uuid.jpg",
|
||||||
contentDescription = "文章图片",
|
contentDescription = "文章图片",
|
||||||
@ -350,7 +353,7 @@ private fun PostCardImages(
|
|||||||
.height(100.dp)
|
.height(100.dp)
|
||||||
.clip(MaterialTheme.shapes.medium),
|
.clip(MaterialTheme.shapes.medium),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
onClick = { onImageClick(uuid) }
|
onClick = { onImageClick(postId, index) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,562 @@
|
|||||||
|
package com.qingshuige.tangyuan.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.gestures.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.BlurredEdgeTreatment
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.qingshuige.tangyuan.TangyuanApplication
|
||||||
|
import com.qingshuige.tangyuan.model.PostCard
|
||||||
|
import com.qingshuige.tangyuan.ui.theme.LiteraryFontFamily
|
||||||
|
import com.qingshuige.tangyuan.ui.theme.TangyuanGeneralFontFamily
|
||||||
|
import com.qingshuige.tangyuan.ui.theme.TangyuanShapes
|
||||||
|
import com.qingshuige.tangyuan.utils.withPanguSpacing
|
||||||
|
import com.qingshuige.tangyuan.viewmodel.PostDetailViewModel
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片详情页面 - 以图片为主的展示界面
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ImageDetailScreen(
|
||||||
|
postId: Int,
|
||||||
|
initialImageIndex: Int = 0,
|
||||||
|
onBackClick: () -> Unit = {},
|
||||||
|
onAuthorClick: (Int) -> Unit = {},
|
||||||
|
onSwitchToTextMode: () -> Unit = {},
|
||||||
|
viewModel: PostDetailViewModel = hiltViewModel(),
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
// 加载帖子详情
|
||||||
|
LaunchedEffect(postId) {
|
||||||
|
viewModel.loadPostDetail(postId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当有帖子数据时才显示内容
|
||||||
|
state.postCard?.let { postCard ->
|
||||||
|
val imageUUIDs = postCard.imageUUIDs
|
||||||
|
val validImageIndex = initialImageIndex.coerceIn(0, imageUUIDs.size - 1)
|
||||||
|
val pagerState = rememberPagerState(
|
||||||
|
initialPage = validImageIndex,
|
||||||
|
pageCount = { imageUUIDs.size }
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
// 背景图片(模糊效果)
|
||||||
|
BackgroundBlurredImage(
|
||||||
|
imageUUIDs = imageUUIDs,
|
||||||
|
currentPage = pagerState.currentPage
|
||||||
|
)
|
||||||
|
|
||||||
|
// 主要内容
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
// 顶部导航栏
|
||||||
|
ImageDetailTopBar(
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
currentIndex = pagerState.currentPage + 1,
|
||||||
|
totalCount = imageUUIDs.size
|
||||||
|
)
|
||||||
|
|
||||||
|
// 图片轮播区域
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (imageUUIDs.isNotEmpty()) {
|
||||||
|
ImagePager(
|
||||||
|
imageUUIDs = imageUUIDs,
|
||||||
|
postId = postCard.postId,
|
||||||
|
pagerState = pagerState,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = animatedContentScope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部内容区域(模糊遮罩)
|
||||||
|
BottomContentOverlay(
|
||||||
|
postCard = postCard,
|
||||||
|
onAuthorClick = onAuthorClick,
|
||||||
|
onSwitchToTextMode = onSwitchToTextMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
if (state.isLoading && state.postCard == null) {
|
||||||
|
LoadingContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
state.error?.let { error ->
|
||||||
|
ErrorContent(
|
||||||
|
message = error,
|
||||||
|
onRetry = {
|
||||||
|
viewModel.clearError()
|
||||||
|
viewModel.loadPostDetail(postId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景模糊图片
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun BackgroundBlurredImage(
|
||||||
|
imageUUIDs: List<String>,
|
||||||
|
currentPage: Int
|
||||||
|
) {
|
||||||
|
if (imageUUIDs.isNotEmpty() && currentPage < imageUUIDs.size) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data("${TangyuanApplication.instance.bizDomain}images/${imageUUIDs[currentPage]}.jpg")
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.blur(radius = 20.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
alpha = 0.3f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渐变遮罩
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Black.copy(alpha = 0.4f),
|
||||||
|
Color.Black.copy(alpha = 0.1f),
|
||||||
|
Color.Black.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶部导航栏
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ImageDetailTopBar(
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
currentIndex: Int,
|
||||||
|
totalCount: Int
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "$currentIndex / $totalCount",
|
||||||
|
fontFamily = TangyuanGeneralFontFamily,
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBackClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = "返回",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片轮播组件
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ImagePager(
|
||||||
|
imageUUIDs: List<String>,
|
||||||
|
postId: Int,
|
||||||
|
pagerState: PagerState,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
|
) {
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) { page ->
|
||||||
|
ZoomableImage(
|
||||||
|
imageUrl = "${TangyuanApplication.instance.bizDomain}images/${imageUUIDs[page]}.jpg",
|
||||||
|
imageUuid = imageUUIDs[page],
|
||||||
|
postId = postId,
|
||||||
|
contentDescription = "图片 ${page + 1}",
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = animatedContentScope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可缩放的图片组件
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ZoomableImage(
|
||||||
|
imageUrl: String,
|
||||||
|
imageUuid: String,
|
||||||
|
postId: Int,
|
||||||
|
contentDescription: String,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
|
) {
|
||||||
|
var scale by remember { mutableFloatStateOf(1f) }
|
||||||
|
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||||
|
|
||||||
|
val transformableState = rememberTransformableState { zoomChange, offsetChange, _ ->
|
||||||
|
scale = (scale * zoomChange).coerceIn(1f, 5f)
|
||||||
|
val maxX = (scale - 1) * 300
|
||||||
|
val maxY = (scale - 1) * 300
|
||||||
|
offset = Offset(
|
||||||
|
x = (offset.x + offsetChange.x).coerceIn(-maxX, maxX),
|
||||||
|
y = (offset.y + offsetChange.y).coerceIn(-maxY, maxY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(imageUrl)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.let { mod ->
|
||||||
|
if (sharedTransitionScope != null && animatedContentScope != null) {
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
mod.sharedElement(
|
||||||
|
rememberSharedContentState(key = "post_card_$postId"),
|
||||||
|
animatedVisibilityScope = animatedContentScope,
|
||||||
|
boundsTransform = { _, _ ->
|
||||||
|
tween(durationMillis = 500)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else mod
|
||||||
|
}
|
||||||
|
.graphicsLayer(
|
||||||
|
scaleX = scale,
|
||||||
|
scaleY = scale,
|
||||||
|
translationX = offset.x,
|
||||||
|
translationY = offset.y
|
||||||
|
)
|
||||||
|
.transformable(state = transformableState)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onDoubleTap = {
|
||||||
|
scale = if (scale > 1f) 1f else 2f
|
||||||
|
offset = Offset.Zero
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底部内容遮罩
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun BottomContentOverlay(
|
||||||
|
postCard: PostCard,
|
||||||
|
onAuthorClick: (Int) -> Unit,
|
||||||
|
onSwitchToTextMode: () -> Unit
|
||||||
|
) {
|
||||||
|
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||||
|
val swipeThreshold = -100f // 上滑超过100px触发切换
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.offset(y = offsetY.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectDragGestures(
|
||||||
|
onDragEnd = {
|
||||||
|
if (offsetY < swipeThreshold) {
|
||||||
|
onSwitchToTextMode()
|
||||||
|
} else {
|
||||||
|
offsetY = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { _, dragAmount ->
|
||||||
|
offsetY = (offsetY + dragAmount.y).coerceAtMost(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp)
|
||||||
|
) {
|
||||||
|
// 作者信息
|
||||||
|
PostAuthorInfo(
|
||||||
|
postCard = postCard,
|
||||||
|
onAuthorClick = onAuthorClick
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 文章内容
|
||||||
|
Text(
|
||||||
|
text = postCard.textContent.withPanguSpacing(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
lineHeight = 22.sp
|
||||||
|
),
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 4,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 分类和时间
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = Color.White.copy(alpha = 0.2f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = postCard.categoryName,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = postCard.getTimeDisplayText(),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontFamily = TangyuanGeneralFontFamily,
|
||||||
|
color = Color.White.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
// 上滑提示 - 放在最下面居中
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "上滑查看详情",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontFamily = TangyuanGeneralFontFamily,
|
||||||
|
color = Color.White.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作者信息组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun PostAuthorInfo(
|
||||||
|
postCard: PostCard,
|
||||||
|
onAuthorClick: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.clickable { onAuthorClick(postCard.authorId) }
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data("${TangyuanApplication.instance.bizDomain}images/${postCard.authorAvatar}.jpg")
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
contentDescription = "${postCard.authorName}的头像",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = postCard.authorName.withPanguSpacing(),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontFamily = TangyuanGeneralFontFamily,
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
if (postCard.authorBio.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
text = postCard.authorBio.withPanguSpacing(),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White.copy(alpha = 0.8f),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载状态组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun LoadingContent() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "正在加载图片...",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误状态组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ErrorContent(
|
||||||
|
message: String,
|
||||||
|
onRetry: () -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.padding(32.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ErrorOutline,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "加载失败",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = message,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White.copy(alpha = 0.8f),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onRetry,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White.copy(alpha = 0.2f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Refresh,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "重试",
|
||||||
|
fontFamily = LiteraryFontFamily,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,7 +51,7 @@ fun PostDetailScreen(
|
|||||||
postId: Int,
|
postId: Int,
|
||||||
onBackClick: () -> Unit = {},
|
onBackClick: () -> Unit = {},
|
||||||
onAuthorClick: (Int) -> Unit = {},
|
onAuthorClick: (Int) -> Unit = {},
|
||||||
onImageClick: (String) -> Unit = {},
|
onImageClick: (Int, Int) -> Unit = { _, _ -> },
|
||||||
viewModel: PostDetailViewModel = hiltViewModel(),
|
viewModel: PostDetailViewModel = hiltViewModel(),
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
@ -181,7 +181,7 @@ private fun PostDetailContent(
|
|||||||
isError: Boolean,
|
isError: Boolean,
|
||||||
errorMessage: String?,
|
errorMessage: String?,
|
||||||
onAuthorClick: (Int) -> Unit,
|
onAuthorClick: (Int) -> Unit,
|
||||||
onImageClick: (String) -> Unit,
|
onImageClick: (Int, Int) -> Unit,
|
||||||
onReplyToComment: (CommentCard) -> Unit,
|
onReplyToComment: (CommentCard) -> Unit,
|
||||||
onDeleteComment: (Int) -> Unit,
|
onDeleteComment: (Int) -> Unit,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
@ -256,7 +256,7 @@ private fun PostDetailContent(
|
|||||||
private fun PostDetailCard(
|
private fun PostDetailCard(
|
||||||
postCard: PostCard,
|
postCard: PostCard,
|
||||||
onAuthorClick: (Int) -> Unit,
|
onAuthorClick: (Int) -> Unit,
|
||||||
onImageClick: (String) -> Unit,
|
onImageClick: (Int, Int) -> Unit,
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
) {
|
) {
|
||||||
@ -311,6 +311,7 @@ private fun PostDetailCard(
|
|||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
PostDetailImages(
|
PostDetailImages(
|
||||||
imageUUIDs = postCard.imageUUIDs,
|
imageUUIDs = postCard.imageUUIDs,
|
||||||
|
postId = postCard.postId,
|
||||||
onImageClick = onImageClick
|
onImageClick = onImageClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -403,7 +404,8 @@ private fun PostDetailHeader(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun PostDetailImages(
|
private fun PostDetailImages(
|
||||||
imageUUIDs: List<String>,
|
imageUUIDs: List<String>,
|
||||||
onImageClick: (String) -> Unit
|
postId: Int,
|
||||||
|
onImageClick: (Int, Int) -> Unit
|
||||||
) {
|
) {
|
||||||
when (imageUUIDs.size) {
|
when (imageUUIDs.size) {
|
||||||
1 -> {
|
1 -> {
|
||||||
@ -416,7 +418,7 @@ private fun PostDetailImages(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.clickable { onImageClick(imageUUIDs[0]) },
|
.clickable { onImageClick(postId, 0) },
|
||||||
contentScale = ContentScale.FillWidth
|
contentScale = ContentScale.FillWidth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -425,10 +427,10 @@ private fun PostDetailImages(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
contentPadding = PaddingValues(horizontal = 4.dp)
|
contentPadding = PaddingValues(horizontal = 4.dp)
|
||||||
) {
|
) {
|
||||||
items(imageUUIDs) { uuid ->
|
items(imageUUIDs.size) { index ->
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data("${TangyuanApplication.instance.bizDomain}images/$uuid.jpg")
|
.data("${TangyuanApplication.instance.bizDomain}images/${imageUUIDs[index]}.jpg")
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.build(),
|
.build(),
|
||||||
contentDescription = "文章图片",
|
contentDescription = "文章图片",
|
||||||
@ -436,7 +438,7 @@ private fun PostDetailImages(
|
|||||||
.width(200.dp)
|
.width(200.dp)
|
||||||
.height(150.dp)
|
.height(150.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.clickable { onImageClick(uuid) },
|
.clickable { onImageClick(postId, index) },
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import kotlinx.coroutines.launch
|
|||||||
fun TalkScreen(
|
fun TalkScreen(
|
||||||
onPostClick: (Int) -> Unit = {},
|
onPostClick: (Int) -> Unit = {},
|
||||||
onAuthorClick: (Int) -> Unit = {},
|
onAuthorClick: (Int) -> Unit = {},
|
||||||
|
onImageClick: (Int, Int) -> Unit = { _, _ -> },
|
||||||
viewModel: TalkViewModel = hiltViewModel(),
|
viewModel: TalkViewModel = hiltViewModel(),
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
@ -101,6 +102,7 @@ fun TalkScreen(
|
|||||||
onMoreClick = { postId ->
|
onMoreClick = { postId ->
|
||||||
// TODO: 显示更多操作菜单
|
// TODO: 显示更多操作菜单
|
||||||
},
|
},
|
||||||
|
onImageClick = onImageClick,
|
||||||
onErrorDismiss = viewModel::clearError,
|
onErrorDismiss = viewModel::clearError,
|
||||||
sharedTransitionScope = sharedTransitionScope,
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
animatedContentScope = animatedContentScope
|
animatedContentScope = animatedContentScope
|
||||||
@ -129,6 +131,7 @@ private fun PostList(
|
|||||||
onShareClick: (Int) -> Unit,
|
onShareClick: (Int) -> Unit,
|
||||||
onBookmarkClick: (Int) -> Unit,
|
onBookmarkClick: (Int) -> Unit,
|
||||||
onMoreClick: (Int) -> Unit,
|
onMoreClick: (Int) -> Unit,
|
||||||
|
onImageClick: (Int, Int) -> Unit,
|
||||||
onErrorDismiss: () -> Unit,
|
onErrorDismiss: () -> Unit,
|
||||||
sharedTransitionScope: SharedTransitionScope? = null,
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
animatedContentScope: AnimatedContentScope? = null
|
animatedContentScope: AnimatedContentScope? = null
|
||||||
@ -151,6 +154,7 @@ private fun PostList(
|
|||||||
onShareClick = onShareClick,
|
onShareClick = onShareClick,
|
||||||
onBookmarkClick = onBookmarkClick,
|
onBookmarkClick = onBookmarkClick,
|
||||||
onMoreClick = onMoreClick,
|
onMoreClick = onMoreClick,
|
||||||
|
onImageClick = onImageClick,
|
||||||
sharedTransitionScope = sharedTransitionScope,
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
animatedContentScope = animatedContentScope
|
animatedContentScope = animatedContentScope
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user