From f6a760371aba4536bcb9113b4b8e4d9075bec72e Mon Sep 17 00:00:00 2001 From: zhong <2724770085@qq.com> Date: Fri, 24 Oct 2025 18:27:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=B0=E9=97=BB=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E9=A1=B5;=E4=BF=AE=E6=94=B9=E5=BA=95=E9=83=A8?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E6=A0=8F=E8=83=8C=E6=99=AF=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/aiosman/ravenow/ui/index/Index.kt | 2 +- .../ravenow/ui/index/tabs/moment/Moment.kt | 3 +- .../index/tabs/moment/tabs/news/NewsScreen.kt | 337 ++++++++++++++++++ app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-zh/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt index 3c5f91f..ef7ae7d 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/Index.kt @@ -276,7 +276,7 @@ fun IndexScreen() { bottomBar = { NavigationBar( modifier = Modifier.height(58.dp + navigationBarHeight), - containerColor = AppColors.background + containerColor = AppColors.secondaryBackground ) { item.forEachIndexed { idx, it -> val isSelected = model.tabIndex == idx diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/Moment.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/Moment.kt index f1b3827..6cb0026 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/Moment.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/Moment.kt @@ -47,6 +47,7 @@ import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.Dynamic import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.Explore import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentsList import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList +import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.NewsScreen import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel import com.aiosman.ravenow.ui.modifiers.noRippleClickable import kotlinx.coroutines.launch @@ -251,7 +252,7 @@ fun MomentsList() { } 5 -> { // 新闻页面 - + NewsScreen() } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt new file mode 100644 index 0000000..08eb59b --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt @@ -0,0 +1,337 @@ +package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.ravenow.LocalAppTheme +import com.aiosman.ravenow.R +import com.aiosman.ravenow.entity.MomentEntity +import com.aiosman.ravenow.exp.timeAgo +import com.aiosman.ravenow.ui.composables.CustomAsyncImage +import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.DynamicViewModel + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun NewsScreen() { + val model = DynamicViewModel + val moments = model.moments + val AppColors = LocalAppTheme.current + val context = LocalContext.current + + // 下拉刷新状态 + val state = rememberPullRefreshState(model.refreshing, onRefresh = { + model.refreshPager(pullRefresh = true) + }) + + // 列表状态 + val listState = rememberLazyListState() + + // 用于跟踪是否已经触发过加载更多 + var hasTriggeredLoadMore by remember { mutableStateOf(false) } + + // 监听滚动到底部 + val reachedBottom by remember { + derivedStateOf { + val layoutInfo = listState.layoutInfo + val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull() + val totalItems = layoutInfo.totalItemsCount + + if (lastVisibleItem == null || totalItems == 0) { + false + } else { + val isLastItemVisible = lastVisibleItem.index >= totalItems - 2 + isLastItemVisible && !hasTriggeredLoadMore + } + } + } + + // 加载更多数据 + LaunchedEffect(reachedBottom) { + if (reachedBottom) { + hasTriggeredLoadMore = true + model.loadMore() + } + } + + // 初始化加载数据 + LaunchedEffect(Unit) { + model.refreshPager() + } + + // 监听数据变化,重置加载状态 + LaunchedEffect(moments.size) { + if (moments.size > 0) { + kotlinx.coroutines.delay(500) + hasTriggeredLoadMore = false + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(AppColors.background) + ) { + Box(Modifier.pullRefresh(state)) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState + ) { + items( + moments.size, + key = { idx -> idx } + ) { idx -> + // 处理下标越界 + if (idx < 0 || idx >= moments.size) return@items + val momentItem = moments[idx] ?: return@items + + NewsItem( + moment = momentItem, + modifier = Modifier.fillMaxWidth() + ) + } + } + PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter)) + } + } +} + +//单个新闻项 +@Composable +fun NewsItem( + moment: MomentEntity, + modifier: Modifier = Modifier +) { + val AppColors = LocalAppTheme.current + val context = LocalContext.current + + Column( + modifier = modifier + .fillMaxWidth() + .height(700.dp) + .background(AppColors.background) + .padding(vertical = 8.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column { + // 新闻图片 + Box( + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + .padding(horizontal = 16.dp) + ) { + if (moment.images.isNotEmpty()) { + CustomAsyncImage( + context = context, + imageUrl = moment.images[0].thumbnail, + contentDescription = "新闻图片", + contentScale = ContentScale.Crop, + blurHash = moment.images[0].blurHash, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + ) + } else { + Image( + painter = androidx.compose.ui.res.painterResource(id = R.drawable.default_moment_img), + contentDescription = "默认图片", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // 新闻标题 + Text( + text = moment.nickname, // 暂时使用用户名作为标题 + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = AppColors.text, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 新闻内容 + Text( + text = moment.momentTextContent, // 使用动态内容作为新闻内容 + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + fontSize = 14.sp, + color = AppColors.text, + lineHeight = 20.sp + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 新闻信息 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // 来源和时间 + Text( + text = "${moment.nickname} • ${moment.time.timeAgo(context)}", + fontSize = 12.sp, + color = AppColors.secondaryText + ) + + // 查看全文 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.read_full_article), + fontSize = 12.sp, + color = AppColors.text + ) + Spacer(modifier = Modifier.width(4.dp)) + Image( + painter = androidx.compose.ui.res.painterResource(id = R.drawable.rider_pro_nav_search), + contentDescription = "箭头", + modifier = Modifier.size(12.dp), + colorFilter = ColorFilter.tint(AppColors.text) + ) + } + } + } + + // 互动栏 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 50.dp), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + // 点赞 + NewsActionButton( + icon = R.drawable.rider_pro_moment_like, + count = moment.likeCount.toString(), + isActive = moment.liked + ) + + // 评论 + NewsActionButton( + icon = R.mipmap.icon_comment, + count = moment.commentCount.toString(), + isActive = false + ) + + // 收藏 + NewsActionButton( + icon = R.mipmap.icon_collect, + count = moment.favoriteCount.toString(), + isActive = moment.isFavorite + ) + + // 分享 + NewsActionButton( + icon = R.mipmap.icon_share, + count = "", + isActive = false, + text = stringResource(R.string.share), + textSize = 8.sp + ) + } + } +} + +// 互动栏按钮 +@Composable +fun NewsActionButton( + icon: Int, + count: String, + isActive: Boolean, + modifier: Modifier = Modifier, + text: String? = null, + textSize: androidx.compose.ui.unit.TextUnit = 12.sp +) { + val AppColors = LocalAppTheme.current + + Row( + modifier = modifier + .width(60.dp) + .background( + color = AppColors.secondaryBackground, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Image( + painter = androidx.compose.ui.res.painterResource(id = icon), + contentDescription = "操作图标", + modifier = Modifier.size(16.dp), + colorFilter = ColorFilter.tint( + if (isActive) AppColors.background else AppColors.text + ) + ) + + if (count.isNotEmpty()) { + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = count, + fontSize = 12.sp, + color = if (isActive) AppColors.background else AppColors.text + ) + } + if (text != null) { + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = text, + fontSize = textSize, + color = AppColors.text + ) + } + } +} diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7b7524b..6c8e612 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -193,6 +193,7 @@ 発見 パスワードは%1$d文字を超えることはできません ブロック + 全文を読む diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 3ef7e5e..1aa274d 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -196,6 +196,7 @@ 密码不能超过 %1$d 个字符 人正在热聊… 拉黑 + 查看全文 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7eb604d..3b44081 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,6 +192,7 @@ Discover Password cannot exceed %1$d characters Block + Read full article Create