5 Commits

Author SHA1 Message Date
234587afc9 缁х画瀹屽杽鍔熻兘鏇存柊 2025-11-10 20:30:31 +08:00
b855acd8d3 Merge remote-tracking branch 'origin/main' into feat/pr-20251104-154907-clean 2025-11-10 19:48:35 +08:00
391f841f45 Merge pull request #59 from Kevinlinpr/atm2
feat: 新增短视频功能
2025-11-10 19:47:34 +08:00
2ed5639cbc feat: 新增短视频功能
- 新增短视频信息流页面,支持上下滑动切换视频。
- 实现视频播放、暂停、加载、空状态及错误处理等基础功能。
- 在视频页面中集成点赞、评论、收藏等互动操作。
- 后端接口新增 `videoFilter` 参数,用于仅获取包含视频的动态。
- 扩展了 `MomentEntity` 和相关数据模型,以支持视频数据。
- 将短视频页面集成到动态(Moment)Tab中。
2025-11-10 17:55:33 +08:00
2cbd2a975f Reapply "增加英文日语翻译 修改编辑资料界面无法更改星座mbit"
This reverts commit a057f7f7fd.
2025-11-10 15:26:31 +08:00
35 changed files with 1329 additions and 377 deletions

View File

@@ -4,6 +4,7 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.entity.MomentEntity import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentImageEntity import com.aiosman.ravenow.entity.MomentImageEntity
import com.aiosman.ravenow.entity.MomentVideoEntity
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import java.io.File import java.io.File
@@ -12,8 +13,12 @@ data class Moment(
val id: Long, val id: Long,
@SerializedName("textContent") @SerializedName("textContent")
val textContent: String, val textContent: String,
@SerializedName("url")
val url: String? = null,
@SerializedName("images") @SerializedName("images")
val images: List<Image>, val images: List<Image>? = null,
@SerializedName("videos")
val videos: List<Video>? = null,
@SerializedName("user") @SerializedName("user")
val user: User, val user: User,
@SerializedName("likeCount") @SerializedName("likeCount")
@@ -24,7 +29,7 @@ data class Moment(
val favoriteCount: Long, val favoriteCount: Long,
@SerializedName("isFavorite") @SerializedName("isFavorite")
val isFavorite: Boolean, val isFavorite: Boolean,
@SerializedName("shareCount") @SerializedName("isCommented")
val isCommented: Boolean, val isCommented: Boolean,
@SerializedName("commentCount") @SerializedName("commentCount")
val commentCount: Long, val commentCount: Long,
@@ -47,6 +52,14 @@ data class Moment(
val newsLanguage: String? = null, val newsLanguage: String? = null,
@SerializedName("newsContent") @SerializedName("newsContent")
val newsContent: String? = null, val newsContent: String? = null,
@SerializedName("hasFullText")
val hasFullText: Boolean = false,
@SerializedName("summary")
val summary: String? = null,
@SerializedName("publishedAt")
val publishedAt: String? = null,
@SerializedName("imageCached")
val imageCached: Boolean = false
) { ) {
fun toMomentItem(): MomentEntity { fun toMomentItem(): MomentEntity {
return MomentEntity( return MomentEntity(
@@ -62,7 +75,7 @@ data class Moment(
commentCount = commentCount.toInt(), commentCount = commentCount.toInt(),
shareCount = 0, shareCount = 0,
favoriteCount = favoriteCount.toInt(), favoriteCount = favoriteCount.toInt(),
images = images.map { images = images?.map {
MomentImageEntity( MomentImageEntity(
url = "${ApiClient.BASE_SERVER}${it.url}", url = "${ApiClient.BASE_SERVER}${it.url}",
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}", thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
@@ -71,10 +84,28 @@ data class Moment(
width = it.width, width = it.width,
height = it.height height = it.height
) )
}, } ?: emptyList(),
authorId = user.id.toInt(), authorId = user.id.toInt(),
liked = isLiked, liked = isLiked,
isFavorite = isFavorite, isFavorite = isFavorite,
url = url,
videos = videos?.map {
MomentVideoEntity(
id = it.id,
url = "${ApiClient.BASE_SERVER}${it.url}",
originalUrl = it.originalUrl,
directUrl = it.directUrl,
thumbnailUrl = it.thumbnailUrl?.let { thumb -> "${ApiClient.BASE_SERVER}$thumb" },
thumbnailDirectUrl = it.thumbnailDirectUrl,
duration = it.duration,
width = it.width,
height = it.height,
size = it.size,
format = it.format,
bitrate = it.bitrate,
frameRate = it.frameRate
)
},
// 新闻相关字段 // 新闻相关字段
isNews = isNews, isNews = isNews,
newsTitle = newsTitle ?: "", newsTitle = newsTitle ?: "",
@@ -82,7 +113,11 @@ data class Moment(
newsSource = newsSource ?: "", newsSource = newsSource ?: "",
newsCategory = newsCategory ?: "", newsCategory = newsCategory ?: "",
newsLanguage = newsLanguage ?: "", newsLanguage = newsLanguage ?: "",
newsContent = newsContent ?: "" newsContent = newsContent ?: "",
hasFullText = hasFullText,
summary = summary,
publishedAt = publishedAt,
imageCached = imageCached
) )
} }
} }
@@ -92,8 +127,26 @@ data class Image(
val id: Long, val id: Long,
@SerializedName("url") @SerializedName("url")
val url: String, val url: String,
@SerializedName("original_url")
val originalUrl: String? = null,
@SerializedName("directUrl")
val directUrl: String? = null,
@SerializedName("thumbnail") @SerializedName("thumbnail")
val thumbnail: String, val thumbnail: String,
@SerializedName("thumbnailDirectUrl")
val thumbnailDirectUrl: String? = null,
@SerializedName("small")
val small: String? = null,
@SerializedName("smallDirectUrl")
val smallDirectUrl: String? = null,
@SerializedName("medium")
val medium: String? = null,
@SerializedName("mediumDirectUrl")
val mediumDirectUrl: String? = null,
@SerializedName("large")
val large: String? = null,
@SerializedName("largeDirectUrl")
val largeDirectUrl: String? = null,
@SerializedName("blurHash") @SerializedName("blurHash")
val blurHash: String?, val blurHash: String?,
@SerializedName("width") @SerializedName("width")
@@ -102,13 +155,68 @@ data class Image(
val height: Int? val height: Int?
) )
data class Video(
@SerializedName("id")
val id: Long,
@SerializedName("url")
val url: String,
@SerializedName("original_url")
val originalUrl: String? = null,
@SerializedName("directUrl")
val directUrl: String? = null,
@SerializedName("thumbnailUrl")
val thumbnailUrl: String? = null,
@SerializedName("thumbnailDirectUrl")
val thumbnailDirectUrl: String? = null,
@SerializedName("duration")
val duration: Int? = null,
@SerializedName("width")
val width: Int? = null,
@SerializedName("height")
val height: Int? = null,
@SerializedName("size")
val size: Long? = null,
@SerializedName("format")
val format: String? = null,
@SerializedName("bitrate")
val bitrate: Int? = null,
@SerializedName("frameRate")
val frameRate: String? = null
)
data class User( data class User(
@SerializedName("id") @SerializedName("id")
val id: Long, val id: Long,
@SerializedName("nickName") @SerializedName("nickName")
val nickName: String, val nickName: String,
@SerializedName("avatar") @SerializedName("avatar")
val avatar: String val avatar: String,
@SerializedName("avatarMedium")
val avatarMedium: String? = null,
@SerializedName("avatarLarge")
val avatarLarge: String? = null,
@SerializedName("originAvatar")
val originAvatar: String? = null,
@SerializedName("avatarDirectUrl")
val avatarDirectUrl: String? = null,
@SerializedName("avatarMediumDirectUrl")
val avatarMediumDirectUrl: String? = null,
@SerializedName("avatarLargeDirectUrl")
val avatarLargeDirectUrl: String? = null,
@SerializedName("aiAccount")
val aiAccount: Boolean = false,
@SerializedName("aiRoleAvatar")
val aiRoleAvatar: String? = null,
@SerializedName("aiRoleAvatarMedium")
val aiRoleAvatarMedium: String? = null,
@SerializedName("aiRoleAvatarLarge")
val aiRoleAvatarLarge: String? = null,
@SerializedName("aiRoleAvatarDirectUrl")
val aiRoleAvatarDirectUrl: String? = null,
@SerializedName("aiRoleAvatarMediumDirectUrl")
val aiRoleAvatarMediumDirectUrl: String? = null,
@SerializedName("aiRoleAvatarLargeDirectUrl")
val aiRoleAvatarLargeDirectUrl: String? = null
) )
data class UploadImage( data class UploadImage(

View File

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

View File

@@ -260,6 +260,38 @@ data class MomentImageEntity(
var height: Int? = null var height: Int? = null
) )
/**
* 动态视频
*/
data class MomentVideoEntity(
// 视频ID
val id: Long,
// 视频URL
val url: String,
// 原始文件名
val originalUrl: String? = null,
// 直接访问URL
val directUrl: String? = null,
// 视频缩略图URL
val thumbnailUrl: String? = null,
// 视频缩略图直接访问URL
val thumbnailDirectUrl: String? = null,
// 视频时长(秒)
val duration: Int? = null,
// 宽度
val width: Int? = null,
// 高度
val height: Int? = null,
// 文件大小(字节)
val size: Long? = null,
// 视频格式
val format: String? = null,
// 视频比特率kbps
val bitrate: Int? = null,
// 帧率
val frameRate: String? = null
)
/** /**
* 动态 * 动态
*/ */
@@ -300,6 +332,10 @@ data class MomentEntity(
var relMoment: MomentEntity? = null, var relMoment: MomentEntity? = null,
// 是否收藏 // 是否收藏
var isFavorite: Boolean = false, var isFavorite: Boolean = false,
// 外部链接
val url: String? = null,
// 动态视频列表
val videos: List<MomentVideoEntity>? = null,
// 新闻相关字段 // 新闻相关字段
val isNews: Boolean = false, val isNews: Boolean = false,
val newsTitle: String = "", val newsTitle: String = "",
@@ -307,13 +343,22 @@ data class MomentEntity(
val newsSource: String = "", val newsSource: String = "",
val newsCategory: String = "", val newsCategory: String = "",
val newsLanguage: String = "", val newsLanguage: String = "",
val newsContent: String = "" val newsContent: String = "",
// 是否已获取完整正文
val hasFullText: Boolean = false,
// 新闻摘要
val summary: String? = null,
// 新闻发布时间
val publishedAt: String? = null,
// 是否已缓存图片
val imageCached: Boolean = false
) )
class MomentLoaderExtraArgs( class MomentLoaderExtraArgs(
val explore: Boolean? = false, val explore: Boolean? = false,
val timelineId: Int? = null, val timelineId: Int? = null,
val authorId : Int? = null, val authorId : Int? = null,
val newsOnly: Boolean? = null val newsOnly: Boolean? = null,
val videoOnly: Boolean? = null
) )
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() { class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
override suspend fun fetchData( override suspend fun fetchData(
@@ -327,7 +372,8 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
explore = if (extra.explore == true) "true" else "", explore = if (extra.explore == true) "true" else "",
timelineId = extra.timelineId, timelineId = extra.timelineId,
authorId = extra.authorId, authorId = extra.authorId,
newsFilter = if (extra.newsOnly == true) "news_only" else "" newsFilter = if (extra.newsOnly == true) "news_only" else "",
videoFilter = if (extra.videoOnly == true) "video_only" else ""
) )
val data = result.body()?.let { val data = result.body()?.let {
ListContainer( ListContainer(

View File

@@ -21,6 +21,8 @@ object AccountEditViewModel : ViewModel() {
var name by mutableStateOf("") var name by mutableStateOf("")
var bio by mutableStateOf("") var bio by mutableStateOf("")
var imageUrl by mutableStateOf<Uri?>(null) var imageUrl by mutableStateOf<Uri?>(null)
var bannerImageUrl by mutableStateOf<Uri?>(null)
var bannerFile by mutableStateOf<File?>(null)
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
var croppedBitmap by mutableStateOf<Bitmap?>(null) var croppedBitmap by mutableStateOf<Bitmap?>(null)
@@ -82,6 +84,30 @@ object AccountEditViewModel : ViewModel() {
it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream()) it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
UploadImage(file, "avatar.jpg", "", "jpg") UploadImage(file, "avatar.jpg", "", "jpg")
} }
// 处理背景图更新
val newBanner = bannerImageUrl?.let { uri ->
bannerFile?.let { file ->
val cursor = context.contentResolver.query(uri, null, null, null, null)
var uploadBanner: UploadImage? = null
cursor?.use { cur ->
val columnIndex = cur.getColumnIndex("_display_name")
if (cur.moveToFirst() && columnIndex != -1) {
val displayName = cur.getString(columnIndex)
val extension = displayName.substringAfterLast(".")
Log.d("AccountEditViewModel", "Banner file name: $displayName, extension: $extension")
uploadBanner = UploadImage(file, displayName, uri.toString(), extension)
} else {
// 如果无法获取文件名,使用默认值
val displayName = "banner.jpg"
val extension = "jpg"
uploadBanner = UploadImage(file, displayName, uri.toString(), extension)
}
}
uploadBanner
}
}
// 去除换行符,确保昵称和个人简介不包含换行 // 去除换行符,确保昵称和个人简介不包含换行
val cleanName = name.trim().replace("\n", "").replace("\r", "") val cleanName = name.trim().replace("\n", "").replace("\r", "")
val cleanBio = bio.trim().replace("\n", "").replace("\r", "") val cleanBio = bio.trim().replace("\n", "").replace("\r", "")
@@ -89,7 +115,7 @@ object AccountEditViewModel : ViewModel() {
val newName = if (cleanName == profile?.nickName) null else cleanName val newName = if (cleanName == profile?.nickName) null else cleanName
accountService.updateProfile( accountService.updateProfile(
avatar = newAvatar, avatar = newAvatar,
banner = null, banner = newBanner,
nickName = newName, nickName = newName,
bio = cleanBio bio = cleanBio
) )
@@ -100,6 +126,9 @@ object AccountEditViewModel : ViewModel() {
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiac) com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiac)
} }
} catch (_: Exception) { } } catch (_: Exception) { }
// 清除背景图状态
bannerImageUrl = null
bannerFile = null
// 刷新用户资料 // 刷新用户资料
reloadProfile() reloadProfile()
// 刷新个人资料页面的用户资料 // 刷新个人资料页面的用户资料
@@ -116,6 +145,8 @@ object AccountEditViewModel : ViewModel() {
name = "" name = ""
bio = "" bio = ""
imageUrl = null imageUrl = null
bannerImageUrl = null
bannerFile = null
croppedBitmap = null croppedBitmap = null
isUpdating = false isUpdating = false
isLoading = false isLoading = false

View File

@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@@ -80,6 +81,10 @@ fun MbtiSelectScreen() {
isSelected = mbti == currentMbti, isSelected = mbti == currentMbti,
onClick = { onClick = {
model.mbti = mbti model.mbti = mbti
// 立即保存到本地存储,确保选择后立即生效
AppState.UserId?.let { uid ->
com.aiosman.ravenow.AppStore.setUserMbti(uid, mbti)
}
navController.navigateUp() navController.navigateUp()
} }
) )

View File

@@ -29,24 +29,55 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
// 星座列表 // 星座资源ID列表
val ZODIAC_SIGNS = listOf( val ZODIAC_SIGN_RES_IDS = listOf(
"白羊座", "金牛座", "双子座", "巨蟹座", R.string.zodiac_aries,
"狮子座", "处女座", "天秤座", "天蝎座", R.string.zodiac_taurus,
"射手座", "摩羯座", "水瓶座", "双鱼座" R.string.zodiac_gemini,
R.string.zodiac_cancer,
R.string.zodiac_leo,
R.string.zodiac_virgo,
R.string.zodiac_libra,
R.string.zodiac_scorpio,
R.string.zodiac_sagittarius,
R.string.zodiac_capricorn,
R.string.zodiac_aquarius,
R.string.zodiac_pisces
) )
/**
* 根据存储的星座字符串可能是任何语言找到对应的资源ID
* 如果找不到返回null
*/
@Composable
fun findZodiacResId(storedZodiac: String?): Int? {
if (storedZodiac.isNullOrEmpty()) return null
// 尝试在所有语言的资源中查找匹配
ZODIAC_SIGN_RES_IDS.forEachIndexed { index, resId ->
val zodiacText = stringResource(resId)
if (zodiacText == storedZodiac) {
return resId
}
}
// 如果找不到精确匹配尝试通过资源ID索引查找兼容旧数据
// 这里可以根据需要添加更多兼容逻辑
return null
}
@Composable @Composable
fun ZodiacSelectScreen() { fun ZodiacSelectScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
val model = AccountEditViewModel val model = AccountEditViewModel
val currentZodiac = model.zodiac val currentZodiacResId = findZodiacResId(model.zodiac)
Column( Column(
modifier = Modifier modifier = Modifier
@@ -70,12 +101,20 @@ fun ZodiacSelectScreen() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp) contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) { ) {
items(ZODIAC_SIGNS) { zodiac -> items(ZODIAC_SIGN_RES_IDS.size) { index ->
val zodiacResId = ZODIAC_SIGN_RES_IDS[index]
val zodiacText = stringResource(zodiacResId)
ZodiacItem( ZodiacItem(
zodiac = zodiac, zodiac = zodiacText,
isSelected = zodiac == currentZodiac, zodiacResId = zodiacResId,
isSelected = zodiacResId == currentZodiacResId,
onClick = { onClick = {
model.zodiac = zodiac // 保存当前语言的星座文本
model.zodiac = zodiacText
// 立即保存到本地存储,确保选择后立即生效
AppState.UserId?.let { uid ->
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiacText)
}
navController.navigateUp() navController.navigateUp()
} }
) )
@@ -88,6 +127,7 @@ fun ZodiacSelectScreen() {
@Composable @Composable
fun ZodiacItem( fun ZodiacItem(
zodiac: String, zodiac: String,
zodiacResId: Int,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit onClick: () -> Unit
) { ) {

View File

@@ -72,6 +72,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
import android.widget.Toast
import java.io.File import java.io.File
/** /**
@@ -97,6 +98,10 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
quality = 100 quality = 100
) { uri, file -> ) { uri, file ->
// 处理选中的图片 // 处理选中的图片
// 保存到 ViewModel 中,等待保存时一起上传
model.bannerImageUrl = uri
model.bannerFile = file
// 如果提供了回调,也调用它(用于个人主页直接更新)
onUpdateBanner?.invoke(uri, file, context) onUpdateBanner?.invoke(uri, file, context)
} }
@@ -104,10 +109,21 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
// 去除换行符,确保昵称不包含换行 // 去除换行符,确保昵称不包含换行
val cleanValue = value.replace("\n", "").replace("\r", "") val cleanValue = value.replace("\n", "").replace("\r", "")
model.name = cleanValue model.name = cleanValue
// 实时验证,但不显示错误(只在保存时显示)
usernameError = when { usernameError = when {
cleanValue.trim().isEmpty() -> "昵称不能为空" cleanValue.trim().isEmpty() -> context.getString(R.string.error_nickname_empty)
cleanValue.length < 3 -> "昵称长度不能小于3" cleanValue.length < 3 -> context.getString(R.string.error_nickname_too_short)
cleanValue.length > 20 -> "昵称长度不能大于20" cleanValue.length > 20 -> context.getString(R.string.error_nickname_too_long)
else -> null
}
}
fun validateNickname(): String? {
val cleanValue = model.name.replace("\n", "").replace("\r", "")
return when {
cleanValue.trim().isEmpty() -> context.getString(R.string.error_nickname_empty)
cleanValue.length < 3 -> context.getString(R.string.error_nickname_too_short)
cleanValue.length > 20 -> context.getString(R.string.error_nickname_too_long)
else -> null else -> null
} }
} }
@@ -118,8 +134,17 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
// 去除换行符,确保个人简介不包含换行 // 去除换行符,确保个人简介不包含换行
val cleanValue = value.replace("\n", "").replace("\r", "") val cleanValue = value.replace("\n", "").replace("\r", "")
model.bio = cleanValue model.bio = cleanValue
// 实时验证,但不显示错误(只在保存时显示)
bioError = when { bioError = when {
cleanValue.length > 100 -> "个人简介长度不能大于100" cleanValue.length > 100 -> context.getString(R.string.error_bio_too_long)
else -> null
}
}
fun validateBio(): String? {
val cleanValue = model.bio.replace("\n", "").replace("\r", "")
return when {
cleanValue.length > 100 -> context.getString(R.string.error_bio_too_long)
else -> null else -> null
} }
} }
@@ -146,21 +171,21 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
model.reloadProfile() model.reloadProfile()
} }
// 设置状态栏为透明,使用浅色图标(因为顶部背景是深色图片) // 设置状态栏为透明,根据暗色模式决定图标颜色
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false) systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
} }
StatusBarMaskLayout( StatusBarMaskLayout(
modifier = Modifier.background(Color(0xFFFAF9FB)), modifier = Modifier.background(appColors.background),
darkIcons = false, // 浅色图标(白色),因为顶部背景是深 darkIcons = !AppState.darkMode, // 根据暗色模式决定图标颜
maskBoxBackgroundColor = Color.Transparent maskBoxBackgroundColor = Color.Transparent
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color(0xFFFAF9FB)) .background(appColors.background)
) { ) {
when { when {
model.isLoading -> { model.isLoading -> {
@@ -179,7 +204,8 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
// 顶部背景区域(圆角在底部,覆盖状态栏) // 顶部背景区域(圆角在底部,覆盖状态栏)
val banner = model.profile?.banner // 优先显示新选择的背景图,如果没有则显示原有的背景图
val banner = model.bannerImageUrl?.toString() ?: model.profile?.banner
val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
Box( Box(
modifier = Modifier modifier = Modifier
@@ -230,21 +256,13 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
) { ) {
// 更换封面图标 // 更换封面图标
Icon( Icon(
painter = painterResource( painter = painterResource(id = R.mipmap.fengm),
id = if (AppState.darkMode) {
// TODO: 添加更换封面暗色模式图标
R.mipmap.frame_4 // 临时占位,需替换为实际图标
} else {
// TODO: 添加更换封面亮色模式图标
R.mipmap.fengm // 临时占位,需替换为实际图标
}
),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
tint = Color.White tint = Color.White
) )
Text( Text(
text = "更换封面", text = stringResource(R.string.change_cover),
fontSize = 12.sp, fontSize = 12.sp,
color = Color.White color = Color.White
) )
@@ -288,7 +306,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
// 标题 // 标题
Text( Text(
text = "编辑资料", text = stringResource(R.string.edit_profile_info),
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = Color.White, color = Color.White,
@@ -318,7 +336,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
modifier = Modifier modifier = Modifier
.size(96.dp) .size(96.dp)
.clip(CircleShape) .clip(CircleShape)
.border(2.4.dp, Color(0xFFFAF9FB), CircleShape), .border(2.4.dp, appColors.background, CircleShape),
contentDescription = "", contentDescription = "",
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
@@ -338,15 +356,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
painter = painterResource( painter = painterResource(id = R.mipmap.bi),
id = if (AppState.darkMode) {
// TODO: 添加编辑头像暗色模式图标
R.mipmap.frame_4 // 临时占位,需替换为实际图标
} else {
// TODO: 添加编辑头像亮色模式图标
R.mipmap.bi // 临时占位,需替换为实际图标
}
),
contentDescription = "Edit Avatar", contentDescription = "Edit Avatar",
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
tint = Color.White tint = Color.White
@@ -365,7 +375,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
) { ) {
// 昵称输入框 // 昵称输入框
ProfileInfoCard( ProfileInfoCard(
label = "昵称", label = stringResource(R.string.nickname),
value = model.name, value = model.name,
placeholder = "Value", placeholder = "Value",
onValueChange = { onNicknameChange(it) }, onValueChange = { onNicknameChange(it) },
@@ -376,7 +386,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
// 个人简介输入框 // 个人简介输入框
ProfileInfoCard( ProfileInfoCard(
label = "个人简介", label = stringResource(R.string.personal_intro),
value = model.bio, value = model.bio,
placeholder = "Welcome to my fantiac word i will show you something about magic", placeholder = "Welcome to my fantiac word i will show you something about magic",
onValueChange = { onBioChange(it) }, onValueChange = { onBioChange(it) },
@@ -390,11 +400,11 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background(Color.White) .background(appColors.secondaryBackground)
) { ) {
// MBTI 类型 // MBTI 类型
ProfileSelectItem( ProfileSelectItem(
label = "MBTI 类型", label = stringResource(R.string.mbti_type),
value = model.mbti ?: "ENFP", value = model.mbti ?: "ENFP",
iconColor = Color(0xFF7C45ED), iconColor = Color(0xFF7C45ED),
iconResDark = null, // TODO: 添加MBTI暗色模式图标 iconResDark = null, // TODO: 添加MBTI暗色模式图标
@@ -411,14 +421,19 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(0.3.dp) .height(0.3.dp)
.background(Color(0x41413C43).copy(alpha = 0.2f)) .background(appColors.divider)
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) )
// 星座(使用当前图标) // 星座(使用当前图标)
ProfileSelectItem( ProfileSelectItem(
label = "星座", label = stringResource(R.string.zodiac),
value = model.zodiac ?: "白羊座", value = model.zodiac?.let { storedZodiac ->
// 尝试找到对应的资源ID并显示当前语言的文本
findZodiacResId(storedZodiac)?.let { resId ->
stringResource(resId)
} ?: storedZodiac // 如果找不到,显示原始存储的值
} ?: stringResource(R.string.zodiac_aries),
iconColor = Color(0xFFFFCC00), iconColor = Color(0xFFFFCC00),
iconResDark = R.mipmap.frame_4, // 星座暗色模式图标 iconResDark = R.mipmap.frame_4, // 星座暗色模式图标
iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标 iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标
@@ -448,10 +463,28 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
) )
) )
.debouncedClickable( .debouncedClickable(
enabled = validate() && !model.isUpdating, enabled = !model.isUpdating,
debounceTime = 1000L debounceTime = 1000L
) { ) {
if (validate() && !model.isUpdating) { if (model.isUpdating) return@debouncedClickable
// 点击保存时重新验证
val nicknameErrorMsg = validateNickname()
val bioErrorMsg = validateBio()
// 如果有错误,显示对应的错误提示
when {
nicknameErrorMsg != null -> {
Toast.makeText(context, nicknameErrorMsg, Toast.LENGTH_SHORT).show()
return@debouncedClickable
}
bioErrorMsg != null -> {
Toast.makeText(context, bioErrorMsg, Toast.LENGTH_SHORT).show()
return@debouncedClickable
}
}
// 验证通过,执行保存
model.viewModelScope.launch { model.viewModelScope.launch {
model.isUpdating = true model.isUpdating = true
model.updateUserProfile(context) model.updateUserProfile(context)
@@ -462,12 +495,11 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
model.isUpdating = false model.isUpdating = false
} }
} }
}
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = "保存", text = stringResource(R.string.save),
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color.White color = Color.White
@@ -483,7 +515,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = "加载用户资料失败,请重试", text = stringResource(R.string.error_load_profile_failed),
color = appColors.text color = appColors.text
) )
} }
@@ -505,13 +537,12 @@ fun ProfileInfoCard(
isMultiline: Boolean = false isMultiline: Boolean = false
) { ) {
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp个人简介66dp .height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp个人简介66dp
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background(Color.White), .background(appColors.secondaryBackground),
contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart
) { ) {
Row( Row(
@@ -526,7 +557,7 @@ fun ProfileInfoCard(
text = label, text = label,
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color.Black, color = appColors.text,
modifier = Modifier.width(100.dp) modifier = Modifier.width(100.dp)
) )
@@ -541,7 +572,7 @@ fun ProfileInfoCard(
text = placeholder, text = placeholder,
fontSize = if (isMultiline) 15.sp else 17.sp, fontSize = if (isMultiline) 15.sp else 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color(0x993C3C43), color = appColors.secondaryText,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
@@ -553,9 +584,9 @@ fun ProfileInfoCard(
textStyle = androidx.compose.ui.text.TextStyle( textStyle = androidx.compose.ui.text.TextStyle(
fontSize = if (isMultiline) 15.sp else 17.sp, fontSize = if (isMultiline) 15.sp else 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color.Black color = appColors.text
), ),
cursorBrush = SolidColor(Color.Black), cursorBrush = SolidColor(appColors.text),
maxLines = if (isMultiline) Int.MAX_VALUE else 1, maxLines = if (isMultiline) Int.MAX_VALUE else 1,
singleLine = !isMultiline singleLine = !isMultiline
) )
@@ -576,6 +607,7 @@ fun ProfileSelectItem(
iconResDark: Int? = null, iconResDark: Int? = null,
iconResLight: Int? = null iconResLight: Int? = null
) { ) {
val appColors = LocalAppTheme.current
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -593,7 +625,8 @@ fun ProfileSelectItem(
Icon( Icon(
painter = painterResource( painter = painterResource(
id = if (AppState.darkMode) { id = if (AppState.darkMode) {
iconResDark ?: R.mipmap.frame_4 // 使用传入的暗色模式图标,或默认占位 // 暗色模式下使用和亮色模式一样的图标
iconResLight ?: iconResDark ?: R.mipmap.naoz
} else { } else {
iconResLight ?: R.mipmap.naoz // 使用传入的亮色模式图标,或默认占位 iconResLight ?: R.mipmap.naoz // 使用传入的亮色模式图标,或默认占位
} }
@@ -607,7 +640,7 @@ fun ProfileSelectItem(
text = label, text = label,
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color.Black color = appColors.text
) )
} }
@@ -619,14 +652,14 @@ fun ProfileSelectItem(
text = value, text = value,
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = Color(0x993C3C43) color = appColors.secondaryText
) )
Icon( Icon(
imageVector = Icons.Default.ArrowForward, imageVector = Icons.Default.ArrowForward,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(8.dp), modifier = Modifier.size(8.dp),
tint = Color(0x4D3C3C43) tint = appColors.secondaryText
) )
} }
} }

View File

@@ -10,6 +10,7 @@ import androidx.compose.animation.togetherWith
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
@@ -36,6 +37,13 @@ fun AnimatedCounter(count: Int, modifier: Modifier = Modifier, fontSize: Int = 2
) )
} }
) { targetCount -> ) { targetCount ->
Text(text = "$targetCount", modifier = modifier, fontSize = fontSize.sp, color = AppColors.text) Text(
text = "$targetCount",
modifier = modifier,
fontSize = fontSize.sp,
color = AppColors.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
} }
} }

View File

@@ -426,7 +426,6 @@ fun MomentOperateBtn(count: String, content: @Composable () -> Unit) {
fontSize = 14, fontSize = 14,
modifier = Modifier modifier = Modifier
.padding(start = 7.dp) .padding(start = 7.dp)
.width(24.dp)
) )
} }
} }

View File

@@ -24,6 +24,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -131,17 +133,25 @@ fun FollowerListScreen(userId: Int) {
) )
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
androidx.compose.material.Text( androidx.compose.material.Text(
text = "还没有人关注你呢", text = stringResource(R.string.follower_empty_title),
color = appColors.text, color = appColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = "试着发信号出来,某人就会被吸引啦~", text = stringResource(R.string.follower_empty_subtitle),
color = appColors.text, color = appColors.text,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@@ -25,6 +25,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
@@ -122,17 +124,25 @@ fun FollowerNoticeScreen() {
) )
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp)) Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = "还没有人关注你呢", text = stringResource(R.string.follower_empty_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = "试着发信号出来,某人就会被吸引啦~", text = stringResource(R.string.follower_empty_subtitle),
color = AppColors.text, color = AppColors.text,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@@ -24,6 +24,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -133,17 +135,25 @@ fun FollowingListScreen(userId: Int) {
) )
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
androidx.compose.material.Text( androidx.compose.material.Text(
text = "还没有关注任何灵魂", text = stringResource(R.string.following_empty_title),
color = appColors.text, color = appColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = "探索一下,总有一个你想靠近的光点 ✨", text = stringResource(R.string.following_empty_subtitle),
color = appColors.secondaryText, color = appColors.secondaryText,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@@ -13,9 +13,6 @@ import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.AgentRuleQuota import com.aiosman.ravenow.data.api.AgentRuleQuota
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.AgentRuleQuota
import com.aiosman.ravenow.data.parseErrorResponse import com.aiosman.ravenow.data.parseErrorResponse
import com.aiosman.ravenow.entity.ChatNotification import com.aiosman.ravenow.entity.ChatNotification
import com.aiosman.ravenow.entity.GroupInfo import com.aiosman.ravenow.entity.GroupInfo

View File

@@ -519,14 +519,31 @@ fun SideMenuContent(
var messageNotificationEnabled by remember { mutableStateOf(true) } var messageNotificationEnabled by remember { mutableStateOf(true) }
var darkModeEnabled by remember { mutableStateOf(AppState.darkMode) } var darkModeEnabled by remember { mutableStateOf(AppState.darkMode) }
// 菜单背景色 #FAF9FB // 同步暗色模式状态
val menuBackgroundColor = Color(0xFFFAF9FB) LaunchedEffect(AppState.darkMode) {
darkModeEnabled = AppState.darkMode
}
// 菜单背景色 - 根据暗色模式适配
val menuBackgroundColor = if (darkModeEnabled) {
appColors.secondaryBackground // 暗色模式:深灰色
} else {
Color(0xFFFAF9FB) // 亮色模式:浅灰色
}
// 遮罩颜色 黑色透明度0.6 // 遮罩颜色 黑色透明度0.6
val overlayColor = Color.Black.copy(alpha = 0.6f) val overlayColor = Color.Black.copy(alpha = 0.6f)
// 卡片背景色 白色 // 卡片背景色 - 根据暗色模式适配
val cardBackgroundColor = Color.White val cardBackgroundColor = if (darkModeEnabled) {
// 跟随系统文字颜色 #979499 appColors.background // 暗色模式:深色背景
val followSystemTextColor = Color(0xFF979499) } else {
Color.White // 亮色模式:白色
}
// 文字颜色 - 根据暗色模式适配
val textColor = appColors.text
// 图标颜色 - 根据暗色模式适配
val iconColor = appColors.text
// 跟随系统文字颜色 - 根据暗色模式适配
val followSystemTextColor = appColors.secondaryText
// 开关开启颜色 #7C45ED // 开关开启颜色 #7C45ED
val switchActiveColor = Color(0xFF7C45ED) val switchActiveColor = Color(0xFF7C45ED)
@@ -579,14 +596,14 @@ fun SideMenuContent(
painter = painterResource(id = R.mipmap.sao), painter = painterResource(id = R.mipmap.sao),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(Color.Black) colorFilter = ColorFilter.tint(iconColor)
) )
} }
// 绝对定位的"扫一扫"文字上方71.5dp右侧66dp // 绝对定位的"扫一扫"文字上方71.5dp右侧66dp
Text( Text(
text = stringResource(R.string.scan_qr), text = stringResource(R.string.scan_qr),
fontSize = 14.sp, fontSize = 14.sp,
color = Color.Black, color = textColor,
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.offset(x = (-66).dp, y = 91.5.dp) .offset(x = (-66).dp, y = 91.5.dp)
@@ -602,7 +619,7 @@ fun SideMenuContent(
.noRippleClickable { .noRippleClickable {
// TODO: 实现QR码功能 // TODO: 实现QR码功能
}, },
colorFilter = ColorFilter.tint(Color.Black) colorFilter = ColorFilter.tint(iconColor)
) )
// 菜单选项卡片组 - 第一组卡片上方距离上方108pt绝对定位 // 菜单选项卡片组 - 第一组卡片上方距离上方108pt绝对定位
@@ -616,6 +633,8 @@ fun SideMenuContent(
// 第一组卡片:编辑资料、账号安全、收藏 // 第一组卡片:编辑资料、账号安全、收藏
MenuCard( MenuCard(
backgroundColor = cardBackgroundColor, backgroundColor = cardBackgroundColor,
textColor = textColor,
iconColor = iconColor,
width = 270.dp, width = 270.dp,
height = 164.dp, height = 164.dp,
items = listOf( items = listOf(
@@ -655,6 +674,8 @@ fun SideMenuContent(
// 第二组卡片:暗色模式、消息通知 // 第二组卡片:暗色模式、消息通知
MenuCard( MenuCard(
backgroundColor = cardBackgroundColor, backgroundColor = cardBackgroundColor,
textColor = textColor,
iconColor = iconColor,
width = 270.dp, width = 270.dp,
height = 112.dp, // 根据设计图第二组卡片高度为112dp height = 112.dp, // 根据设计图第二组卡片高度为112dp
items = listOf( items = listOf(
@@ -709,6 +730,8 @@ fun SideMenuContent(
// 第三组卡片:关于派派、反馈、退出登录 // 第三组卡片:关于派派、反馈、退出登录
MenuCard( MenuCard(
backgroundColor = cardBackgroundColor, backgroundColor = cardBackgroundColor,
textColor = textColor,
iconColor = iconColor,
width = 270.dp, width = 270.dp,
height = 164.dp, height = 164.dp,
items = listOf( items = listOf(
@@ -776,6 +799,8 @@ data class MenuItem(
@Composable @Composable
fun MenuCard( fun MenuCard(
backgroundColor: Color, backgroundColor: Color,
textColor: Color,
iconColor: Color,
items: List<MenuItem>, items: List<MenuItem>,
width: androidx.compose.ui.unit.Dp? = null, width: androidx.compose.ui.unit.Dp? = null,
height: androidx.compose.ui.unit.Dp? = null height: androidx.compose.ui.unit.Dp? = null
@@ -794,14 +819,15 @@ fun MenuCard(
.then(if (height != null) Modifier.weight(1f) else Modifier), .then(if (height != null) Modifier.weight(1f) else Modifier),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
MenuItemRow(item = item, compact = height != null) // 传递compact参数 MenuItemRow(item = item, compact = height != null, textColor = textColor, iconColor = iconColor) // 传递颜色参数
} }
} }
} }
} }
@Composable @Composable
fun MenuItemRow(item: MenuItem, compact: Boolean = false) { fun MenuItemRow(item: MenuItem, compact: Boolean = false, textColor: Color, iconColor: Color) {
val appColors = LocalAppTheme.current
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -825,12 +851,12 @@ fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
painter = painterResource(id = item.icon), painter = painterResource(id = item.icon),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(Color.Black) colorFilter = ColorFilter.tint(iconColor)
) )
Text( Text(
text = item.label, text = item.label,
fontSize = 14.sp, fontSize = 14.sp,
color = Color.Black color = textColor
) )
} }
@@ -841,7 +867,7 @@ fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
painter = painterResource(id = R.drawable.rave_now_nav_right), painter = painterResource(id = R.drawable.rave_now_nav_right),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(Color(0xFF111213)) colorFilter = ColorFilter.tint(appColors.text)
) )
} }
} }

View File

@@ -50,6 +50,8 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -981,9 +983,13 @@ fun ChatRoomCard(
Text( Text(
text = "${chatRoom.memberCount} ${stringResource(R.string.chatting_now)}", text = "${chatRoom.memberCount} ${stringResource(R.string.chatting_now)}",
fontSize = 12.sp, fontSize = 12.sp,
modifier = Modifier.alpha(0.6f), modifier = Modifier
.alpha(0.6f)
.weight(1f),
color = AppColors.text, color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500 fontWeight = androidx.compose.ui.text.font.FontWeight.W500,
maxLines = 1,
overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@@ -34,6 +34,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -108,13 +109,21 @@ fun AgentChatListScreen() {
text = stringResource(R.string.agent_chat_empty_title), text = stringResource(R.string.agent_chat_empty_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.agent_chat_empty_subtitle), text = stringResource(R.string.agent_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} }
else { else {
@@ -130,13 +139,21 @@ fun AgentChatListScreen() {
text = stringResource(R.string.friend_chat_no_network_title), text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_no_network_subtitle), text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ReloadButton( ReloadButton(

View File

@@ -186,13 +186,21 @@ fun AllChatListScreen() {
text = stringResource(R.string.friend_chat_empty_title), text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_empty_subtitle), text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} else { } else {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
@@ -207,13 +215,21 @@ fun AllChatListScreen() {
text = stringResource(R.string.friend_chat_no_network_title), text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_no_network_subtitle), text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ReloadButton( ReloadButton(

View File

@@ -96,13 +96,21 @@ fun FriendChatListScreen() {
text = stringResource(R.string.friend_chat_empty_title), text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_empty_subtitle), text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
}else { }else {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
@@ -117,13 +125,21 @@ fun FriendChatListScreen() {
text = stringResource(R.string.friend_chat_no_network_title), text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_no_network_subtitle), text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ReloadButton( ReloadButton(

View File

@@ -23,6 +23,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -88,13 +89,21 @@ fun GroupChatListScreen() {
text = stringResource(R.string.group_chat_empty), text = stringResource(R.string.group_chat_empty),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.group_chat_empty_join), text = stringResource(R.string.group_chat_empty_join),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
}else { }else {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
@@ -109,13 +118,21 @@ fun GroupChatListScreen() {
text = stringResource(R.string.friend_chat_no_network_title), text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_no_network_subtitle), text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ReloadButton( ReloadButton(

View File

@@ -59,6 +59,7 @@ import androidx.compose.ui.graphics.ColorFilter
import com.aiosman.ravenow.ui.composables.TabItem import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.UnderlineTabItem import com.aiosman.ravenow.ui.composables.UnderlineTabItem
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.shorts.ShortVideoScreen
/** /**
* 动态列表 * 动态列表
@@ -71,8 +72,8 @@ fun MomentsList() {
val navigationBarPaddings = val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
// 现在有6个tab推荐、短视频、新闻、探索、关注、热门 // 根据登录状态设置标签页数量游客模式5个tab非游客模式6个tab
val tabCount = 6 val tabCount = if (AppStore.isGuest) 5 else 6
var pagerState = rememberPagerState { tabCount } var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
Column( Column(
@@ -173,14 +174,14 @@ fun MomentsList() {
} }
) )
} else { } else {
// 热门标签 (游客模式) // 热门标签 (游客模式) - 在游客模式下热门标签对应第3页
UnderlineTabItem( UnderlineTabItem(
text = stringResource(R.string.index_hot), text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4, isSelected = pagerState.currentPage == 3,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { scope.launch {
pagerState.animateScrollToPage(4) pagerState.animateScrollToPage(3)
} }
} }
} }
@@ -188,14 +189,15 @@ fun MomentsList() {
} }
// 新闻标签 // 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页
val newsPageIndex = if (AppStore.isGuest) 4 else 5
UnderlineTabItem( UnderlineTabItem(
text = stringResource(R.string.tab_news), text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == 5, isSelected = pagerState.currentPage == newsPageIndex,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { scope.launch {
pagerState.animateScrollToPage(5) pagerState.animateScrollToPage(newsPageIndex)
} }
} }
} }
@@ -234,6 +236,7 @@ fun MomentsList() {
} }
1 -> { 1 -> {
// 短视频页面 // 短视频页面
ShortVideoScreen()
} }
2 -> { 2 -> {
// 动态页面 - 暂时显示时间线内容 // 动态页面 - 暂时显示时间线内容
@@ -248,16 +251,22 @@ fun MomentsList() {
} }
} }
4 -> { 4 -> {
// 热门页面 (仅非游客用户) // 热门页面 (仅非游客用户) 或 新闻页面 (游客用户)
if (AppStore.isGuest) {
NewsScreen()
} else {
HotMomentsList() HotMomentsList()
} }
}
5 -> { 5 -> {
// 新闻页面 // 新闻页面 (仅非游客用户)
if (!AppStore.isGuest) {
NewsScreen() NewsScreen()
} }
} }
} }
} }
}
} }
@Composable @Composable
fun CustomTabItem( fun CustomTabItem(

View File

@@ -15,6 +15,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
@@ -137,8 +139,18 @@ fun DiscoverView() {
val debouncer = rememberDebouncer() val debouncer = rememberDebouncer()
val textContent = momentItem.momentTextContent val textContent = momentItem.momentTextContent
// 对于英文和日文,每行字符数会更少,使用更保守的估算
val estimatedCharsPerLine = if (textContent.isNotEmpty()) {
// 检测是否包含非中文字符(英文、日文等)
val hasNonChinese = textContent.any {
val code = it.code
!(code >= 0x4E00 && code <= 0x9FFF) // 不在中文字符范围内
}
if (hasNonChinese) 15 else 20 // 英文/日文每行更少字符
} else {
20
}
val textLines = if (textContent.isNotEmpty()) { val textLines = if (textContent.isNotEmpty()) {
val estimatedCharsPerLine = 20
val estimatedLines = (textContent.length / estimatedCharsPerLine) + 1 val estimatedLines = (textContent.length / estimatedCharsPerLine) + 1
minOf(estimatedLines, 2) // 最多2行 minOf(estimatedLines, 2) // 最多2行
} else { } else {
@@ -146,21 +158,20 @@ fun DiscoverView() {
} }
val baseHeight = 200.dp val baseHeight = 200.dp
val singleLineTextHeight = 20.dp val singleLineTextHeight = 24.dp // 增加高度以适应英文/日文
val doubleLineTextHeight = 40.dp val doubleLineTextHeight = 44.dp // 增加高度以适应英文/日文
val authorInfoHeight = 25.dp val authorInfoHeight = 25.dp
val paddingHeight = 10.dp val paddingHeight = 10.dp
val paddingHeight2 =3.dp val paddingHeight2 = 3.dp
val totalHeight = baseHeight + when (textLines) { val totalHeight = baseHeight + when (textLines) {
0 -> authorInfoHeight + paddingHeight 0 -> authorInfoHeight + paddingHeight
1 -> singleLineTextHeight + authorInfoHeight + paddingHeight 1 -> singleLineTextHeight + authorInfoHeight + paddingHeight
else -> doubleLineTextHeight + authorInfoHeight +paddingHeight2 else -> doubleLineTextHeight + authorInfoHeight + paddingHeight2
} }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(totalHeight)
.padding(2.dp) .padding(2.dp)
.noRippleClickable { .noRippleClickable {
debouncer { debouncer {
@@ -173,7 +184,9 @@ fun DiscoverView() {
} }
) { ) {
Column( Column(
modifier = Modifier.fillMaxSize().background(AppColors.secondaryBackground, RoundedCornerShape(12.dp)) modifier = Modifier
.fillMaxWidth()
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = momentItem.images[0].thumbnail, imageUrl = momentItem.images[0].thumbnail,
@@ -193,9 +206,9 @@ fun DiscoverView() {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(totalHeight - baseHeight)
.padding(horizontal = 8.dp, vertical = 8.dp) .padding(horizontal = 8.dp, vertical = 8.dp)
) { ) {
// 文本内容区域,限制最大高度
if (momentItem.momentTextContent.isNotEmpty()) { if (momentItem.momentTextContent.isNotEmpty()) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = momentItem.momentTextContent, text = momentItem.momentTextContent,
@@ -203,13 +216,19 @@ fun DiscoverView() {
fontSize = 12.sp, fontSize = 12.sp,
color = AppColors.text, color = AppColors.text,
maxLines = 2, maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
lineHeight = 16.sp // 设置行高以适应不同语言
) )
} }
// 使用 Spacer 确保头像昵称栏始终在底部,有足够的空间
Spacer(modifier = Modifier.weight(1f))
// 头像昵称栏,确保始终完整显示,设置最小高度避免被挤压
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 25.dp) // 最小高度确保完整显示,自适应避免被挤压
.padding(top = 5.dp), .padding(top = 5.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -225,7 +244,9 @@ fun DiscoverView() {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = momentItem.nickname, text = momentItem.nickname,
modifier = Modifier.padding(start = 4.dp), modifier = Modifier
.padding(start = 4.dp)
.weight(1f),
fontSize = 11.sp, fontSize = 11.sp,
color = AppColors.text.copy(alpha = 0.6f), color = AppColors.text.copy(alpha = 0.6f),
maxLines = 1, maxLines = 1,

View File

@@ -0,0 +1,185 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.shorts
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.shorts.ShortViewCompose
import kotlinx.coroutines.launch
/**
* 短视频页面
*/
@Composable
fun ShortVideoScreen() {
val viewModel = ShortVideoViewModel
val allMoments = viewModel.moments
val navController = LocalNavController.current
val scope = rememberCoroutineScope()
val AppColors = LocalAppTheme.current
val momentLoader = viewModel.momentLoader
// 记录当前播放的短视频索引,切换 Tab 返回时恢复
val currentIndex = rememberSaveable { androidx.compose.runtime.mutableStateOf(0) }
// 过滤出包含视频的动态
val videoMoments = remember(allMoments) {
val filtered = allMoments.filter { it.videos != null && it.videos.isNotEmpty() }
Log.d("ShortVideoScreen", "过滤视频动态 - 总动态数: ${allMoments.size}, 包含视频的动态数: ${filtered.size}")
filtered.forEach { moment ->
Log.d("ShortVideoScreen", "视频动态 ID: ${moment.id}, 视频数: ${moment.videos?.size}, 第一个视频URL: ${moment.videos?.firstOrNull()?.url}")
}
filtered
}
// 初始加载数据
LaunchedEffect(Unit) {
Log.d("ShortVideoScreen", "开始加载数据")
viewModel.refreshPager()
}
// 加载更多数据
LaunchedEffect(allMoments.size, videoMoments.size) {
Log.d("ShortVideoScreen", "检查是否需要加载更多 - allMoments: ${allMoments.size}, videoMoments: ${videoMoments.size}, hasNext: ${momentLoader.hasNext}, isLoading: ${momentLoader.isLoading}")
if (allMoments.isNotEmpty() && videoMoments.size < 10 && momentLoader.hasNext && !momentLoader.isLoading) {
// 如果视频数量少于10个尝试加载更多
Log.d("ShortVideoScreen", "开始加载更多数据")
viewModel.loadMore()
}
}
// 加载状态
if (momentLoader.isLoading && videoMoments.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center
) {
CircularProgressIndicator(color = AppColors.main)
Text(
text = "加载中...",
modifier = Modifier.padding(top = 16.dp),
color = AppColors.text,
fontSize = 14.sp
)
}
}
}
// 错误状态
else if (momentLoader.error != null && videoMoments.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Text(
text = "加载失败: ${momentLoader.error}",
color = AppColors.error,
fontSize = 14.sp
)
}
}
// 空状态 - 已加载但无视频
else if (!momentLoader.isLoading && videoMoments.isEmpty() && allMoments.isNotEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Text(
text = "暂无短视频\n已加载 ${allMoments.size} 条动态,但都不包含视频",
color = AppColors.text,
fontSize = 16.sp
)
}
}
// 初始状态 - 还没有加载过数据
else if (!momentLoader.isLoading && videoMoments.isEmpty() && allMoments.isEmpty() && momentLoader.error == null) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Text(
text = "准备加载...",
color = AppColors.text,
fontSize = 16.sp
)
}
}
// 显示视频列表
else if (videoMoments.isNotEmpty()) {
ShortViewCompose(
videoMoments = videoMoments,
clickItemPosition = currentIndex.value,
onLikeClick = { moment ->
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
if (moment.liked) {
viewModel.dislikeMoment(moment.id)
} else {
viewModel.likeMoment(moment.id)
}
}
}
},
onCommentClick = { moment ->
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
viewModel.onAddComment(moment.id)
}
}
},
onFavoriteClick = { moment ->
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
if (moment.isFavorite) {
viewModel.unfavoriteMoment(moment.id)
} else {
viewModel.favoriteMoment(moment.id)
}
}
}
},
onShareClick = { moment ->
// TODO: 实现分享功能
},
onPageChanged = { idx -> currentIndex.value = idx }
)
}
}

