Merge pull request #45 from Kevinlinpr/zhong_1

登录界面UI调整;新增新闻评论
This commit is contained in:
2025-10-28 18:45:22 +08:00
committed by GitHub
19 changed files with 616 additions and 154 deletions

View File

@@ -32,6 +32,21 @@ data class Moment(
val time: String,
@SerializedName("isFollowed")
val isFollowed: Boolean,
// 新闻相关字段
@SerializedName("isNews")
val isNews: Boolean = false,
@SerializedName("newsTitle")
val newsTitle: String? = null,
@SerializedName("newsUrl")
val newsUrl: String? = null,
@SerializedName("newsSource")
val newsSource: String? = null,
@SerializedName("newsCategory")
val newsCategory: String? = null,
@SerializedName("newsLanguage")
val newsLanguage: String? = null,
@SerializedName("newsContent")
val newsContent: String? = null,
) {
fun toMomentItem(): MomentEntity {
return MomentEntity(
@@ -60,6 +75,14 @@ data class Moment(
authorId = user.id.toInt(),
liked = isLiked,
isFavorite = isFavorite,
// 新闻相关字段
isNews = isNews,
newsTitle = newsTitle ?: "",
newsUrl = newsUrl ?: "",
newsSource = newsSource ?: "",
newsCategory = newsCategory ?: "",
newsLanguage = newsLanguage ?: "",
newsContent = newsContent ?: ""
)
}
}

View File

@@ -527,6 +527,7 @@ interface RaveNowAPI {
@Query("trend") trend: String? = null,
@Query("favouriteUserId") favouriteUserId: Int? = null,
@Query("explore") explore: String? = null,
@Query("newsFilter") newsFilter: String? = null,
): Response<ListContainer<Moment>>
@Multipart

View File

@@ -299,12 +299,21 @@ data class MomentEntity(
// 关联动态
var relMoment: MomentEntity? = null,
// 是否收藏
var isFavorite: Boolean = false
var isFavorite: Boolean = false,
// 新闻相关字段
val isNews: Boolean = false,
val newsTitle: String = "",
val newsUrl: String = "",
val newsSource: String = "",
val newsCategory: String = "",
val newsLanguage: String = "",
val newsContent: String = ""
)
class MomentLoaderExtraArgs(
val explore: Boolean? = false,
val timelineId: Int? = null,
val authorId : Int? = null
val authorId : Int? = null,
val newsOnly: Boolean? = null
)
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
override suspend fun fetchData(
@@ -317,7 +326,8 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
pageSize = pageSize,
explore = if (extra.explore == true) "true" else "",
timelineId = extra.timelineId,
authorId = extra.authorId
authorId = extra.authorId,
newsFilter = if (extra.newsOnly == true) "news_only" else ""
)
val data = result.body()?.let {
ListContainer(
@@ -355,6 +365,18 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
onListChanged?.invoke(this.list)
}
fun updateCommentCount(id: Int, delta: Int) {
this.list = this.list.map { momentItem ->
if (momentItem.id == id) {
val newCount = (momentItem.commentCount + delta).coerceAtLeast(0)
momentItem.copy(commentCount = newCount)
} else {
momentItem
}
}.toMutableList()
onListChanged?.invoke(this.list)
}
fun removeMoment(id: Int) {
this.list = this.list.filter { it.id != id }.toMutableList()
onListChanged?.invoke(this.list)

View File

@@ -51,7 +51,8 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable
fun EditCommentBottomModal(
replyComment: CommentEntity? = null,
onSend: (String) -> Unit = {}
autoFocus: Boolean = false,
onSend: (String) -> Unit = {},
) {
val AppColors = LocalAppTheme.current
var text by remember { mutableStateOf("") }
@@ -59,8 +60,10 @@ fun EditCommentBottomModal(
val focusRequester = remember { FocusRequester() }
val context = LocalContext.current
LaunchedEffect(Unit) {
focusRequester.requestFocus()
LaunchedEffect(autoFocus) {
if (autoFocus) {
focusRequester.requestFocus()
}
}
Column(

View File

@@ -72,9 +72,12 @@ open class BaseMomentModel :ViewModel(){
}
suspend fun onAddComment(id: Int) {
// val currentPagingData = _momentsFlow.value
// updateCommentCount(id)
fun onAddComment(id: Int) {
momentLoader.updateCommentCount(id, +1)
}
fun onDeleteComment(id: Int) {
momentLoader.updateCommentCount(id, -1)
}
@@ -83,6 +86,7 @@ open class BaseMomentModel :ViewModel(){
fun onMomentFavoriteChangeEvent(event: MomentFavouriteChangeEvent) {
momentLoader.updateFavoriteCount(event.postId, event.isFavourite)
}
suspend fun favoriteMoment(id: Int) {
momentService.favoriteMoment(id)
momentLoader.updateFavoriteCount(id, true)

View File

@@ -0,0 +1,306 @@
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.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.data.CommentService
import com.aiosman.ravenow.data.CommentServiceImpl
import com.aiosman.ravenow.entity.CommentEntity
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
import com.aiosman.ravenow.ui.composables.debouncedClickable
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.post.CommentContent
import com.aiosman.ravenow.ui.post.CommentMenuModal
import com.aiosman.ravenow.ui.post.CommentsViewModel
import com.aiosman.ravenow.ui.post.OrderSelectionComponent
import kotlinx.coroutines.launch
class NewsCommentModalViewModel(
val postId: Int?
) : ViewModel() {
var commentsViewModel: CommentsViewModel = CommentsViewModel(postId.toString())
var commentService: CommentService = CommentServiceImpl()
init {
commentsViewModel.preTransit()
}
fun likeComment(commentId: Int) {
viewModelScope.launch {
commentsViewModel.likeComment(commentId)
}
}
fun unlikeComment(commentId: Int) {
viewModelScope.launch {
commentsViewModel.unlikeComment(commentId)
}
}
fun createComment(
content: String,
parentCommentId: Int? = null,
replyUserId: Int? = null,
replyCommentId: Int? = null
) {
viewModelScope.launch {
commentsViewModel.createComment(
content = content,
parentCommentId = parentCommentId,
replyUserId = replyUserId,
replyCommentId = replyCommentId
)
}
}
fun deleteComment(commentId: Int) {
commentsViewModel.deleteComment(commentId)
}
}
// 新闻评论弹窗
// @param postId 新闻帖子ID
// @param commentCount 评论数量
// @param onDismiss 关闭回调
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NewsCommentModal(
postId: Int? = null,
commentCount: Int = 0,
onDismiss: () -> Unit = {},
onCommentAdded: () -> Unit = {},
onCommentDeleted: () -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val debouncedNavigation = rememberDebouncedNavigation()
// 实时评论数状态
var currentCommentCount by remember { mutableStateOf(commentCount) }
val model = viewModel<NewsCommentModalViewModel>(
key = "NewsCommentModalViewModel_$postId",
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return NewsCommentModalViewModel(postId) as T
}
}
)
val commentViewModel = model.commentsViewModel
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
var showCommentMenu by remember { mutableStateOf(false) }
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
// 菜单弹窗
if (showCommentMenu) {
ModalBottomSheet(
onDismissRequest = {
showCommentMenu = false
},
containerColor = AppColors.background,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) {
CommentMenuModal(
onDeleteClick = {
showCommentMenu = false
contextComment?.let {
model.deleteComment(it.id)
onCommentDeleted()
currentCommentCount = (currentCommentCount - 1).coerceAtLeast(0)
}
},
commentEntity = contextComment,
onCloseClick = {
showCommentMenu = false
},
isSelf = AppState.UserId?.toLong() == contextComment?.author,
onLikeClick = {
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
showCommentMenu = false
contextComment?.let {
if (it.liked) {
model.unlikeComment(it.id)
} else {
model.likeComment(it.id)
}
}
}
},
onReplyClick = {
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
showCommentMenu = false
replyComment = contextComment
}
}
)
}
}
Column(
modifier = Modifier.background(AppColors.background)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${currentCommentCount}条评论",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text
)
// 排序选择
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
OrderSelectionComponent {
commentViewModel.order = it
commentViewModel.reloadComment()
}
}
}
// 评论列表
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Box(
modifier = Modifier.fillMaxWidth()
) {
LazyColumn {
item {
CommentContent(
viewModel = commentViewModel,
onLongClick = { comment ->
showCommentMenu = true
contextComment = comment
},
onReply = { parentComment, _, _, _ ->
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
replyComment = parentComment
}
}
)
}
}
}
}
// 底部输入栏
Column(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.background)
) {
HorizontalDivider(color = AppColors.inputBackground)
EditCommentBottomModal(
replyComment = replyComment,
autoFocus = false
) {
if (replyComment != null) {
if (replyComment?.parentCommentId != null) {
// 第三级评论
model.createComment(
content = it,
parentCommentId = replyComment?.parentCommentId,
replyUserId = replyComment?.author?.toInt(),
replyCommentId = replyComment?.id
)
} else {
// 子级评论
model.createComment(
content = it,
parentCommentId = replyComment?.id,
replyCommentId = replyComment?.id
)
}
} else {
// 顶级评论
model.createComment(content = it)
}
replyComment = null
onCommentAdded()
currentCommentCount++
}
Spacer(modifier = Modifier.height(navBarHeight))
}
}
}

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -10,24 +11,27 @@ 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.statusBars
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.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
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.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
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.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -39,56 +43,42 @@ 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 androidx.compose.ui.platform.LocalConfiguration
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.exp.timeAgo
import com.aiosman.ravenow.exp.formatPostTime2
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.DynamicViewModel
import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.NewsViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun NewsScreen() {
val model = DynamicViewModel
val model = NewsViewModel
val moments = model.moments
val AppColors = LocalAppTheme.current
val context = LocalContext.current
val navController = LocalNavController.current
val scope = rememberCoroutineScope()
// 下拉刷新状态
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(pullRefresh = true)
})
// 评论弹窗状态
var showCommentModal by remember { mutableStateOf(false) }
var selectedMoment by remember { mutableStateOf<MomentEntity?>(null) }
// 垂直翻页状态
val pagerState = rememberPagerState(pageCount = { moments.size })
// 列表状态
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
// 防抖器
val likeDebouncer = rememberDebouncer()
val favoriteDebouncer = rememberDebouncer()
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()
@@ -96,9 +86,13 @@ fun NewsScreen() {
// 监听数据变化,重置加载状态
LaunchedEffect(moments.size) {
if (moments.size > 0) {
kotlinx.coroutines.delay(500)
hasTriggeredLoadMore = false
// 当数据增加时如果接近列表末尾Pager会自动更新页数
}
// 当翻页接近末尾时加载更多
LaunchedEffect(pagerState.currentPage, moments.size) {
if (moments.isNotEmpty() && pagerState.currentPage >= moments.size - 2) {
model.loadMore()
}
}
@@ -107,26 +101,96 @@ fun NewsScreen() {
.fillMaxSize()
.background(AppColors.background)
) {
Box(Modifier.pullRefresh(state)) {
LazyColumn(
if (moments.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
state = listState
contentAlignment = Alignment.Center
) {
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()
)
}
Text(text = "暂无新闻内容", color = AppColors.text, fontSize = 16.sp)
}
} else {
VerticalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
val momentItem = moments.getOrNull(page) ?: return@VerticalPager
NewsItem(
moment = momentItem,
modifier = Modifier.fillMaxSize(),
onCommentClick = {
selectedMoment = momentItem
showCommentModal = true
},
onLikeClick = {
likeDebouncer {
// 检查游客模式
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
}
}
},
onFavoriteClick = {
favoriteDebouncer {
// 检查游客模式
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
}
}
}
)
}
}
// 评论弹窗
if (showCommentModal && selectedMoment != null) {
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.67f // 三分之二高度
ModalBottomSheet(
onDismissRequest = {
showCommentModal = false
},
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight),
containerColor = AppColors.background,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
) {
NewsCommentModal(
postId = selectedMoment?.id,
commentCount = selectedMoment?.commentCount ?: 0,
onDismiss = {
showCommentModal = false
},
onCommentAdded = {
selectedMoment?.id?.let { model.onAddComment(it) }
},
onCommentDeleted = {
selectedMoment?.id?.let { model.onDeleteComment(it) }
}
)
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}
@@ -135,20 +199,26 @@ fun NewsScreen() {
@Composable
fun NewsItem(
moment: MomentEntity,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onCommentClick: () -> Unit = {},
onLikeClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
Column(
modifier = modifier
.fillMaxWidth()
.height(700.dp)
.fillMaxSize()
.background(AppColors.background)
.padding(vertical = 8.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = 30.dp)
) {
// 新闻图片
Box(
modifier = Modifier
@@ -183,7 +253,7 @@ fun NewsItem(
// 新闻标题
Text(
text = moment.nickname, // 暂时使用用户名作为标题
text = if (moment.newsTitle.isNotEmpty()) moment.newsTitle else moment.nickname,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
@@ -196,15 +266,17 @@ fun NewsItem(
Spacer(modifier = Modifier.height(16.dp))
// 新闻内容
// 新闻内容(超出使用省略号)
Text(
text = moment.momentTextContent, // 使用动态内容作为新闻内容
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
fontSize = 14.sp,
color = AppColors.text,
lineHeight = 20.sp
lineHeight = 20.sp,
maxLines = 6,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(16.dp))
@@ -217,11 +289,16 @@ fun NewsItem(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// 来源和时间
// 来源和时间(显示月份与具体时间)
Text(
text = "${moment.nickname}${moment.time.timeAgo(context)}",
text = if (moment.newsSource.isNotEmpty()) "${moment.newsSource}${moment.time.formatPostTime2()}" else "${moment.nickname}${moment.time.formatPostTime2()}",
fontSize = 12.sp,
color = AppColors.secondaryText
color = AppColors.secondaryText,
modifier = Modifier
.weight(1f)
.padding(end = 8.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
// 查看全文
@@ -249,28 +326,31 @@ fun NewsItem(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 50.dp),
.padding(bottom = 25.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// 点赞
NewsActionButton(
icon = R.drawable.rider_pro_moment_like,
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
count = moment.likeCount.toString(),
isActive = moment.liked
isActive = moment.liked,
modifier = Modifier.noRippleClickable { onLikeClick() }
)
// 评论
NewsActionButton(
icon = R.mipmap.icon_comment,
count = moment.commentCount.toString(),
isActive = false
isActive = false,
modifier = Modifier.noRippleClickable { onCommentClick() }
)
// 收藏
NewsActionButton(
icon = R.mipmap.icon_collect,
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
count = moment.favoriteCount.toString(),
isActive = moment.isFavorite
isActive = moment.isFavorite,
modifier = Modifier.noRippleClickable { onFavoriteClick() }
)
// 分享
@@ -311,10 +391,7 @@ fun NewsActionButton(
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
)
modifier = Modifier.size(16.dp)
)
if (count.isNotEmpty()) {
@@ -322,7 +399,7 @@ fun NewsActionButton(
Text(
text = count,
fontSize = 12.sp,
color = if (isActive) AppColors.background else AppColors.text
color = AppColors.text
)
}
if (text != null) {

View File

@@ -0,0 +1,18 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
object NewsViewModel : BaseMomentModel() {
override fun extraArgs(): MomentLoaderExtraArgs {
// 只拉取新闻
return MomentLoaderExtraArgs(
explore = false,
timelineId = null,
authorId = null,
newsOnly = true
)
}
}

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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
@@ -47,66 +48,72 @@ fun SelfProfileAction(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(10.dp))
.background(AppColors.nonActive)
.padding(horizontal = 5.dp, vertical = 12.dp)
.width(60.dp).height(25.dp)
.clip(RoundedCornerShape(12.dp))
.background(androidx.compose.ui.graphics.Color(0x229284BD))
.noRippleClickable {
editProfileDebouncer {
onEditProfile()
}
}
) {
Image(
painter = painterResource(id = R.mipmap.fill_and_sign),
contentDescription = "",
modifier = Modifier.size(12.dp),
colorFilter = ColorFilter.tint(androidx.compose.ui.graphics.Color(0xFF9284BD))
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(R.string.edit_profile),
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
fontSize = 12.sp,
fontWeight = FontWeight.W600,
color = androidx.compose.ui.graphics.Color(0xFF9284BD),
)
}
// 预留按钮位置
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(10.dp))
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
}
) {
Text(
text = "",
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
)
}
// 分享按钮
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(10.dp))
.background(AppColors.nonActive)
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
shareDebouncer {
// TODO: 添加分享逻辑
}
}
) {
Text(
text = stringResource(R.string.share),
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
)
}
// // 预留按钮位置
// Row(
// verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.Center,
// modifier = Modifier
// .weight(1f)
// .clip(RoundedCornerShape(10.dp))
// .padding(horizontal = 16.dp, vertical = 12.dp)
// .noRippleClickable {
//
// }
// ) {
// Text(
// text = "",
// fontSize = 14.sp,
// fontWeight = FontWeight.W900,
// color = AppColors.text,
// )
// }
//
// // 分享按钮
// Row(
// verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.Center,
// modifier = Modifier
// .weight(1f)
// .clip(RoundedCornerShape(10.dp))
// .background(AppColors.nonActive)
// .padding(horizontal = 16.dp, vertical = 12.dp)
// .noRippleClickable {
// shareDebouncer {
// // TODO: 添加分享逻辑
// }
// }
// ) {
// Text(
// text = stringResource(R.string.share),
// fontSize = 14.sp,
// fontWeight = FontWeight.W900,
// color = AppColors.text,
// )
// }
// // Rave Premium 按钮(右侧)
// Row(

View File

@@ -359,30 +359,31 @@ fun LoginPage() {
NavigationRoute.EmailSignUp.route,
)
}
//苹果登录tab
//谷歌登录tab
Spacer(modifier = Modifier.height(16.dp))
ActionButton(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.border(
width = 1.dp,
width = 1.5.dp,
color = if (AppState.darkMode) Color.White else Color.Black,
shape = RoundedCornerShape(24.dp)
),
text = stringResource(R.string.sign_in_with_apple),
text = stringResource(R.string.sign_in_with_google),
color = if (AppState.darkMode) Color.White else Color.Black,
backgroundColor = if (AppState.darkMode) Color.Black else Color.White,
leading = {
Image(
painter = painterResource(id = R.mipmap.apple_logo_medium),
contentDescription = "Apple",
modifier = Modifier.size(36.dp),
colorFilter = ColorFilter.tint(if (AppState.darkMode) Color.White else Color.Black)
painter = painterResource(id = R.mipmap.rider_pro_signup_google),
contentDescription = "Google",
modifier = Modifier.size(18.dp),
)
},
expandText = true,
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 10.dp)
) {
googleLogin()
}
//登录tab

View File

@@ -211,7 +211,7 @@ fun SignupScreen() {
)
Spacer(modifier = Modifier.width(8.dp))
},
text = stringResource(R.string.sign_in_with_apple),
text = stringResource(R.string.sign_in_with_google),
) {
googleLogin()

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

View File

@@ -33,7 +33,7 @@
<string name="text_hint_password">パスワードを入力してください</string>
<string name="sign_up_upper">サインアップ</string>
<string name="sign_in_with_email">メールで接続</string>
<string name="sign_in_with_apple">Appleで接続</string>
<string name="sign_in_with_google">Googleで接続</string>
<string name="back_upper">戻る</string>
<string name="text_hint_confirm_password">パスワードを再入力してください</string>
<string name="login_confirm_password_label">パスワードの確認</string>

View File

@@ -32,7 +32,7 @@
<string name="text_hint_password">输入密码</string>
<string name="sign_up_upper">注册</string>
<string name="sign_in_with_email">使用邮箱注册</string>
<string name="sign_in_with_apple">使用Apple登录</string>
<string name="sign_in_with_google">使用Google账号登录</string>
<string name="back_upper">返回</string>
<string name="text_hint_confirm_password">再次输入密码</string>
<string name="login_confirm_password_label">再次输入密码</string>
@@ -48,7 +48,7 @@
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
<string name="empty_my_post_title">还没有发布任何动态</string>
<string name="empty_my_post_content">发布一个动态吧</string>
<string name="edit_profile">编辑资料</string>
<string name="edit_profile">编辑</string>
<string name="share">分享</string>
<string name="logout">登出</string>
<string name="change_password">修改密码</string>

View File

@@ -32,7 +32,7 @@
<string name="text_hint_password">Enter your password</string>
<string name="sign_up_upper">Sign Up</string>
<string name="sign_in_with_email">Connect with Email</string>
<string name="sign_in_with_apple">Continue with Apple</string>
<string name="sign_in_with_google">Continue with Google</string>
<string name="back_upper">BACK</string>
<string name="text_hint_confirm_password">Enter your password again</string>
<string name="login_confirm_password_label">Confirm password</string>