View File

@@ -0,0 +1,21 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.shorts
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
import org.greenrobot.eventbus.EventBus
object ShortVideoViewModel : BaseMomentModel() {
init {
EventBus.getDefault().register(this)
}
override fun extraArgs(): MomentLoaderExtraArgs {
return MomentLoaderExtraArgs(explore = true, videoOnly = true)
}
// 获取包含视频的动态列表
fun getVideoMoments(): List<com.aiosman.ravenow.entity.MomentEntity> {
return moments.filter { it.videos != null && it.videos.isNotEmpty() }
}
}

View File

@@ -604,9 +604,19 @@ fun TopNavigationBar(
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) } val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
// 根据背景透明度决定图标颜色透明度为1时变黑否则为白色 // 根据背景透明度和暗色模式决定图标颜色
val iconColor = if (backgroundAlpha >= 1f) Color.Black else Color.White // 暗色模式下:图标始终为白色
val cardBorderColor = if (backgroundAlpha >= 1f) Color.Black else Color.White // 亮色模式下根据背景透明度决定透明度为1时变黑否则为白色
val iconColor = if (AppState.darkMode) {
Color.White // 暗色模式下图标始终为白色
} else {
if (backgroundAlpha >= 1f) Color.Black else Color.White
}
val cardBorderColor = if (AppState.darkMode) {
Color.White // 暗色模式下边框应为白色
} else {
if (backgroundAlpha >= 1f) Color.Black else Color.White
}
Box( Box(
modifier = Modifier modifier = Modifier
@@ -625,7 +635,12 @@ fun TopNavigationBar(
val baseColor = remember(backgroundAlpha) { val baseColor = remember(backgroundAlpha) {
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f) val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
// 初始状态:半透明深色,让白色图标清晰可见 if (AppState.darkMode) {
// 暗色模式下从黑色透明度0逐渐变为黑色透明度1
val alpha = smoothProgress.coerceIn(0f, 1f)
Color.Black.copy(alpha = alpha)
} else {
// 亮色模式:初始状态:半透明深色,让白色图标清晰可见
val initialDarkAlpha = 0.12f val initialDarkAlpha = 0.12f
// 使用平滑的插值函数,让整个过渡更自然 // 使用平滑的插值函数,让整个过渡更自然
@@ -645,6 +660,7 @@ fun TopNavigationBar(
alpha = alpha.coerceIn(0f, initialDarkAlpha) alpha = alpha.coerceIn(0f, initialDarkAlpha)
) )
} }
}
Box( Box(
modifier = Modifier modifier = Modifier
@@ -703,7 +719,7 @@ fun TopNavigationBar(
text = numberFormat.format(interactionCount), text = numberFormat.format(interactionCount),
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
color = Color.Black, // 文字始终为黑色 color = if (AppState.darkMode) Color.White else Color.Black, // 暗色模式下为白色,亮色模式下为黑色
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
@@ -744,6 +760,9 @@ fun TopNavigationBar(
// 如果不是主页面,显示返回按钮和用户信息 // 如果不是主页面,显示返回按钮和用户信息
if (!isMain) { if (!isMain) {
val statusBarPadding = WindowInsets.systemBars.asPaddingValues() val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
// 判断是否有背景图
val hasBanner = profile?.banner != null
Row( Row(
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
@@ -751,6 +770,8 @@ fun TopNavigationBar(
.padding(top = statusBarPadding.calculateTopPadding()), .padding(top = statusBarPadding.calculateTopPadding()),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// 返回按钮:深色模式下为白色,亮色模式下为黑色
val backButtonColor = if (AppState.darkMode) Color.White else Color.Black
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon), painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = "Back", contentDescription = "Back",
@@ -759,28 +780,22 @@ fun TopNavigationBar(
navController.navigateUp() navController.navigateUp()
} }
.size(24.dp), .size(24.dp),
colorFilter = ColorFilter.tint(Color.White) colorFilter = ColorFilter.tint(backButtonColor) // 深色模式下为白色,亮色模式下为黑色
) )
// 未设置背景的用户(自己的主页且没有背景图)显示昵称
if (isSelf && !hasBanner && profile != null) {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Text( Text(
text = profile?.nickName ?: "", text = profile.nickName ?: "",
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
color = Color.White color = backButtonColor // 深色模式下为白色,亮色模式下为黑色
) )
} }
} }
} }
}
} }
// 分享图标(向上箭头) // 分享图标(向上箭头)

View File

@@ -42,6 +42,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
@@ -199,19 +201,27 @@ fun GalleryGrid(
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp)) Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
Text( Text(
text = "你的故事还没开始", text = stringResource(R.string.your_story_not_started),
fontSize = 16.sp, fontSize = 16.sp,
color = AppColors.text, color = AppColors.text,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "发布一条动态,和世界打个招呼吧", text = stringResource(R.string.publish_moment_greeting),
fontSize = 14.sp, fontSize = 14.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} }
} else { } else {
@@ -221,7 +231,7 @@ fun GalleryGrid(
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp), modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
) { ) {
itemsIndexed(moments) { idx, moment -> itemsIndexed(moments) { idx, moment ->
if (moment != null) { if (moment != null && moment.images.isNotEmpty()) {
val itemDebouncer = rememberDebouncer() val itemDebouncer = rememberDebouncer()
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Divider
import androidx.compose.material3.Text 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
@@ -27,27 +26,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@Composable @Composable
fun GroupChatEmptyContent() { fun GroupChatEmptyContent() {
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
val AppColors = LocalAppTheme.current
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
// 分割线(紧贴上方栏)
Divider(
color = Color(0xFFF0F0F0), // 更浅的灰色
thickness = 1.dp,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 分段控制器 // 分段控制器
@@ -71,10 +68,14 @@ fun GroupChatEmptyContent() {
// 空状态文本 // 空状态文本
Text( Text(
text = "空空如也~", text = stringResource(R.string.empty_nothing),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = Color(0xFF000000) color = AppColors.text,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
} }
} }
@@ -86,6 +87,7 @@ private fun SegmentedControl(
onSegmentSelected: (Int) -> Unit, onSegmentSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val AppColors = LocalAppTheme.current
Row( Row(
modifier = modifier modifier = modifier
.height(32.dp), .height(32.dp),
@@ -94,30 +96,33 @@ private fun SegmentedControl(
) { ) {
// 全部 // 全部
SegmentButton( SegmentButton(
text = "全部", text = stringResource(R.string.chat_all),
isSelected = selectedIndex == 0, isSelected = selectedIndex == 0,
onClick = { onSegmentSelected(0) }, onClick = { onSegmentSelected(0) },
width = 54.dp width = 54.dp,
appColors = AppColors
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
// 公开 // 公开
SegmentButton( SegmentButton(
text = "公开", text = stringResource(R.string.public_label),
isSelected = selectedIndex == 1, isSelected = selectedIndex == 1,
onClick = { onSegmentSelected(1) }, onClick = { onSegmentSelected(1) },
width = 59.dp width = 59.dp,
appColors = AppColors
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
// 私有 // 私有
SegmentButton( SegmentButton(
text = "私有", text = stringResource(R.string.private_label),
isSelected = selectedIndex == 2, isSelected = selectedIndex == 2,
onClick = { onSegmentSelected(2) }, onClick = { onSegmentSelected(2) },
width = 54.dp width = 54.dp,
appColors = AppColors
) )
} }
} }
@@ -127,7 +132,8 @@ private fun SegmentButton(
text: String, text: String,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
width: androidx.compose.ui.unit.Dp width: androidx.compose.ui.unit.Dp,
appColors: com.aiosman.ravenow.AppThemeData
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -135,7 +141,7 @@ private fun SegmentButton(
.height(32.dp) .height(32.dp)
.background( .background(
color = if (isSelected) { color = if (isSelected) {
Color(0xFF110C13) // RGB(17, 12, 19) appColors.checkedBackground // 使用选中背景色(暗色模式下是白色,亮色模式下是黑色)
} else { } else {
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08) Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
}, },
@@ -148,7 +154,11 @@ private fun SegmentButton(
text = text, text = text,
fontSize = 13.sp, fontSize = 13.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000) color = if (isSelected) {
appColors.checkedText // 选中时使用选中文本颜色(暗色模式下是黑色,亮色模式下是白色)
} else {
appColors.text // 未选中时使用文本颜色
}
) )
} }
} }

View File

@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Divider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -35,6 +34,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -209,13 +209,6 @@ fun AgentEmptyContentWithSegments() {
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
// 分割线(紧贴上方栏)
Divider(
color = Color(0xFFF0F0F0), // 更浅的灰色
thickness = 1.dp,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 分段控制器 // 分段控制器
@@ -247,19 +240,27 @@ fun AgentEmptyContentWithSegments() {
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp)) Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
Text( Text(
text = "专属AI等你召唤", text = stringResource(R.string.exclusive_ai_waiting),
fontSize = 16.sp, fontSize = 16.sp,
color = AppColors.text, color = AppColors.text,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "AI将成为你的伙伴而不是工具", text = stringResource(R.string.ai_companion_not_tool),
fontSize = 14.sp, fontSize = 14.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis
) )
} else { } else {
Image( Image(
@@ -303,6 +304,7 @@ private fun AgentSegmentedControl(
onSegmentSelected: (Int) -> Unit, onSegmentSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val AppColors = LocalAppTheme.current
Row( Row(
modifier = modifier modifier = modifier
.height(32.dp), .height(32.dp),
@@ -311,30 +313,33 @@ private fun AgentSegmentedControl(
) { ) {
// 全部 // 全部
AgentSegmentButton( AgentSegmentButton(
text = "全部", text = stringResource(R.string.chat_all),
isSelected = selectedIndex == 0, isSelected = selectedIndex == 0,
onClick = { onSegmentSelected(0) }, onClick = { onSegmentSelected(0) },
width = 54.dp width = 54.dp,
appColors = AppColors
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
// 公开 // 公开
AgentSegmentButton( AgentSegmentButton(
text = "公开", text = stringResource(R.string.public_label),
isSelected = selectedIndex == 1, isSelected = selectedIndex == 1,
onClick = { onSegmentSelected(1) }, onClick = { onSegmentSelected(1) },
width = 59.dp width = 59.dp,
appColors = AppColors
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
// 私有 // 私有
AgentSegmentButton( AgentSegmentButton(
text = "私有", text = stringResource(R.string.private_label),
isSelected = selectedIndex == 2, isSelected = selectedIndex == 2,
onClick = { onSegmentSelected(2) }, onClick = { onSegmentSelected(2) },
width = 54.dp width = 54.dp,
appColors = AppColors
) )
} }
} }
@@ -344,7 +349,8 @@ private fun AgentSegmentButton(
text: String, text: String,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
width: androidx.compose.ui.unit.Dp width: androidx.compose.ui.unit.Dp,
appColors: com.aiosman.ravenow.AppThemeData
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -352,7 +358,7 @@ private fun AgentSegmentButton(
.height(32.dp) .height(32.dp)
.background( .background(
color = if (isSelected) { color = if (isSelected) {
Color(0xFF110C13) // RGB(17, 12, 19) appColors.checkedBackground // 使用选中背景色(暗色模式下是白色,亮色模式下是黑色)
} else { } else {
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08) Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
}, },
@@ -365,7 +371,11 @@ private fun AgentSegmentButton(
text = text, text = text,
fontSize = 13.sp, fontSize = 13.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000) color = if (isSelected) {
appColors.checkedText // 选中时使用选中文本颜色(暗色模式下是黑色,亮色模式下是白色)
} else {
appColors.text // 未选中时使用文本颜色
}
) )
} }
} }

View File

@@ -10,21 +10,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -52,7 +45,7 @@ fun UserContentPageIndicator(
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
) { ) {
// 图片/相册 Tab // 动态 Tab
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
@@ -64,15 +57,24 @@ fun UserContentPageIndicator(
} }
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
) { ) {
Icon( Text(
painter = painterResource(id = R.drawable.rider_pro_images), text = stringResource(R.string.index_dynamic),
contentDescription = "Gallery", fontSize = 16.sp,
tint = if (pagerState.currentPage == 0) AppColors.text else AppColors.text.copy(alpha = 0.6f), fontWeight = if (pagerState.currentPage == 0) FontWeight.SemiBold else FontWeight.Medium,
modifier = Modifier.size(24.dp) color = if (pagerState.currentPage == 0) AppColors.text else AppColors.secondaryText
)
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 0) 6.dp else 4.dp))
if (pagerState.currentPage == 0) {
Box(
modifier = Modifier
.width(20.dp)
.height(2.dp)
.background(AppColors.text)
) )
} }
}
// Agent Tab (只在非智能体用户时显示) // 智能体 Tab (只在非智能体用户时显示)
if (showAgentTab) { if (showAgentTab) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -85,40 +87,54 @@ fun UserContentPageIndicator(
} }
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
) { ) {
Icon( Text(
painter = painterResource(id = R.drawable.rider_pro_nav_ai), text = stringResource(R.string.chat_ai),
contentDescription = "Agents", fontSize = 16.sp,
tint = if (pagerState.currentPage == 1) AppColors.text else AppColors.text.copy(alpha = 0.6f), fontWeight = if (pagerState.currentPage == 1) FontWeight.SemiBold else FontWeight.Medium,
modifier = Modifier.size(24.dp) color = if (pagerState.currentPage == 1) AppColors.text else AppColors.secondaryText
)
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 1) 6.dp else 4.dp))
if (pagerState.currentPage == 1) {
Box(
modifier = Modifier
.width(20.dp)
.height(2.dp)
.background(AppColors.text)
) )
} }
} }
} }
// 下划线指示器 // 群聊 Tab (只在非智能体用户时显示)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.height(2.dp)
.background(
if (pagerState.currentPage == 0) AppColors.text else Color.Transparent
)
)
if (showAgentTab) { if (showAgentTab) {
Box( Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.noRippleClickable {
scope.launch {
pagerState.scrollToPage(2)
}
}
.padding(vertical = 12.dp)
) {
Text(
text = stringResource(R.string.chat_group),
fontSize = 16.sp,
fontWeight = if (pagerState.currentPage == 2) FontWeight.SemiBold else FontWeight.Medium,
color = if (pagerState.currentPage == 2) AppColors.text else AppColors.secondaryText
)
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 2) 6.dp else 4.dp))
if (pagerState.currentPage == 2) {
Box(
modifier = Modifier
.width(20.dp)
.height(2.dp) .height(2.dp)
.background( .background(AppColors.text)
if (pagerState.currentPage == 1) AppColors.text else Color.Transparent
)
) )
} }
} }
} }
}
}
} }

View File

@@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -117,15 +118,15 @@ fun UserItem(
text = postCount.toString(), text = postCount.toString(),
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 15.sp, fontSize = 15.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( Text(
text = "帖子", text = stringResource(R.string.posts),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 11.sp, fontSize = 11.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
@@ -152,15 +153,15 @@ fun UserItem(
text = formattedFollowerCount, text = formattedFollowerCount,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 15.sp, fontSize = 15.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( Text(
text = "粉丝", text = stringResource(R.string.followers_upper),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 11.sp, fontSize = 11.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
@@ -188,15 +189,15 @@ fun UserItem(
text = accountProfileEntity.followingCount.toString(), text = accountProfileEntity.followingCount.toString(),
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 15.sp, fontSize = 15.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( Text(
text = "关注", text = stringResource(R.string.following_upper),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 11.sp, fontSize = 11.sp,
color = Color(0xFF000000), color = AppColors.text,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
@@ -216,7 +217,7 @@ fun UserItem(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 22.sp, fontSize = 22.sp,
letterSpacing = (-0.3).sp, letterSpacing = (-0.3).sp,
color = Color(0xFF000000) color = AppColors.text
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -226,7 +227,7 @@ fun UserItem(
Text( Text(
text = accountProfileEntity.bio, text = accountProfileEntity.bio,
fontSize = 13.sp, fontSize = 13.sp,
color = Color(0x993C3C43), // 60/255, 60/255, 67/255, alpha 0.6 color = AppColors.secondaryText,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -234,7 +235,7 @@ fun UserItem(
Text( Text(
text = "Welcome to my fantiac word i will show you something about magic", text = "Welcome to my fantiac word i will show you something about magic",
fontSize = 13.sp, fontSize = 13.sp,
color = Color(0x993C3C43), color = AppColors.secondaryText,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -257,7 +258,7 @@ fun UserItem(
ProfileTag( ProfileTag(
text = mbti, text = mbti,
backgroundColor = Color(0x33FF8D28), // 255/255, 141/255, 40/255, alpha 0.2 backgroundColor = Color(0x33FF8D28), // 255/255, 141/255, 40/255, alpha 0.2
textColor = Color(0xFF000000) textColor = AppColors.text
) )
} }
@@ -266,19 +267,19 @@ fun UserItem(
ProfileTag( ProfileTag(
text = zodiac, text = zodiac,
backgroundColor = Color(0x33FFCC00), // 255/255, 204/255, 0/255, alpha 0.2 backgroundColor = Color(0x33FFCC00), // 255/255, 204/255, 0/255, alpha 0.2
textColor = Color(0xFF000000) textColor = AppColors.text
) )
} }
// 编辑标签(仅自己可见) // 编辑标签(仅自己可见)
if (isSelf) { if (isSelf) {
ProfileTag( ProfileTag(
text = "编辑", text = stringResource(R.string.edit_profile),
backgroundColor = Color(0x14947A80), // 124/255, 116/255, 128/255, alpha 0.08 backgroundColor = Color(0x14947A80), // 124/255, 116/255, 128/255, alpha 0.08
textColor = Color(0xFF9284BD), // 146/255, 132/255, 189/255 textColor = AppColors.text,
leadingIcon = { leadingIcon = {
EditIcon( EditIcon(
color = Color(0xFF9284BD), color = AppColors.text,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
}, },
@@ -333,7 +334,7 @@ private fun EditIcon(
) { ) {
Image( Image(
painter = painterResource(id = R.mipmap.bi), painter = painterResource(id = R.mipmap.bi),
contentDescription = "编辑", contentDescription = stringResource(R.string.edit_profile),
modifier = modifier, modifier = modifier,
colorFilter = ColorFilter.tint(color) colorFilter = ColorFilter.tint(color)
) )

View File

@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -61,27 +62,65 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util import androidx.media3.common.util.Util
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSourceFactory import androidx.media3.datasource.DefaultDataSourceFactory
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.ui.comment.CommentModalContent import com.aiosman.ravenow.ui.comment.CommentModalContent
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun ShortViewCompose( fun ShortViewCompose(
videoItemsUrl: List<String>, videoItemsUrl: List<String> = emptyList(),
videoMoments: List<MomentEntity> = emptyList(),
clickItemPosition: Int = 0, clickItemPosition: Int = 0,
videoHeader: @Composable () -> Unit = {}, videoHeader: @Composable () -> Unit = {},
videoBottom: @Composable () -> Unit = {} videoBottom: @Composable ((MomentEntity) -> Unit)? = null,
onLikeClick: ((MomentEntity) -> Unit)? = null,
onCommentClick: ((MomentEntity) -> Unit)? = null,
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
onShareClick: ((MomentEntity) -> Unit)? = null,
onPageChanged: ((Int) -> Unit)? = null
) { ) {
val pagerState: PagerState = run { // 优先使用 videoMoments如果没有则使用 videoItemsUrl
remember { val items = if (videoMoments.isNotEmpty()) {
PagerState(clickItemPosition, 0, videoItemsUrl.size - 1) videoMoments.mapNotNull { moment ->
// MomentVideoEntity 的 url 已经在 toMomentItem() 中添加了 BASE_SERVER 前缀
moment.videos?.firstOrNull()?.url
} }
} else {
videoItemsUrl
}
// 如果视频列表为空,显示空状态
if (items.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "暂无视频",
color = Color.White,
fontSize = 16.sp
)
}
return
}
// 确保 items 不为空后再创建 PagerState
val pagerState: PagerState = remember(items.size) {
val maxPage = maxOf(0, items.size - 1)
PagerState(
currentPage = clickItemPosition.coerceIn(0, maxPage),
minPage = 0,
maxPage = maxPage
)
} }
val initialLayout = remember { val initialLayout = remember {
mutableStateOf(true) mutableStateOf(true)
@@ -89,20 +128,39 @@ fun ShortViewCompose(
val pauseIconVisibleState = remember { val pauseIconVisibleState = remember {
mutableStateOf(false) mutableStateOf(false)
} }
Pager( Pager(
modifier = Modifier
.fillMaxSize()
.clip(RectangleShape),
state = pagerState, state = pagerState,
orientation = Orientation.Vertical, orientation = Orientation.Vertical,
offscreenLimit = 1 offscreenLimit = 1
) { ) {
pauseIconVisibleState.value = false pauseIconVisibleState.value = false
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
videoMoments[page]
} else {
null
}
// 同步页码到外部(用于返回时恢复进度)
LaunchedEffect(pagerState.currentPage) {
onPageChanged?.invoke(pagerState.currentPage)
}
SingleVideoItemContent( SingleVideoItemContent(
videoItemsUrl[page], videoUrl = items[page],
pagerState, moment = currentMoment,
page, pagerState = pagerState,
initialLayout, pager = page,
pauseIconVisibleState, initialLayout = initialLayout,
videoHeader, pauseIconVisibleState = pauseIconVisibleState,
videoBottom VideoHeader = videoHeader,
VideoBottom = videoBottom,
onLikeClick = onLikeClick,
onCommentClick = onCommentClick,
onFavoriteClick = onFavoriteClick,
onShareClick = onShareClick
) )
} }
@@ -116,18 +174,39 @@ fun ShortViewCompose(
@Composable @Composable
private fun SingleVideoItemContent( private fun SingleVideoItemContent(
videoUrl: String, videoUrl: String,
moment: MomentEntity?,
pagerState: PagerState, pagerState: PagerState,
pager: Int, pager: Int,
initialLayout: MutableState<Boolean>, initialLayout: MutableState<Boolean>,
pauseIconVisibleState: MutableState<Boolean>, pauseIconVisibleState: MutableState<Boolean>,
VideoHeader: @Composable() () -> Unit, VideoHeader: @Composable() () -> Unit = {},
VideoBottom: @Composable() () -> Unit, VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
onLikeClick: ((MomentEntity) -> Unit)? = null,
onCommentClick: ((MomentEntity) -> Unit)? = null,
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
onShareClick: ((MomentEntity) -> Unit)? = null
) { ) {
Box(modifier = Modifier.fillMaxSize()) { Box(
VideoPlayer(videoUrl, pagerState, pager, pauseIconVisibleState) modifier = Modifier
.fillMaxSize()
.clip(RectangleShape) // 确保内容不会溢出到box外
) {
VideoPlayer(
videoUrl = videoUrl,
moment = moment,
pagerState = pagerState,
pager = pager,
pauseIconVisibleState = pauseIconVisibleState,
onLikeClick = onLikeClick,
onCommentClick = onCommentClick,
onFavoriteClick = onFavoriteClick,
onShareClick = onShareClick
)
VideoHeader.invoke() VideoHeader.invoke()
if (moment != null && VideoBottom != null) {
Box(modifier = Modifier.align(Alignment.BottomStart)) { Box(modifier = Modifier.align(Alignment.BottomStart)) {
VideoBottom.invoke() VideoBottom.invoke(moment)
}
} }
if (initialLayout.value) { if (initialLayout.value) {
Box( Box(
@@ -143,9 +222,14 @@ private fun SingleVideoItemContent(
@Composable @Composable
fun VideoPlayer( fun VideoPlayer(
videoUrl: String, videoUrl: String,
moment: MomentEntity?,
pagerState: PagerState, pagerState: PagerState,
pager: Int, pager: Int,
pauseIconVisibleState: MutableState<Boolean>, pauseIconVisibleState: MutableState<Boolean>,
onLikeClick: ((MomentEntity) -> Unit)? = null,
onCommentClick: ((MomentEntity) -> Unit)? = null,
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
onShareClick: ((MomentEntity) -> Unit)? = null,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -158,9 +242,20 @@ fun VideoPlayer(
ExoPlayer.Builder(context) ExoPlayer.Builder(context)
.build() .build()
.apply { .apply {
// 创建带有认证头的 HttpDataSource.Factory
val httpDataSourceFactory = DefaultHttpDataSource.Factory()
.setUserAgent(Util.getUserAgent(context, context.packageName))
.setDefaultRequestProperties(
mapOf(
"Authorization" to "Bearer ${com.aiosman.ravenow.AppStore.token ?: ""}",
"DEVICE-OS" to "Android"
)
)
// 创建 DataSource.Factory使用自定义的 HttpDataSource.Factory
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory( val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
context, context,
Util.getUserAgent(context, context.packageName) httpDataSourceFactory
) )
val source = ProgressiveMediaSource.Factory(dataSourceFactory) val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl))) .createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
@@ -275,21 +370,54 @@ fun VideoPlayer(
modifier = Modifier.padding(bottom = 72.dp, end = 12.dp), modifier = Modifier.padding(bottom = 72.dp, end = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
if (moment != null) {
UserAvatar(avatarUrl = moment.avatar)
VideoBtn(
icon = R.drawable.rider_pro_video_like,
text = formatCount(moment.likeCount)
) {
moment?.let { onLikeClick?.invoke(it) }
}
VideoBtn(
icon = R.drawable.rider_pro_video_comment,
text = formatCount(moment.commentCount)
) {
moment?.let {
showCommentModal = true
onCommentClick?.invoke(it)
}
}
VideoBtn(
icon = R.drawable.rider_pro_video_favor,
text = formatCount(moment.favoriteCount)
) {
moment?.let { onFavoriteClick?.invoke(it) }
}
VideoBtn(
icon = R.drawable.rider_pro_video_share,
text = formatCount(moment.shareCount)
) {
moment?.let { onShareClick?.invoke(it) }
}
} else {
UserAvatar() UserAvatar()
VideoBtn(icon = R.drawable.rider_pro_video_like, text = "975.9k") VideoBtn(icon = R.drawable.rider_pro_video_like, text = "0")
VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "1896") { VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "0") {
showCommentModal = true showCommentModal = true
} }
VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "234") VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "0")
VideoBtn(icon = R.drawable.rider_pro_video_share, text = "677k") VideoBtn(icon = R.drawable.rider_pro_video_share, text = "0")
}
} }
} }
// info // info
if (moment != null) {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomStart contentAlignment = Alignment.BottomStart
) { ) {
Column(modifier = Modifier.padding(start = 16.dp, bottom = 16.dp)) { Column(modifier = Modifier.padding(start = 16.dp, bottom = 16.dp)) {
if (moment.location.isNotEmpty() && moment.location != "Worldwide") {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp) .padding(bottom = 8.dp)
@@ -306,39 +434,43 @@ fun VideoPlayer(
) )
Text( Text(
modifier = Modifier.padding(end = 4.dp), modifier = Modifier.padding(end = 4.dp),
text = "USA", text = moment.location,
fontSize = 12.sp, fontSize = 12.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold) style = TextStyle(fontWeight = FontWeight.Bold)
) )
} }
}
Text( Text(
text = "@Kevinlinpr", text = "@${moment.nickname}",
fontSize = 16.sp, fontSize = 16.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold) style = TextStyle(fontWeight = FontWeight.Bold)
) )
if (moment.momentTextContent.isNotEmpty()) {
Text( Text(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 4.dp), // 确保Text占用可用宽度 .padding(top = 4.dp),
text = "Pedro Acosta to join KTM in 2025 on a multi-year deal! \uD83D\uDFE0", text = moment.momentTextContent,
fontSize = 16.sp, fontSize = 16.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold), style = TextStyle(fontWeight = FontWeight.Bold),
overflow = TextOverflow.Ellipsis, // 超出范围时显示省略号 overflow = TextOverflow.Ellipsis,
maxLines = 2 // 最多显示两行 maxLines = 2
) )
} }
} }
}
}
if (showCommentModal) { if (showCommentModal && moment != null) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { showCommentModal = false }, onDismissRequest = { showCommentModal = false },
containerColor = Color.White, containerColor = Color.White,
sheetState = sheetState sheetState = sheetState
) { ) {
CommentModalContent() { CommentModalContent(postId = moment.id) {
} }
} }
@@ -346,16 +478,37 @@ fun VideoPlayer(
} }
@Composable @Composable
fun UserAvatar() { fun UserAvatar(avatarUrl: String? = null) {
Image( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 16.dp) .padding(bottom = 16.dp)
.size(40.dp) .size(40.dp)
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp)) .border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
.clip( .clip(RoundedCornerShape(40.dp))
RoundedCornerShape(40.dp) ) {
), painter = painterResource(id = R.drawable.default_avatar), contentDescription = "" if (avatarUrl != null && avatarUrl.isNotEmpty()) {
CustomAsyncImage(
imageUrl = avatarUrl,
contentDescription = "用户头像",
modifier = Modifier.fillMaxSize(),
defaultRes = R.drawable.default_avatar
) )
} else {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "用户头像"
)
}
}
}
// 格式化数字显示
private fun formatCount(count: Int): String {
return when {
count >= 1_000_000 -> String.format("%.1fM", count / 1_000_000.0)
count >= 1_000 -> String.format("%.1fK", count / 1_000.0)
else -> count.toString()
}
} }
@Composable @Composable

View File

@@ -204,7 +204,7 @@ fun EmailSignupScreen() {
email = it email = it
}, },
label = stringResource(R.string.login_email_label), label = stringResource(R.string.login_email_label),
hint = "输入电子邮件", hint = stringResource(R.string.text_hint_email),
error = emailError, error = emailError,
leadingIcon = { leadingIcon = {
Image( Image(

View File

@@ -26,6 +26,8 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@@ -68,6 +70,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -160,8 +163,9 @@ fun NewPostScreen() {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(start = 16.dp) .padding(start = 16.dp)
.width(100.dp)
.height(40.dp) .height(40.dp)
.widthIn(min = 100.dp, max = 200.dp)
.wrapContentWidth()
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background( .background(
brush = Brush.linearGradient( brush = Brush.linearGradient(
@@ -189,6 +193,8 @@ fun NewPostScreen() {
modifier = Modifier modifier = Modifier
.padding(start = 2.dp), .padding(start = 2.dp),
color = Color.White, color = Color.White,
maxLines = 1,
overflow = TextOverflow.Ellipsis
) )
} }

View File

@@ -12,6 +12,7 @@
<string name="users">ユーザー</string> <string name="users">ユーザー</string>
<string name="like_upper">いいね</string> <string name="like_upper">いいね</string>
<string name="followers_upper">フォロワー</string> <string name="followers_upper">フォロワー</string>
<string name="posts">投稿</string>
<string name="favourites_upper">お気に入り</string> <string name="favourites_upper">お気に入り</string>
<string name="favourites_null">あれ、何もない。..</string> <string name="favourites_null">あれ、何もない。..</string>
<string name="notifications_upper">通知</string> <string name="notifications_upper">通知</string>
@@ -52,6 +53,10 @@
<string name="error_not_accept_term">最高のサービスを提供するために、登録前に利用規約を読み、同意してください。</string> <string name="error_not_accept_term">最高のサービスを提供するために、登録前に利用規約を読み、同意してください。</string>
<string name="empty_my_post_title">まだ投稿がありません</string> <string name="empty_my_post_title">まだ投稿がありません</string>
<string name="empty_my_post_content">今すぐモーメントを投稿</string> <string name="empty_my_post_content">今すぐモーメントを投稿</string>
<string name="story_not_started">ストーリーはまだ始まっていません</string>
<string name="your_story_not_started">あなたのストーリーはまだ始まっていません</string>
<string name="publish_moment_greeting">モーメントを投稿して、世界に挨拶しましょう</string>
<string name="no_image">画像がありません</string>
<string name="edit_profile">プロフィールを編集</string> <string name="edit_profile">プロフィールを編集</string>
<string name="share">シェア</string> <string name="share">シェア</string>
<string name="logout">ログアウト</string> <string name="logout">ログアウト</string>
@@ -144,17 +149,21 @@
<string name="agent_desc_hint">例:経験豊富な営業担当者で、ユーモアと生きた事例を使って、複雑な製品を顧客が理解しやすい形に変えるのが得意</string> <string name="agent_desc_hint">例:経験豊富な営業担当者で、ユーモアと生きた事例を使って、複雑な製品を顧客が理解しやすい形に変えるのが得意</string>
<string name="agent_create">エージェントを作成</string> <string name="agent_create">エージェントを作成</string>
<string name="moment_content_hint">投稿のインスピレーションが必要ですかAIがお手伝いします</string> <string name="moment_content_hint">投稿のインスピレーションが必要ですかAIがお手伝いします</string>
<string name="moment_ai_co">AIの文案最適化</string> <string name="moment_ai_co">文案最適化</string>
<string name="moment_ai_delete">削除</string> <string name="moment_ai_delete">削除</string>
<string name="moment_ai_apply">適用</string> <string name="moment_ai_apply">適用</string>
<string name="chat_ai">AI</string> <string name="chat_ai">AI</string>
<string name="chat_group">グループ</string> <string name="chat_group">グループ</string>
<string name="chat_friend">友達</string> <string name="chat_friend">友達</string>
<string name="chat_all">すべて</string> <string name="chat_all">すべて</string>
<string name="public_label">公開</string>
<string name="private_label">プライベート</string>
<string name="chatting_now">人はおしゃべりをしている…</string> <string name="chatting_now">人はおしゃべりをしている…</string>
<string name="agent_chat_list_title">AIエージェントチャット</string> <string name="agent_chat_list_title">AIエージェントチャット</string>
<string name="agent_chat_empty_title">AIエージェントチャットがありません</string> <string name="agent_chat_empty_title">AIエージェントチャットがありません</string>
<string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string> <string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string>
<string name="exclusive_ai_waiting">専属AIがあなたを待っています</string>
<string name="ai_companion_not_tool">AIはあなたのパートナーとなり、ツールではありません</string>
<string name="agent_chat_me_prefix">私: </string> <string name="agent_chat_me_prefix">私: </string>
<string name="agent_chat_image">[画像]</string> <string name="agent_chat_image">[画像]</string>
<string name="agent_chat_voice">[音声]</string> <string name="agent_chat_voice">[音声]</string>
@@ -166,10 +175,15 @@
<string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string> <string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string>
<string name="group_chat_empty">グループチャットがありません</string> <string name="group_chat_empty">グループチャットがありません</string>
<string name="group_chat_empty_join">まだどのグループチャットにも参加していません</string> <string name="group_chat_empty_join">まだどのグループチャットにも参加していません</string>
<string name="empty_nothing">何もありません~</string>
<string name="group_chat_empty_title">グループチャットメッセージのない宇宙は静かすぎます</string> <string name="group_chat_empty_title">グループチャットメッセージのない宇宙は静かすぎます</string>
<string name="group_chat_empty_subtitle">ホームで興味のあるテーマルームを探してみましょう</string> <string name="group_chat_empty_subtitle">ホームで興味のあるテーマルームを探してみましょう</string>
<string name="friend_chat_empty_title">まだ友達とチャットしていません~</string> <string name="friend_chat_empty_title">まだ友達とチャットしていません~</string>
<string name="friend_chat_empty_subtitle">友達のアバターをクリックして、すぐにチャットを始めましょう。</string> <string name="friend_chat_empty_subtitle">友達のアバターをクリックして、すぐにチャットを始めましょう。</string>
<string name="following_empty_title">まだ誰もフォローしていません</string>
<string name="following_empty_subtitle">探索してみてください。近づきたい光が見つかります ✨</string>
<string name="follower_empty_title">まだ誰もあなたをフォローしていません</string>
<string name="follower_empty_subtitle">信号を送ってみてください。誰かが引き寄せられるでしょう~</string>
<string name="friend_chat_me_prefix">私: </string> <string name="friend_chat_me_prefix">私: </string>
<string name="friend_chat_load_failed">読み込みに失敗しました</string> <string name="friend_chat_load_failed">読み込みに失敗しました</string>
<string name="create_group_chat">グループチャットを作成</string> <string name="create_group_chat">グループチャットを作成</string>
@@ -255,9 +269,28 @@
<!-- Edit Profile Extras --> <!-- Edit Profile Extras -->
<string name="mbti_type">MBTIタイプ</string> <string name="mbti_type">MBTIタイプ</string>
<string name="zodiac">星座</string> <string name="zodiac">星座</string>
<string name="change_cover">カバーを変更</string>
<string name="personal_intro">自己紹介</string>
<string name="error_nickname_empty">ニックネームは空にできません</string>
<string name="error_nickname_too_short">ニックネームの長さは3文字以上である必要があります</string>
<string name="error_nickname_too_long">ニックネームの長さは20文字以下である必要があります</string>
<string name="error_bio_too_long">自己紹介の長さは100文字以下である必要があります</string>
<string name="error_load_profile_failed">ユーザープロフィールの読み込みに失敗しました。もう一度お試しください</string>
<string name="save">保存</string> <string name="save">保存</string>
<string name="choose_mbti">MBTIを選択</string> <string name="choose_mbti">MBTIを選択</string>
<string name="choose_zodiac">星座を選択</string> <string name="choose_zodiac">星座を選択</string>
<string name="zodiac_aries">牡羊座</string>
<string name="zodiac_taurus">牡牛座</string>
<string name="zodiac_gemini">双子座</string>
<string name="zodiac_cancer">蟹座</string>
<string name="zodiac_leo">獅子座</string>
<string name="zodiac_virgo">乙女座</string>
<string name="zodiac_libra">天秤座</string>
<string name="zodiac_scorpio">蠍座</string>
<string name="zodiac_sagittarius">射手座</string>
<string name="zodiac_capricorn">山羊座</string>
<string name="zodiac_aquarius">水瓶座</string>
<string name="zodiac_pisces">魚座</string>
<!-- Side Menu --> <!-- Side Menu -->
<string name="scan_qr">さっと動かす</string> <string name="scan_qr">さっと動かす</string>

View File

@@ -12,6 +12,7 @@
<string name="users">用户</string> <string name="users">用户</string>
<string name="like_upper"></string> <string name="like_upper"></string>
<string name="followers_upper">粉丝</string> <string name="followers_upper">粉丝</string>
<string name="posts">帖子</string>
<string name="favourites_upper">收藏</string> <string name="favourites_upper">收藏</string>
<string name="notifications_upper">消息</string> <string name="notifications_upper">消息</string>
<string name="following_upper">关注</string> <string name="following_upper">关注</string>
@@ -51,6 +52,10 @@
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string> <string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
<string name="empty_my_post_title">还没有发布任何动态</string> <string name="empty_my_post_title">还没有发布任何动态</string>
<string name="empty_my_post_content">发布一个动态吧</string> <string name="empty_my_post_content">发布一个动态吧</string>
<string name="story_not_started">故事还没开始</string>
<string name="your_story_not_started">你的故事还没开始</string>
<string name="publish_moment_greeting">发布一条动态,和世界打个招呼吧</string>
<string name="no_image">暂无图片</string>
<string name="edit_profile">编辑</string> <string name="edit_profile">编辑</string>
<string name="share">分享</string> <string name="share">分享</string>
<string name="logout">登出</string> <string name="logout">登出</string>
@@ -153,11 +158,15 @@
<string name="chat_group">群聊</string> <string name="chat_group">群聊</string>
<string name="chat_friend">朋友</string> <string name="chat_friend">朋友</string>
<string name="chat_all">全部</string> <string name="chat_all">全部</string>
<string name="public_label">公开</string>
<string name="private_label">私有</string>
<string name="favourites_null">咦,什么都没有...</string> <string name="favourites_null">咦,什么都没有...</string>
<string name="agent_chat_list_title">智能体聊天</string> <string name="agent_chat_list_title">智能体聊天</string>
<string name="agent_chat_empty_title">AI 在等你的开场白</string> <string name="agent_chat_empty_title">AI 在等你的开场白</string>
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起对话!</string> <string name="agent_chat_empty_subtitle">去首页探索一下,主动发起对话!</string>
<string name="exclusive_ai_waiting">专属AI等你召唤</string>
<string name="ai_companion_not_tool">AI将成为你的伙伴而不是工具</string>
<string name="agent_chat_me_prefix">我: </string> <string name="agent_chat_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string> <string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string> <string name="agent_chat_voice">[语音]</string>
@@ -169,10 +178,15 @@
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string> <string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
<string name="group_chat_empty">没有群聊,宇宙好安静</string> <string name="group_chat_empty">没有群聊,宇宙好安静</string>
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string> <string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
<string name="empty_nothing">空空如也~</string>
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string> <string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string> <string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</string> <string name="friend_chat_empty_title">和朋友,还没有对话哦~</string>
<string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string> <string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string>
<string name="following_empty_title">还没有关注任何灵魂</string>
<string name="following_empty_subtitle">探索一下,总有一个你想靠近的光点 ✨</string>
<string name="follower_empty_title">还没有人关注你呢</string>
<string name="follower_empty_subtitle">试着发信号出来,某人就会被吸引啦~</string>
<string name="friend_chat_me_prefix">我: </string> <string name="friend_chat_me_prefix">我: </string>
<string name="friend_chat_load_failed">加载失败</string> <string name="friend_chat_load_failed">加载失败</string>
<string name="create_group_chat">创建群聊</string> <string name="create_group_chat">创建群聊</string>
@@ -292,9 +306,28 @@
<!-- Edit Profile Extras --> <!-- Edit Profile Extras -->
<string name="mbti_type">MBTI 类型</string> <string name="mbti_type">MBTI 类型</string>
<string name="zodiac">星座</string> <string name="zodiac">星座</string>
<string name="change_cover">更换封面</string>
<string name="personal_intro">个人简介</string>
<string name="error_nickname_empty">昵称不能为空</string>
<string name="error_nickname_too_short">昵称长度不能小于3</string>
<string name="error_nickname_too_long">昵称长度不能大于20</string>
<string name="error_bio_too_long">个人简介长度不能大于100</string>
<string name="error_load_profile_failed">加载用户资料失败,请重试</string>
<string name="save">保存</string> <string name="save">保存</string>
<string name="choose_mbti">选择 MBTI</string> <string name="choose_mbti">选择 MBTI</string>
<string name="choose_zodiac">选择星座</string> <string name="choose_zodiac">选择星座</string>
<string name="zodiac_aries">白羊座</string>
<string name="zodiac_taurus">金牛座</string>
<string name="zodiac_gemini">双子座</string>
<string name="zodiac_cancer">巨蟹座</string>
<string name="zodiac_leo">狮子座</string>
<string name="zodiac_virgo">处女座</string>
<string name="zodiac_libra">天秤座</string>
<string name="zodiac_scorpio">天蝎座</string>
<string name="zodiac_sagittarius">射手座</string>
<string name="zodiac_capricorn">摩羯座</string>
<string name="zodiac_aquarius">水瓶座</string>
<string name="zodiac_pisces">双鱼座</string>
<!-- Side Menu --> <!-- Side Menu -->
<string name="scan_qr">扫一扫</string> <string name="scan_qr">扫一扫</string>

View File

@@ -11,6 +11,7 @@
<string name="users">Users</string> <string name="users">Users</string>
<string name="like_upper">LIKE</string> <string name="like_upper">LIKE</string>
<string name="followers_upper">FOLLOWERS</string> <string name="followers_upper">FOLLOWERS</string>
<string name="posts">Posts</string>
<string name="favourites_upper">FAVOURITES</string> <string name="favourites_upper">FAVOURITES</string>
<string name="favourites_null">Well,nothing </string> <string name="favourites_null">Well,nothing </string>
<string name="notifications_upper">NOTIFICATIONS</string> <string name="notifications_upper">NOTIFICATIONS</string>
@@ -51,6 +52,10 @@
<string name="error_not_accept_term">To provide you with the best service, please read and agree to our User Agreement before registering.</string> <string name="error_not_accept_term">To provide you with the best service, please read and agree to our User Agreement before registering.</string>
<string name="empty_my_post_title">You haven\'t left any tracks yet</string> <string name="empty_my_post_title">You haven\'t left any tracks yet</string>
<string name="empty_my_post_content">Post a moment now</string> <string name="empty_my_post_content">Post a moment now</string>
<string name="story_not_started">Your story hasn\'t started yet</string>
<string name="your_story_not_started">Your story hasn\'t started yet</string>
<string name="publish_moment_greeting">Post a moment and say hello to the world</string>
<string name="no_image">No image</string>
<string name="edit_profile">Edit profile</string> <string name="edit_profile">Edit profile</string>
<string name="share">share</string> <string name="share">share</string>
<string name="logout">Logout</string> <string name="logout">Logout</string>
@@ -143,7 +148,7 @@
<string name="agent_desc_hint">Example: An experienced salesperson who is good at transforming complex products into topics that customers can easily understand and be interested in through humorous language and vivid cases</string> <string name="agent_desc_hint">Example: An experienced salesperson who is good at transforming complex products into topics that customers can easily understand and be interested in through humorous language and vivid cases</string>
<string name="agent_create">Create Agent</string> <string name="agent_create">Create Agent</string>
<string name="moment_content_hint">Need some inspiration for your post? Let AI assist you!</string> <string name="moment_content_hint">Need some inspiration for your post? Let AI assist you!</string>
<string name="moment_ai_co">AI copywriting optimization</string> <string name="moment_ai_co">copywriting optimization</string>
<string name="moment_ai_delete">Delete</string> <string name="moment_ai_delete">Delete</string>
<string name="moment_ai_apply">Apply</string> <string name="moment_ai_apply">Apply</string>
<string name="chat_ai">Ai</string> <string name="chat_ai">Ai</string>
@@ -151,9 +156,13 @@
<string name="chat_friend">Friends</string> <string name="chat_friend">Friends</string>
<string name="chatting_now">people chatting now…</string> <string name="chatting_now">people chatting now…</string>
<string name="chat_all">All</string> <string name="chat_all">All</string>
<string name="public_label">Public</string>
<string name="private_label">Private</string>
<string name="agent_chat_list_title">Agent Chat</string> <string name="agent_chat_list_title">Agent Chat</string>
<string name="agent_chat_empty_title">No Agent Chat</string> <string name="agent_chat_empty_title">No Agent Chat</string>
<string name="agent_chat_empty_subtitle">Start chatting with agents</string> <string name="agent_chat_empty_subtitle">Start chatting with agents</string>
<string name="exclusive_ai_waiting">Exclusive AI waiting for you</string>
<string name="ai_companion_not_tool">AI will be your companion, not a tool</string>
<string name="agent_chat_me_prefix">Me: </string> <string name="agent_chat_me_prefix">Me: </string>
<string name="agent_chat_image">[Image]</string> <string name="agent_chat_image">[Image]</string>
<string name="agent_chat_voice">[Voice]</string> <string name="agent_chat_voice">[Voice]</string>
@@ -165,10 +174,15 @@
<string name="agent_chat_user_info_failed">Failed to get user info: %s</string> <string name="agent_chat_user_info_failed">Failed to get user info: %s</string>
<string name="group_chat_empty">No group chats</string> <string name="group_chat_empty">No group chats</string>
<string name="group_chat_empty_join">You have not joined any group chats yet</string> <string name="group_chat_empty_join">You have not joined any group chats yet</string>
<string name="empty_nothing">Nothing here~</string>
<string name="group_chat_empty_title">The universe is too quiet without group chat messages</string> <string name="group_chat_empty_title">The universe is too quiet without group chat messages</string>
<string name="group_chat_empty_subtitle">Explore interesting theme rooms on the homepage</string> <string name="group_chat_empty_subtitle">Explore interesting theme rooms on the homepage</string>
<string name="friend_chat_empty_title">Have not chatted with friends yet~</string> <string name="friend_chat_empty_title">Have not chatted with friends yet~</string>
<string name="friend_chat_empty_subtitle">Click on the avatar of friend to start chatting instantly.</string> <string name="friend_chat_empty_subtitle">Click on the avatar of friend to start chatting instantly.</string>
<string name="following_empty_title">Not following anyone yet</string>
<string name="following_empty_subtitle">Explore and you\'ll find a light you want to get close to ✨</string>
<string name="follower_empty_title">No one is following you yet</string>
<string name="follower_empty_subtitle">Try sending out a signal, someone will be attracted to you~</string>
<string name="friend_chat_me_prefix">Me: </string> <string name="friend_chat_me_prefix">Me: </string>
<string name="friend_chat_load_failed">Failed to load</string> <string name="friend_chat_load_failed">Failed to load</string>
<string name="create_group_chat">Create Group Chat</string> <string name="create_group_chat">Create Group Chat</string>
@@ -286,9 +300,28 @@
<!-- Edit Profile Extras --> <!-- Edit Profile Extras -->
<string name="mbti_type">MBTI</string> <string name="mbti_type">MBTI</string>
<string name="zodiac">Zodiac</string> <string name="zodiac">Zodiac</string>
<string name="change_cover">Change Cover</string>
<string name="personal_intro">Personal Introduction</string>
<string name="error_nickname_empty">Nickname cannot be empty</string>
<string name="error_nickname_too_short">Nickname length cannot be less than 3</string>
<string name="error_nickname_too_long">Nickname length cannot be greater than 20</string>
<string name="error_bio_too_long">Bio length cannot be greater than 100</string>
<string name="error_load_profile_failed">Failed to load user profile, please try again</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="choose_mbti">Choose MBTI</string> <string name="choose_mbti">Choose MBTI</string>
<string name="choose_zodiac">Choose Zodiac</string> <string name="choose_zodiac">Choose Zodiac</string>
<string name="zodiac_aries">Aries</string>
<string name="zodiac_taurus">Taurus</string>
<string name="zodiac_gemini">Gemini</string>
<string name="zodiac_cancer">Cancer</string>
<string name="zodiac_leo">Leo</string>
<string name="zodiac_virgo">Virgo</string>
<string name="zodiac_libra">Libra</string>
<string name="zodiac_scorpio">Scorpio</string>
<string name="zodiac_sagittarius">Sagittarius</string>
<string name="zodiac_capricorn">Capricorn</string>
<string name="zodiac_aquarius">Aquarius</string>
<string name="zodiac_pisces">Pisces</string>
<!-- Side Menu --> <!-- Side Menu -->
<string name="scan_qr">Scan QR</string> <string name="scan_qr">Scan QR</string>