Compare commits
5 Commits
feat/pr-20
...
feat/pr-20
| Author | SHA1 | Date | |
|---|---|---|---|
| 234587afc9 | |||
| b855acd8d3 | |||
| 391f841f45 | |||
| 2ed5639cbc | |||
| 2cbd2a975f |
@@ -4,6 +4,7 @@ import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentImageEntity
|
||||
import com.aiosman.ravenow.entity.MomentVideoEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.File
|
||||
|
||||
@@ -12,8 +13,12 @@ data class Moment(
|
||||
val id: Long,
|
||||
@SerializedName("textContent")
|
||||
val textContent: String,
|
||||
@SerializedName("url")
|
||||
val url: String? = null,
|
||||
@SerializedName("images")
|
||||
val images: List<Image>,
|
||||
val images: List<Image>? = null,
|
||||
@SerializedName("videos")
|
||||
val videos: List<Video>? = null,
|
||||
@SerializedName("user")
|
||||
val user: User,
|
||||
@SerializedName("likeCount")
|
||||
@@ -24,7 +29,7 @@ data class Moment(
|
||||
val favoriteCount: Long,
|
||||
@SerializedName("isFavorite")
|
||||
val isFavorite: Boolean,
|
||||
@SerializedName("shareCount")
|
||||
@SerializedName("isCommented")
|
||||
val isCommented: Boolean,
|
||||
@SerializedName("commentCount")
|
||||
val commentCount: Long,
|
||||
@@ -47,6 +52,14 @@ data class Moment(
|
||||
val newsLanguage: String? = null,
|
||||
@SerializedName("newsContent")
|
||||
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 {
|
||||
return MomentEntity(
|
||||
@@ -62,7 +75,7 @@ data class Moment(
|
||||
commentCount = commentCount.toInt(),
|
||||
shareCount = 0,
|
||||
favoriteCount = favoriteCount.toInt(),
|
||||
images = images.map {
|
||||
images = images?.map {
|
||||
MomentImageEntity(
|
||||
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
|
||||
@@ -71,10 +84,28 @@ data class Moment(
|
||||
width = it.width,
|
||||
height = it.height
|
||||
)
|
||||
},
|
||||
} ?: emptyList(),
|
||||
authorId = user.id.toInt(),
|
||||
liked = isLiked,
|
||||
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,
|
||||
newsTitle = newsTitle ?: "",
|
||||
@@ -82,7 +113,11 @@ data class Moment(
|
||||
newsSource = newsSource ?: "",
|
||||
newsCategory = newsCategory ?: "",
|
||||
newsLanguage = newsLanguage ?: "",
|
||||
newsContent = newsContent ?: ""
|
||||
newsContent = newsContent ?: "",
|
||||
hasFullText = hasFullText,
|
||||
summary = summary,
|
||||
publishedAt = publishedAt,
|
||||
imageCached = imageCached
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -92,8 +127,26 @@ data class Image(
|
||||
val id: Long,
|
||||
@SerializedName("url")
|
||||
val url: String,
|
||||
@SerializedName("original_url")
|
||||
val originalUrl: String? = null,
|
||||
@SerializedName("directUrl")
|
||||
val directUrl: String? = null,
|
||||
@SerializedName("thumbnail")
|
||||
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")
|
||||
val blurHash: String?,
|
||||
@SerializedName("width")
|
||||
@@ -102,13 +155,68 @@ data class Image(
|
||||
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(
|
||||
@SerializedName("id")
|
||||
val id: Long,
|
||||
@SerializedName("nickName")
|
||||
val nickName: String,
|
||||
@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(
|
||||
|
||||
@@ -800,6 +800,7 @@ interface RaveNowAPI {
|
||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||
@Query("explore") explore: String? = null,
|
||||
@Query("newsFilter") newsFilter: String? = null,
|
||||
@Query("videoFilter") videoFilter: String? = null,
|
||||
): Response<ListContainer<Moment>>
|
||||
|
||||
@Multipart
|
||||
|
||||
@@ -260,6 +260,38 @@ data class MomentImageEntity(
|
||||
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 isFavorite: Boolean = false,
|
||||
// 外部链接
|
||||
val url: String? = null,
|
||||
// 动态视频列表
|
||||
val videos: List<MomentVideoEntity>? = null,
|
||||
// 新闻相关字段
|
||||
val isNews: Boolean = false,
|
||||
val newsTitle: String = "",
|
||||
@@ -307,13 +343,22 @@ data class MomentEntity(
|
||||
val newsSource: String = "",
|
||||
val newsCategory: 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(
|
||||
val explore: Boolean? = false,
|
||||
val timelineId: Int? = null,
|
||||
val authorId : Int? = null,
|
||||
val newsOnly: Boolean? = null
|
||||
val newsOnly: Boolean? = null,
|
||||
val videoOnly: Boolean? = null
|
||||
)
|
||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
override suspend fun fetchData(
|
||||
@@ -327,7 +372,8 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
explore = if (extra.explore == true) "true" else "",
|
||||
timelineId = extra.timelineId,
|
||||
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 {
|
||||
ListContainer(
|
||||
|
||||
@@ -21,6 +21,8 @@ object AccountEditViewModel : ViewModel() {
|
||||
var name by mutableStateOf("")
|
||||
var bio by mutableStateOf("")
|
||||
var imageUrl by mutableStateOf<Uri?>(null)
|
||||
var bannerImageUrl by mutableStateOf<Uri?>(null)
|
||||
var bannerFile by mutableStateOf<File?>(null)
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var croppedBitmap by mutableStateOf<Bitmap?>(null)
|
||||
@@ -82,6 +84,30 @@ object AccountEditViewModel : ViewModel() {
|
||||
it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
|
||||
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 cleanBio = bio.trim().replace("\n", "").replace("\r", "")
|
||||
@@ -89,7 +115,7 @@ object AccountEditViewModel : ViewModel() {
|
||||
val newName = if (cleanName == profile?.nickName) null else cleanName
|
||||
accountService.updateProfile(
|
||||
avatar = newAvatar,
|
||||
banner = null,
|
||||
banner = newBanner,
|
||||
nickName = newName,
|
||||
bio = cleanBio
|
||||
)
|
||||
@@ -100,6 +126,9 @@ object AccountEditViewModel : ViewModel() {
|
||||
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiac)
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
// 清除背景图状态
|
||||
bannerImageUrl = null
|
||||
bannerFile = null
|
||||
// 刷新用户资料
|
||||
reloadProfile()
|
||||
// 刷新个人资料页面的用户资料
|
||||
@@ -116,6 +145,8 @@ object AccountEditViewModel : ViewModel() {
|
||||
name = ""
|
||||
bio = ""
|
||||
imageUrl = null
|
||||
bannerImageUrl = null
|
||||
bannerFile = null
|
||||
croppedBitmap = null
|
||||
isUpdating = false
|
||||
isLoading = false
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
@@ -80,6 +81,10 @@ fun MbtiSelectScreen() {
|
||||
isSelected = mbti == currentMbti,
|
||||
onClick = {
|
||||
model.mbti = mbti
|
||||
// 立即保存到本地存储,确保选择后立即生效
|
||||
AppState.UserId?.let { uid ->
|
||||
com.aiosman.ravenow.AppStore.setUserMbti(uid, mbti)
|
||||
}
|
||||
navController.navigateUp()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -29,24 +29,55 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
|
||||
// 星座列表
|
||||
val ZODIAC_SIGNS = listOf(
|
||||
"白羊座", "金牛座", "双子座", "巨蟹座",
|
||||
"狮子座", "处女座", "天秤座", "天蝎座",
|
||||
"射手座", "摩羯座", "水瓶座", "双鱼座"
|
||||
// 星座资源ID列表
|
||||
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
|
||||
fun ZodiacSelectScreen() {
|
||||
val navController = LocalNavController.current
|
||||
val appColors = LocalAppTheme.current
|
||||
val model = AccountEditViewModel
|
||||
val currentZodiac = model.zodiac
|
||||
val currentZodiacResId = findZodiacResId(model.zodiac)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -70,12 +101,20 @@ fun ZodiacSelectScreen() {
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
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(
|
||||
zodiac = zodiac,
|
||||
isSelected = zodiac == currentZodiac,
|
||||
zodiac = zodiacText,
|
||||
zodiacResId = zodiacResId,
|
||||
isSelected = zodiacResId == currentZodiacResId,
|
||||
onClick = {
|
||||
model.zodiac = zodiac
|
||||
// 保存当前语言的星座文本
|
||||
model.zodiac = zodiacText
|
||||
// 立即保存到本地存储,确保选择后立即生效
|
||||
AppState.UserId?.let { uid ->
|
||||
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiacText)
|
||||
}
|
||||
navController.navigateUp()
|
||||
}
|
||||
)
|
||||
@@ -88,6 +127,7 @@ fun ZodiacSelectScreen() {
|
||||
@Composable
|
||||
fun ZodiacItem(
|
||||
zodiac: String,
|
||||
zodiacResId: Int,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
||||
@@ -72,6 +72,7 @@ import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
|
||||
import android.widget.Toast
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,10 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
quality = 100
|
||||
) { uri, file ->
|
||||
// 处理选中的图片
|
||||
// 保存到 ViewModel 中,等待保存时一起上传
|
||||
model.bannerImageUrl = uri
|
||||
model.bannerFile = file
|
||||
// 如果提供了回调,也调用它(用于个人主页直接更新)
|
||||
onUpdateBanner?.invoke(uri, file, context)
|
||||
}
|
||||
|
||||
@@ -104,10 +109,21 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
// 去除换行符,确保昵称不包含换行
|
||||
val cleanValue = value.replace("\n", "").replace("\r", "")
|
||||
model.name = cleanValue
|
||||
// 实时验证,但不显示错误(只在保存时显示)
|
||||
usernameError = when {
|
||||
cleanValue.trim().isEmpty() -> "昵称不能为空"
|
||||
cleanValue.length < 3 -> "昵称长度不能小于3"
|
||||
cleanValue.length > 20 -> "昵称长度不能大于20"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -118,8 +134,17 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
// 去除换行符,确保个人简介不包含换行
|
||||
val cleanValue = value.replace("\n", "").replace("\r", "")
|
||||
model.bio = cleanValue
|
||||
// 实时验证,但不显示错误(只在保存时显示)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -146,21 +171,21 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
model.reloadProfile()
|
||||
}
|
||||
|
||||
// 设置状态栏为透明,使用浅色图标(因为顶部背景是深色图片)
|
||||
// 设置状态栏为透明,根据暗色模式决定图标颜色
|
||||
val systemUiController = rememberSystemUiController()
|
||||
LaunchedEffect(Unit) {
|
||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
|
||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
|
||||
}
|
||||
|
||||
StatusBarMaskLayout(
|
||||
modifier = Modifier.background(Color(0xFFFAF9FB)),
|
||||
darkIcons = false, // 浅色图标(白色),因为顶部背景是深色
|
||||
modifier = Modifier.background(appColors.background),
|
||||
darkIcons = !AppState.darkMode, // 根据暗色模式决定图标颜色
|
||||
maskBoxBackgroundColor = Color.Transparent
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFFAF9FB))
|
||||
.background(appColors.background)
|
||||
) {
|
||||
when {
|
||||
model.isLoading -> {
|
||||
@@ -179,7 +204,8 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 顶部背景区域(圆角在底部,覆盖状态栏)
|
||||
val banner = model.profile?.banner
|
||||
// 优先显示新选择的背景图,如果没有则显示原有的背景图
|
||||
val banner = model.bannerImageUrl?.toString() ?: model.profile?.banner
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -230,21 +256,13 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
) {
|
||||
// 更换封面图标
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
// TODO: 添加更换封面暗色模式图标
|
||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
||||
} else {
|
||||
// TODO: 添加更换封面亮色模式图标
|
||||
R.mipmap.fengm // 临时占位,需替换为实际图标
|
||||
}
|
||||
),
|
||||
painter = painterResource(id = R.mipmap.fengm),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
Text(
|
||||
text = "更换封面",
|
||||
text = stringResource(R.string.change_cover),
|
||||
fontSize = 12.sp,
|
||||
color = Color.White
|
||||
)
|
||||
@@ -288,7 +306,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "编辑资料",
|
||||
text = stringResource(R.string.edit_profile_info),
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
@@ -318,7 +336,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.clip(CircleShape)
|
||||
.border(2.4.dp, Color(0xFFFAF9FB), CircleShape),
|
||||
.border(2.4.dp, appColors.background, CircleShape),
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
@@ -338,15 +356,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
// TODO: 添加编辑头像暗色模式图标
|
||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
||||
} else {
|
||||
// TODO: 添加编辑头像亮色模式图标
|
||||
R.mipmap.bi // 临时占位,需替换为实际图标
|
||||
}
|
||||
),
|
||||
painter = painterResource(id = R.mipmap.bi),
|
||||
contentDescription = "Edit Avatar",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.White
|
||||
@@ -365,7 +375,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
) {
|
||||
// 昵称输入框
|
||||
ProfileInfoCard(
|
||||
label = "昵称",
|
||||
label = stringResource(R.string.nickname),
|
||||
value = model.name,
|
||||
placeholder = "Value",
|
||||
onValueChange = { onNicknameChange(it) },
|
||||
@@ -376,7 +386,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
|
||||
// 个人简介输入框
|
||||
ProfileInfoCard(
|
||||
label = "个人简介",
|
||||
label = stringResource(R.string.personal_intro),
|
||||
value = model.bio,
|
||||
placeholder = "Welcome to my fantiac word i will show you something about magic",
|
||||
onValueChange = { onBioChange(it) },
|
||||
@@ -390,11 +400,11 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White)
|
||||
.background(appColors.secondaryBackground)
|
||||
) {
|
||||
// MBTI 类型
|
||||
ProfileSelectItem(
|
||||
label = "MBTI 类型",
|
||||
label = stringResource(R.string.mbti_type),
|
||||
value = model.mbti ?: "ENFP",
|
||||
iconColor = Color(0xFF7C45ED),
|
||||
iconResDark = null, // TODO: 添加MBTI暗色模式图标
|
||||
@@ -411,14 +421,19 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(0.3.dp)
|
||||
.background(Color(0x41413C43).copy(alpha = 0.2f))
|
||||
.background(appColors.divider)
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
// 星座(使用当前图标)
|
||||
ProfileSelectItem(
|
||||
label = "星座",
|
||||
value = model.zodiac ?: "白羊座",
|
||||
label = stringResource(R.string.zodiac),
|
||||
value = model.zodiac?.let { storedZodiac ->
|
||||
// 尝试找到对应的资源ID并显示当前语言的文本
|
||||
findZodiacResId(storedZodiac)?.let { resId ->
|
||||
stringResource(resId)
|
||||
} ?: storedZodiac // 如果找不到,显示原始存储的值
|
||||
} ?: stringResource(R.string.zodiac_aries),
|
||||
iconColor = Color(0xFFFFCC00),
|
||||
iconResDark = R.mipmap.frame_4, // 星座暗色模式图标
|
||||
iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标
|
||||
@@ -448,10 +463,28 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
)
|
||||
)
|
||||
.debouncedClickable(
|
||||
enabled = validate() && !model.isUpdating,
|
||||
enabled = !model.isUpdating,
|
||||
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.isUpdating = true
|
||||
model.updateUserProfile(context)
|
||||
@@ -462,12 +495,11 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
model.isUpdating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "保存",
|
||||
text = stringResource(R.string.save),
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.White
|
||||
@@ -483,7 +515,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "加载用户资料失败,请重试",
|
||||
text = stringResource(R.string.error_load_profile_failed),
|
||||
color = appColors.text
|
||||
)
|
||||
}
|
||||
@@ -505,13 +537,12 @@ fun ProfileInfoCard(
|
||||
isMultiline: Boolean = false
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp,个人简介66dp
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White),
|
||||
.background(appColors.secondaryBackground),
|
||||
contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart
|
||||
) {
|
||||
Row(
|
||||
@@ -526,7 +557,7 @@ fun ProfileInfoCard(
|
||||
text = label,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Black,
|
||||
color = appColors.text,
|
||||
modifier = Modifier.width(100.dp)
|
||||
)
|
||||
|
||||
@@ -541,7 +572,7 @@ fun ProfileInfoCard(
|
||||
text = placeholder,
|
||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0x993C3C43),
|
||||
color = appColors.secondaryText,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -553,9 +584,9 @@ fun ProfileInfoCard(
|
||||
textStyle = androidx.compose.ui.text.TextStyle(
|
||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
||||
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,
|
||||
singleLine = !isMultiline
|
||||
)
|
||||
@@ -576,6 +607,7 @@ fun ProfileSelectItem(
|
||||
iconResDark: Int? = null,
|
||||
iconResLight: Int? = null
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -593,7 +625,8 @@ fun ProfileSelectItem(
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (AppState.darkMode) {
|
||||
iconResDark ?: R.mipmap.frame_4 // 使用传入的暗色模式图标,或默认占位
|
||||
// 暗色模式下使用和亮色模式一样的图标
|
||||
iconResLight ?: iconResDark ?: R.mipmap.naoz
|
||||
} else {
|
||||
iconResLight ?: R.mipmap.naoz // 使用传入的亮色模式图标,或默认占位
|
||||
}
|
||||
@@ -607,7 +640,7 @@ fun ProfileSelectItem(
|
||||
text = label,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Black
|
||||
color = appColors.text
|
||||
)
|
||||
}
|
||||
|
||||
@@ -619,14 +652,14 @@ fun ProfileSelectItem(
|
||||
text = value,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0x993C3C43)
|
||||
color = appColors.secondaryText
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowForward,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(8.dp),
|
||||
tint = Color(0x4D3C3C43)
|
||||
tint = appColors.secondaryText
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
|
||||
@@ -36,6 +37,13 @@ fun AnimatedCounter(count: Int, modifier: Modifier = Modifier, fontSize: Int = 2
|
||||
)
|
||||
}
|
||||
) { 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -426,7 +426,6 @@ fun MomentOperateBtn(count: String, content: @Composable () -> Unit) {
|
||||
fontSize = 14,
|
||||
modifier = Modifier
|
||||
.padding(start = 7.dp)
|
||||
.width(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -131,17 +133,25 @@ fun FollowerListScreen(userId: Int) {
|
||||
)
|
||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
||||
androidx.compose.material.Text(
|
||||
text = "还没有人关注你呢",
|
||||
text = stringResource(R.string.follower_empty_title),
|
||||
color = appColors.text,
|
||||
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))
|
||||
androidx.compose.material.Text(
|
||||
text = "试着发信号出来,某人就会被吸引啦~",
|
||||
text = stringResource(R.string.follower_empty_subtitle),
|
||||
color = appColors.text,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
fontWeight = FontWeight.W400,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
@@ -122,17 +124,25 @@ fun FollowerNoticeScreen() {
|
||||
)
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
androidx.compose.material.Text(
|
||||
text = "还没有人关注你呢",
|
||||
text = stringResource(R.string.follower_empty_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
androidx.compose.material.Text(
|
||||
text = "试着发信号出来,某人就会被吸引啦~",
|
||||
text = stringResource(R.string.follower_empty_subtitle),
|
||||
color = AppColors.text,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
fontWeight = FontWeight.W400,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -133,17 +135,25 @@ fun FollowingListScreen(userId: Int) {
|
||||
)
|
||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
||||
androidx.compose.material.Text(
|
||||
text = "还没有关注任何灵魂",
|
||||
text = stringResource(R.string.following_empty_title),
|
||||
color = appColors.text,
|
||||
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))
|
||||
androidx.compose.material.Text(
|
||||
text = "探索一下,总有一个你想靠近的光点 ✨",
|
||||
text = stringResource(R.string.following_empty_subtitle),
|
||||
color = appColors.secondaryText,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
fontWeight = FontWeight.W400,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ import com.aiosman.ravenow.data.api.AgentRule
|
||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
||||
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.entity.ChatNotification
|
||||
import com.aiosman.ravenow.entity.GroupInfo
|
||||
|
||||
@@ -519,14 +519,31 @@ fun SideMenuContent(
|
||||
var messageNotificationEnabled by remember { mutableStateOf(true) }
|
||||
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
|
||||
val overlayColor = Color.Black.copy(alpha = 0.6f)
|
||||
// 卡片背景色 白色
|
||||
val cardBackgroundColor = Color.White
|
||||
// 跟随系统文字颜色 #979499
|
||||
val followSystemTextColor = Color(0xFF979499)
|
||||
// 卡片背景色 - 根据暗色模式适配
|
||||
val cardBackgroundColor = if (darkModeEnabled) {
|
||||
appColors.background // 暗色模式:深色背景
|
||||
} else {
|
||||
Color.White // 亮色模式:白色
|
||||
}
|
||||
// 文字颜色 - 根据暗色模式适配
|
||||
val textColor = appColors.text
|
||||
// 图标颜色 - 根据暗色模式适配
|
||||
val iconColor = appColors.text
|
||||
// 跟随系统文字颜色 - 根据暗色模式适配
|
||||
val followSystemTextColor = appColors.secondaryText
|
||||
// 开关开启颜色 #7C45ED
|
||||
val switchActiveColor = Color(0xFF7C45ED)
|
||||
|
||||
@@ -579,14 +596,14 @@ fun SideMenuContent(
|
||||
painter = painterResource(id = R.mipmap.sao),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
colorFilter = ColorFilter.tint(iconColor)
|
||||
)
|
||||
}
|
||||
// 绝对定位的"扫一扫"文字:上方71.5dp,右侧66dp
|
||||
Text(
|
||||
text = stringResource(R.string.scan_qr),
|
||||
fontSize = 14.sp,
|
||||
color = Color.Black,
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.offset(x = (-66).dp, y = 91.5.dp)
|
||||
@@ -602,7 +619,7 @@ fun SideMenuContent(
|
||||
.noRippleClickable {
|
||||
// TODO: 实现QR码功能
|
||||
},
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
colorFilter = ColorFilter.tint(iconColor)
|
||||
)
|
||||
|
||||
// 菜单选项卡片组 - 第一组卡片上方距离上方108pt(绝对定位)
|
||||
@@ -616,6 +633,8 @@ fun SideMenuContent(
|
||||
// 第一组卡片:编辑资料、账号安全、收藏
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
textColor = textColor,
|
||||
iconColor = iconColor,
|
||||
width = 270.dp,
|
||||
height = 164.dp,
|
||||
items = listOf(
|
||||
@@ -655,6 +674,8 @@ fun SideMenuContent(
|
||||
// 第二组卡片:暗色模式、消息通知
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
textColor = textColor,
|
||||
iconColor = iconColor,
|
||||
width = 270.dp,
|
||||
height = 112.dp, // 根据设计图,第二组卡片高度为112dp
|
||||
items = listOf(
|
||||
@@ -709,6 +730,8 @@ fun SideMenuContent(
|
||||
// 第三组卡片:关于派派、反馈、退出登录
|
||||
MenuCard(
|
||||
backgroundColor = cardBackgroundColor,
|
||||
textColor = textColor,
|
||||
iconColor = iconColor,
|
||||
width = 270.dp,
|
||||
height = 164.dp,
|
||||
items = listOf(
|
||||
@@ -776,6 +799,8 @@ data class MenuItem(
|
||||
@Composable
|
||||
fun MenuCard(
|
||||
backgroundColor: Color,
|
||||
textColor: Color,
|
||||
iconColor: Color,
|
||||
items: List<MenuItem>,
|
||||
width: 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),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
MenuItemRow(item = item, compact = height != null) // 传递compact参数
|
||||
MenuItemRow(item = item, compact = height != null, textColor = textColor, iconColor = iconColor) // 传递颜色参数
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
|
||||
fun MenuItemRow(item: MenuItem, compact: Boolean = false, textColor: Color, iconColor: Color) {
|
||||
val appColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -825,12 +851,12 @@ fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
|
||||
painter = painterResource(id = item.icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
colorFilter = ColorFilter.tint(iconColor)
|
||||
)
|
||||
Text(
|
||||
text = item.label,
|
||||
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),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color(0xFF111213))
|
||||
colorFilter = ColorFilter.tint(appColors.text)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -981,9 +983,13 @@ fun ChatRoomCard(
|
||||
Text(
|
||||
text = "${chatRoom.memberCount} ${stringResource(R.string.chatting_now)}",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.alpha(0.6f),
|
||||
modifier = Modifier
|
||||
.alpha(0.6f)
|
||||
.weight(1f),
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -108,13 +109,21 @@ fun AgentChatListScreen() {
|
||||
text = stringResource(R.string.agent_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.agent_chat_empty_subtitle),
|
||||
color = AppColors.secondaryText,
|
||||
fontSize = 14.sp
|
||||
fontSize = 14.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
else {
|
||||
@@ -130,13 +139,21 @@ fun AgentChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_no_network_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||
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))
|
||||
ReloadButton(
|
||||
|
||||
@@ -186,13 +186,21 @@ fun AllChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_empty_subtitle),
|
||||
color = AppColors.secondaryText,
|
||||
fontSize = 14.sp
|
||||
fontSize = 14.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
@@ -207,13 +215,21 @@ fun AllChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_no_network_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||
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))
|
||||
ReloadButton(
|
||||
|
||||
@@ -96,13 +96,21 @@ fun FriendChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_empty_subtitle),
|
||||
color = AppColors.secondaryText,
|
||||
fontSize = 14.sp
|
||||
fontSize = 14.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}else {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
@@ -117,13 +125,21 @@ fun FriendChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_no_network_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||
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))
|
||||
ReloadButton(
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -88,13 +89,21 @@ fun GroupChatListScreen() {
|
||||
text = stringResource(R.string.group_chat_empty),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.group_chat_empty_join),
|
||||
color = AppColors.secondaryText,
|
||||
fontSize = 14.sp
|
||||
fontSize = 14.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}else {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
@@ -109,13 +118,21 @@ fun GroupChatListScreen() {
|
||||
text = stringResource(R.string.friend_chat_no_network_title),
|
||||
color = AppColors.text,
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||
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))
|
||||
ReloadButton(
|
||||
|
||||
@@ -59,6 +59,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import com.aiosman.ravenow.ui.composables.TabItem
|
||||
import com.aiosman.ravenow.ui.composables.UnderlineTabItem
|
||||
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 =
|
||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
// 现在有6个tab:推荐、短视频、新闻、探索、关注、热门
|
||||
val tabCount = 6
|
||||
// 根据登录状态设置标签页数量:游客模式5个tab,非游客模式6个tab
|
||||
val tabCount = if (AppStore.isGuest) 5 else 6
|
||||
var pagerState = rememberPagerState { tabCount }
|
||||
var scope = rememberCoroutineScope()
|
||||
Column(
|
||||
@@ -173,14 +174,14 @@ fun MomentsList() {
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// 热门标签 (游客模式)
|
||||
// 热门标签 (游客模式) - 在游客模式下,热门标签对应第3页
|
||||
UnderlineTabItem(
|
||||
text = stringResource(R.string.index_hot),
|
||||
isSelected = pagerState.currentPage == 4,
|
||||
isSelected = pagerState.currentPage == 3,
|
||||
onClick = {
|
||||
tabDebouncer {
|
||||
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(
|
||||
text = stringResource(R.string.tab_news),
|
||||
isSelected = pagerState.currentPage == 5,
|
||||
isSelected = pagerState.currentPage == newsPageIndex,
|
||||
onClick = {
|
||||
tabDebouncer {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(5)
|
||||
pagerState.animateScrollToPage(newsPageIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,6 +236,7 @@ fun MomentsList() {
|
||||
}
|
||||
1 -> {
|
||||
// 短视频页面
|
||||
ShortVideoScreen()
|
||||
}
|
||||
2 -> {
|
||||
// 动态页面 - 暂时显示时间线内容
|
||||
@@ -248,16 +251,22 @@ fun MomentsList() {
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
// 热门页面 (仅非游客用户)
|
||||
// 热门页面 (仅非游客用户) 或 新闻页面 (游客用户)
|
||||
if (AppStore.isGuest) {
|
||||
NewsScreen()
|
||||
} else {
|
||||
HotMomentsList()
|
||||
}
|
||||
}
|
||||
5 -> {
|
||||
// 新闻页面
|
||||
// 新闻页面 (仅非游客用户)
|
||||
if (!AppStore.isGuest) {
|
||||
NewsScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun CustomTabItem(
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
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.items
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
@@ -137,8 +139,18 @@ fun DiscoverView() {
|
||||
val debouncer = rememberDebouncer()
|
||||
|
||||
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 estimatedCharsPerLine = 20
|
||||
val estimatedLines = (textContent.length / estimatedCharsPerLine) + 1
|
||||
minOf(estimatedLines, 2) // 最多2行
|
||||
} else {
|
||||
@@ -146,21 +158,20 @@ fun DiscoverView() {
|
||||
}
|
||||
|
||||
val baseHeight = 200.dp
|
||||
val singleLineTextHeight = 20.dp
|
||||
val doubleLineTextHeight = 40.dp
|
||||
val singleLineTextHeight = 24.dp // 增加高度以适应英文/日文
|
||||
val doubleLineTextHeight = 44.dp // 增加高度以适应英文/日文
|
||||
val authorInfoHeight = 25.dp
|
||||
val paddingHeight = 10.dp
|
||||
val paddingHeight2 =3.dp
|
||||
val paddingHeight2 = 3.dp
|
||||
val totalHeight = baseHeight + when (textLines) {
|
||||
0 -> authorInfoHeight + paddingHeight
|
||||
1 -> singleLineTextHeight + authorInfoHeight + paddingHeight
|
||||
else -> doubleLineTextHeight + authorInfoHeight +paddingHeight2
|
||||
else -> doubleLineTextHeight + authorInfoHeight + paddingHeight2
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(totalHeight)
|
||||
.padding(2.dp)
|
||||
.noRippleClickable {
|
||||
debouncer {
|
||||
@@ -173,7 +184,9 @@ fun DiscoverView() {
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = momentItem.images[0].thumbnail,
|
||||
@@ -193,9 +206,9 @@ fun DiscoverView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(totalHeight - baseHeight)
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
) {
|
||||
// 文本内容区域,限制最大高度
|
||||
if (momentItem.momentTextContent.isNotEmpty()) {
|
||||
androidx.compose.material3.Text(
|
||||
text = momentItem.momentTextContent,
|
||||
@@ -203,13 +216,19 @@ fun DiscoverView() {
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 25.dp) // 最小高度确保完整显示,自适应避免被挤压
|
||||
.padding(top = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@@ -225,7 +244,9 @@ fun DiscoverView() {
|
||||
|
||||
androidx.compose.material3.Text(
|
||||
text = momentItem.nickname,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp)
|
||||
.weight(1f),
|
||||
fontSize = 11.sp,
|
||||
color = AppColors.text.copy(alpha = 0.6f),
|
||||
maxLines = 1,
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,9 +604,19 @@ fun TopNavigationBar(
|
||||
val appColors = LocalAppTheme.current
|
||||
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(
|
||||
modifier = Modifier
|
||||
@@ -625,7 +635,12 @@ fun TopNavigationBar(
|
||||
val baseColor = remember(backgroundAlpha) {
|
||||
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
|
||||
|
||||
// 使用平滑的插值函数,让整个过渡更自然
|
||||
@@ -645,6 +660,7 @@ fun TopNavigationBar(
|
||||
alpha = alpha.coerceIn(0f, initialDarkAlpha)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -703,7 +719,7 @@ fun TopNavigationBar(
|
||||
text = numberFormat.format(interactionCount),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
color = Color.Black, // 文字始终为黑色
|
||||
color = if (AppState.darkMode) Color.White else Color.Black, // 暗色模式下为白色,亮色模式下为黑色
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
@@ -744,6 +760,9 @@ fun TopNavigationBar(
|
||||
// 如果不是主页面,显示返回按钮和用户信息
|
||||
if (!isMain) {
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
|
||||
// 判断是否有背景图
|
||||
val hasBanner = profile?.banner != null
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
@@ -751,6 +770,8 @@ fun TopNavigationBar(
|
||||
.padding(top = statusBarPadding.calculateTopPadding()),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 返回按钮:深色模式下为白色,亮色模式下为黑色
|
||||
val backButtonColor = if (AppState.darkMode) Color.White else Color.Black
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "Back",
|
||||
@@ -759,28 +780,22 @@ fun TopNavigationBar(
|
||||
navController.navigateUp()
|
||||
}
|
||||
.size(24.dp),
|
||||
colorFilter = ColorFilter.tint(Color.White)
|
||||
colorFilter = ColorFilter.tint(backButtonColor) // 深色模式下为白色,亮色模式下为黑色
|
||||
)
|
||||
|
||||
// 未设置背景的用户(自己的主页且没有背景图)显示昵称
|
||||
if (isSelf && !hasBanner && profile != null) {
|
||||
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 = profile?.nickName ?: "",
|
||||
text = profile.nickName ?: "",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = Color.White
|
||||
color = backButtonColor // 深色模式下为白色,亮色模式下为黑色
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分享图标(向上箭头)
|
||||
|
||||
@@ -42,6 +42,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
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.ui.composables.rememberDebouncer
|
||||
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))
|
||||
|
||||
Text(
|
||||
text = "你的故事还没开始",
|
||||
text = stringResource(R.string.your_story_not_started),
|
||||
fontSize = 16.sp,
|
||||
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))
|
||||
|
||||
Text(
|
||||
text = "发布一条动态,和世界打个招呼吧",
|
||||
text = stringResource(R.string.publish_moment_greeting),
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
fontWeight = FontWeight.W400
|
||||
fontWeight = FontWeight.W400,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -221,7 +231,7 @@ fun GalleryGrid(
|
||||
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
|
||||
) {
|
||||
itemsIndexed(moments) { idx, moment ->
|
||||
if (moment != null) {
|
||||
if (moment != null && moment.images.isNotEmpty()) {
|
||||
val itemDebouncer = rememberDebouncer()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -27,27 +26,25 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.R
|
||||
|
||||
@Composable
|
||||
fun GroupChatEmptyContent() {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 分割线(紧贴上方栏)
|
||||
Divider(
|
||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 分段控制器
|
||||
@@ -71,10 +68,14 @@ fun GroupChatEmptyContent() {
|
||||
|
||||
// 空状态文本
|
||||
Text(
|
||||
text = "空空如也~",
|
||||
text = stringResource(R.string.empty_nothing),
|
||||
fontSize = 16.sp,
|
||||
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,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(32.dp),
|
||||
@@ -94,30 +96,33 @@ private fun SegmentedControl(
|
||||
) {
|
||||
// 全部
|
||||
SegmentButton(
|
||||
text = "全部",
|
||||
text = stringResource(R.string.chat_all),
|
||||
isSelected = selectedIndex == 0,
|
||||
onClick = { onSegmentSelected(0) },
|
||||
width = 54.dp
|
||||
width = 54.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 公开
|
||||
SegmentButton(
|
||||
text = "公开",
|
||||
text = stringResource(R.string.public_label),
|
||||
isSelected = selectedIndex == 1,
|
||||
onClick = { onSegmentSelected(1) },
|
||||
width = 59.dp
|
||||
width = 59.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 私有
|
||||
SegmentButton(
|
||||
text = "私有",
|
||||
text = stringResource(R.string.private_label),
|
||||
isSelected = selectedIndex == 2,
|
||||
onClick = { onSegmentSelected(2) },
|
||||
width = 54.dp
|
||||
width = 54.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -127,7 +132,8 @@ private fun SegmentButton(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
width: androidx.compose.ui.unit.Dp
|
||||
width: androidx.compose.ui.unit.Dp,
|
||||
appColors: com.aiosman.ravenow.AppThemeData
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -135,7 +141,7 @@ private fun SegmentButton(
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) {
|
||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
||||
appColors.checkedBackground // 使用选中背景色(暗色模式下是白色,亮色模式下是黑色)
|
||||
} else {
|
||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
||||
},
|
||||
@@ -148,7 +154,11 @@ private fun SegmentButton(
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
||||
color = if (isSelected) {
|
||||
appColors.checkedText // 选中时使用选中文本颜色(暗色模式下是黑色,亮色模式下是白色)
|
||||
} else {
|
||||
appColors.text // 未选中时使用文本颜色
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -209,13 +209,6 @@ fun AgentEmptyContentWithSegments() {
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 分割线(紧贴上方栏)
|
||||
Divider(
|
||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 分段控制器
|
||||
@@ -247,19 +240,27 @@ fun AgentEmptyContentWithSegments() {
|
||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
||||
|
||||
Text(
|
||||
text = "专属AI等你召唤",
|
||||
text = stringResource(R.string.exclusive_ai_waiting),
|
||||
fontSize = 16.sp,
|
||||
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))
|
||||
|
||||
Text(
|
||||
text = "AI将成为你的伙伴,而不是工具",
|
||||
text = stringResource(R.string.ai_companion_not_tool),
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
fontWeight = FontWeight.W400
|
||||
fontWeight = FontWeight.W400,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
@@ -303,6 +304,7 @@ private fun AgentSegmentedControl(
|
||||
onSegmentSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(32.dp),
|
||||
@@ -311,30 +313,33 @@ private fun AgentSegmentedControl(
|
||||
) {
|
||||
// 全部
|
||||
AgentSegmentButton(
|
||||
text = "全部",
|
||||
text = stringResource(R.string.chat_all),
|
||||
isSelected = selectedIndex == 0,
|
||||
onClick = { onSegmentSelected(0) },
|
||||
width = 54.dp
|
||||
width = 54.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 公开
|
||||
AgentSegmentButton(
|
||||
text = "公开",
|
||||
text = stringResource(R.string.public_label),
|
||||
isSelected = selectedIndex == 1,
|
||||
onClick = { onSegmentSelected(1) },
|
||||
width = 59.dp
|
||||
width = 59.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 私有
|
||||
AgentSegmentButton(
|
||||
text = "私有",
|
||||
text = stringResource(R.string.private_label),
|
||||
isSelected = selectedIndex == 2,
|
||||
onClick = { onSegmentSelected(2) },
|
||||
width = 54.dp
|
||||
width = 54.dp,
|
||||
appColors = AppColors
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -344,7 +349,8 @@ private fun AgentSegmentButton(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
width: androidx.compose.ui.unit.Dp
|
||||
width: androidx.compose.ui.unit.Dp,
|
||||
appColors: com.aiosman.ravenow.AppThemeData
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -352,7 +358,7 @@ private fun AgentSegmentButton(
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) {
|
||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
||||
appColors.checkedBackground // 使用选中背景色(暗色模式下是白色,亮色模式下是黑色)
|
||||
} else {
|
||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
||||
},
|
||||
@@ -365,7 +371,11 @@ private fun AgentSegmentButton(
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
||||
color = if (isSelected) {
|
||||
appColors.checkedText // 选中时使用选中文本颜色(暗色模式下是黑色,亮色模式下是白色)
|
||||
} else {
|
||||
appColors.text // 未选中时使用文本颜色
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,14 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -52,7 +45,7 @@ fun UserContentPageIndicator(
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
// 图片/相册 Tab
|
||||
// 动态 Tab
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
@@ -64,15 +57,24 @@ fun UserContentPageIndicator(
|
||||
}
|
||||
.padding(vertical = 12.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_images),
|
||||
contentDescription = "Gallery",
|
||||
tint = if (pagerState.currentPage == 0) AppColors.text else AppColors.text.copy(alpha = 0.6f),
|
||||
modifier = Modifier.size(24.dp)
|
||||
Text(
|
||||
text = stringResource(R.string.index_dynamic),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = if (pagerState.currentPage == 0) FontWeight.SemiBold else FontWeight.Medium,
|
||||
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) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -85,40 +87,54 @@ fun UserContentPageIndicator(
|
||||
}
|
||||
.padding(vertical = 12.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_nav_ai),
|
||||
contentDescription = "Agents",
|
||||
tint = if (pagerState.currentPage == 1) AppColors.text else AppColors.text.copy(alpha = 0.6f),
|
||||
modifier = Modifier.size(24.dp)
|
||||
Text(
|
||||
text = stringResource(R.string.chat_ai),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = if (pagerState.currentPage == 1) FontWeight.SemiBold else FontWeight.Medium,
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下划线指示器
|
||||
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
|
||||
)
|
||||
)
|
||||
// 群聊 Tab (只在非智能体用户时显示)
|
||||
if (showAgentTab) {
|
||||
Box(
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.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)
|
||||
.background(
|
||||
if (pagerState.currentPage == 1) AppColors.text else Color.Transparent
|
||||
)
|
||||
.background(AppColors.text)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -117,15 +118,15 @@ fun UserItem(
|
||||
text = postCount.toString(),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "帖子",
|
||||
text = stringResource(R.string.posts),
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
@@ -152,15 +153,15 @@ fun UserItem(
|
||||
text = formattedFollowerCount,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "粉丝",
|
||||
text = stringResource(R.string.followers_upper),
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
@@ -188,15 +189,15 @@ fun UserItem(
|
||||
text = accountProfileEntity.followingCount.toString(),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "关注",
|
||||
text = stringResource(R.string.following_upper),
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF000000),
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
@@ -216,7 +217,7 @@ fun UserItem(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 22.sp,
|
||||
letterSpacing = (-0.3).sp,
|
||||
color = Color(0xFF000000)
|
||||
color = AppColors.text
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
@@ -226,7 +227,7 @@ fun UserItem(
|
||||
Text(
|
||||
text = accountProfileEntity.bio,
|
||||
fontSize = 13.sp,
|
||||
color = Color(0x993C3C43), // 60/255, 60/255, 67/255, alpha 0.6
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -234,7 +235,7 @@ fun UserItem(
|
||||
Text(
|
||||
text = "Welcome to my fantiac word i will show you something about magic",
|
||||
fontSize = 13.sp,
|
||||
color = Color(0x993C3C43),
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -257,7 +258,7 @@ fun UserItem(
|
||||
ProfileTag(
|
||||
text = mbti,
|
||||
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(
|
||||
text = zodiac,
|
||||
backgroundColor = Color(0x33FFCC00), // 255/255, 204/255, 0/255, alpha 0.2
|
||||
textColor = Color(0xFF000000)
|
||||
textColor = AppColors.text
|
||||
)
|
||||
}
|
||||
|
||||
// 编辑标签(仅自己可见)
|
||||
if (isSelf) {
|
||||
ProfileTag(
|
||||
text = "编辑",
|
||||
text = stringResource(R.string.edit_profile),
|
||||
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 = {
|
||||
EditIcon(
|
||||
color = Color(0xFF9284BD),
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
},
|
||||
@@ -333,7 +334,7 @@ private fun EditIcon(
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.bi),
|
||||
contentDescription = "编辑",
|
||||
contentDescription = stringResource(R.string.edit_profile),
|
||||
modifier = modifier,
|
||||
colorFilter = ColorFilter.tint(color)
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -61,27 +62,65 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ShortViewCompose(
|
||||
videoItemsUrl: List<String>,
|
||||
videoItemsUrl: List<String> = emptyList(),
|
||||
videoMoments: List<MomentEntity> = emptyList(),
|
||||
clickItemPosition: Int = 0,
|
||||
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 {
|
||||
remember {
|
||||
PagerState(clickItemPosition, 0, videoItemsUrl.size - 1)
|
||||
// 优先使用 videoMoments,如果没有则使用 videoItemsUrl
|
||||
val items = if (videoMoments.isNotEmpty()) {
|
||||
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 {
|
||||
mutableStateOf(true)
|
||||
@@ -89,20 +128,39 @@ fun ShortViewCompose(
|
||||
val pauseIconVisibleState = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Pager(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RectangleShape),
|
||||
state = pagerState,
|
||||
orientation = Orientation.Vertical,
|
||||
offscreenLimit = 1
|
||||
) {
|
||||
pauseIconVisibleState.value = false
|
||||
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
||||
videoMoments[page]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// 同步页码到外部(用于返回时恢复进度)
|
||||
LaunchedEffect(pagerState.currentPage) {
|
||||
onPageChanged?.invoke(pagerState.currentPage)
|
||||
}
|
||||
SingleVideoItemContent(
|
||||
videoItemsUrl[page],
|
||||
pagerState,
|
||||
page,
|
||||
initialLayout,
|
||||
pauseIconVisibleState,
|
||||
videoHeader,
|
||||
videoBottom
|
||||
videoUrl = items[page],
|
||||
moment = currentMoment,
|
||||
pagerState = pagerState,
|
||||
pager = page,
|
||||
initialLayout = initialLayout,
|
||||
pauseIconVisibleState = pauseIconVisibleState,
|
||||
VideoHeader = videoHeader,
|
||||
VideoBottom = videoBottom,
|
||||
onLikeClick = onLikeClick,
|
||||
onCommentClick = onCommentClick,
|
||||
onFavoriteClick = onFavoriteClick,
|
||||
onShareClick = onShareClick
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,18 +174,39 @@ fun ShortViewCompose(
|
||||
@Composable
|
||||
private fun SingleVideoItemContent(
|
||||
videoUrl: String,
|
||||
moment: MomentEntity?,
|
||||
pagerState: PagerState,
|
||||
pager: Int,
|
||||
initialLayout: MutableState<Boolean>,
|
||||
pauseIconVisibleState: MutableState<Boolean>,
|
||||
VideoHeader: @Composable() () -> Unit,
|
||||
VideoBottom: @Composable() () -> Unit,
|
||||
VideoHeader: @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()) {
|
||||
VideoPlayer(videoUrl, pagerState, pager, pauseIconVisibleState)
|
||||
Box(
|
||||
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()
|
||||
if (moment != null && VideoBottom != null) {
|
||||
Box(modifier = Modifier.align(Alignment.BottomStart)) {
|
||||
VideoBottom.invoke()
|
||||
VideoBottom.invoke(moment)
|
||||
}
|
||||
}
|
||||
if (initialLayout.value) {
|
||||
Box(
|
||||
@@ -143,9 +222,14 @@ private fun SingleVideoItemContent(
|
||||
@Composable
|
||||
fun VideoPlayer(
|
||||
videoUrl: String,
|
||||
moment: MomentEntity?,
|
||||
pagerState: PagerState,
|
||||
pager: Int,
|
||||
pauseIconVisibleState: MutableState<Boolean>,
|
||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
||||
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
||||
onShareClick: ((MomentEntity) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -158,9 +242,20 @@ fun VideoPlayer(
|
||||
ExoPlayer.Builder(context)
|
||||
.build()
|
||||
.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(
|
||||
context,
|
||||
Util.getUserAgent(context, context.packageName)
|
||||
httpDataSourceFactory
|
||||
)
|
||||
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
|
||||
@@ -275,21 +370,54 @@ fun VideoPlayer(
|
||||
modifier = Modifier.padding(bottom = 72.dp, end = 12.dp),
|
||||
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()
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_like, text = "975.9k")
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "1896") {
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_like, text = "0")
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "0") {
|
||||
showCommentModal = true
|
||||
}
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "234")
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_share, text = "677k")
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "0")
|
||||
VideoBtn(icon = R.drawable.rider_pro_video_share, text = "0")
|
||||
}
|
||||
}
|
||||
}
|
||||
// info
|
||||
if (moment != null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.BottomStart
|
||||
) {
|
||||
Column(modifier = Modifier.padding(start = 16.dp, bottom = 16.dp)) {
|
||||
if (moment.location.isNotEmpty() && moment.location != "Worldwide") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp)
|
||||
@@ -306,39 +434,43 @@ fun VideoPlayer(
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = "USA",
|
||||
text = moment.location,
|
||||
fontSize = 12.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "@Kevinlinpr",
|
||||
text = "@${moment.nickname}",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
if (moment.momentTextContent.isNotEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp), // 确保Text占用可用宽度
|
||||
text = "Pedro Acosta to join KTM in 2025 on a multi-year deal! \uD83D\uDFE0",
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent,
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
overflow = TextOverflow.Ellipsis, // 超出范围时显示省略号
|
||||
maxLines = 2 // 最多显示两行
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showCommentModal) {
|
||||
if (showCommentModal && moment != null) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showCommentModal = false },
|
||||
containerColor = Color.White,
|
||||
sheetState = sheetState
|
||||
) {
|
||||
CommentModalContent() {
|
||||
CommentModalContent(postId = moment.id) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -346,16 +478,37 @@ fun VideoPlayer(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserAvatar() {
|
||||
Image(
|
||||
fun UserAvatar(avatarUrl: String? = null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.size(40.dp)
|
||||
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
||||
.clip(
|
||||
RoundedCornerShape(40.dp)
|
||||
), painter = painterResource(id = R.drawable.default_avatar), contentDescription = ""
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
) {
|
||||
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
|
||||
|
||||
@@ -204,7 +204,7 @@ fun EmailSignupScreen() {
|
||||
email = it
|
||||
},
|
||||
label = stringResource(R.string.login_email_label),
|
||||
hint = "输入电子邮件",
|
||||
hint = stringResource(R.string.text_hint_email),
|
||||
error = emailError,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
|
||||
@@ -26,6 +26,8 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.LazyVerticalGrid
|
||||
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.font.FontWeight
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -160,8 +163,9 @@ fun NewPostScreen() {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp)
|
||||
.width(100.dp)
|
||||
.height(40.dp)
|
||||
.widthIn(min = 100.dp, max = 200.dp)
|
||||
.wrapContentWidth()
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
@@ -189,6 +193,8 @@ fun NewPostScreen() {
|
||||
modifier = Modifier
|
||||
.padding(start = 2.dp),
|
||||
color = Color.White,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<string name="users">ユーザー</string>
|
||||
<string name="like_upper">いいね</string>
|
||||
<string name="followers_upper">フォロワー</string>
|
||||
<string name="posts">投稿</string>
|
||||
<string name="favourites_upper">お気に入り</string>
|
||||
<string name="favourites_null">あれ、何もない。..</string>
|
||||
<string name="notifications_upper">通知</string>
|
||||
@@ -52,6 +53,10 @@
|
||||
<string name="error_not_accept_term">最高のサービスを提供するために、登録前に利用規約を読み、同意してください。</string>
|
||||
<string name="empty_my_post_title">まだ投稿がありません</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="share">シェア</string>
|
||||
<string name="logout">ログアウト</string>
|
||||
@@ -144,17 +149,21 @@
|
||||
<string name="agent_desc_hint">例:経験豊富な営業担当者で、ユーモアと生きた事例を使って、複雑な製品を顧客が理解しやすい形に変えるのが得意</string>
|
||||
<string name="agent_create">エージェントを作成</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_apply">適用</string>
|
||||
<string name="chat_ai">AI</string>
|
||||
<string name="chat_group">グループ</string>
|
||||
<string name="chat_friend">友達</string>
|
||||
<string name="chat_all">すべて</string>
|
||||
<string name="public_label">公開</string>
|
||||
<string name="private_label">プライベート</string>
|
||||
<string name="chatting_now">人はおしゃべりをしている…</string>
|
||||
<string name="agent_chat_list_title">AIエージェントチャット</string>
|
||||
<string name="agent_chat_empty_title">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_image">[画像]</string>
|
||||
<string name="agent_chat_voice">[音声]</string>
|
||||
@@ -166,10 +175,15 @@
|
||||
<string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string>
|
||||
<string name="group_chat_empty">グループチャットがありません</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_subtitle">ホームで興味のあるテーマルームを探してみましょう</string>
|
||||
<string name="friend_chat_empty_title">まだ友達とチャットしていません~</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_load_failed">読み込みに失敗しました</string>
|
||||
<string name="create_group_chat">グループチャットを作成</string>
|
||||
@@ -255,9 +269,28 @@
|
||||
<!-- Edit Profile Extras -->
|
||||
<string name="mbti_type">MBTIタイプ</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="choose_mbti">MBTIを選択</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 -->
|
||||
<string name="scan_qr">さっと動かす</string>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<string name="users">用户</string>
|
||||
<string name="like_upper">赞</string>
|
||||
<string name="followers_upper">粉丝</string>
|
||||
<string name="posts">帖子</string>
|
||||
<string name="favourites_upper">收藏</string>
|
||||
<string name="notifications_upper">消息</string>
|
||||
<string name="following_upper">关注</string>
|
||||
@@ -51,6 +52,10 @@
|
||||
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
|
||||
<string name="empty_my_post_title">还没有发布任何动态</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="share">分享</string>
|
||||
<string name="logout">登出</string>
|
||||
@@ -153,11 +158,15 @@
|
||||
<string name="chat_group">群聊</string>
|
||||
<string name="chat_friend">朋友</string>
|
||||
<string name="chat_all">全部</string>
|
||||
<string name="public_label">公开</string>
|
||||
<string name="private_label">私有</string>
|
||||
<string name="favourites_null">咦,什么都没有...</string>
|
||||
|
||||
<string name="agent_chat_list_title">智能体聊天</string>
|
||||
<string name="agent_chat_empty_title">AI 在等你的开场白</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_image">[图片]</string>
|
||||
<string name="agent_chat_voice">[语音]</string>
|
||||
@@ -169,10 +178,15 @@
|
||||
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
|
||||
<string name="group_chat_empty">没有群聊,宇宙好安静</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_join">去首页探索感兴趣的高能对话</string>
|
||||
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</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_load_failed">加载失败</string>
|
||||
<string name="create_group_chat">创建群聊</string>
|
||||
@@ -292,9 +306,28 @@
|
||||
<!-- Edit Profile Extras -->
|
||||
<string name="mbti_type">MBTI 类型</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="choose_mbti">选择 MBTI</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 -->
|
||||
<string name="scan_qr">扫一扫</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="users">Users</string>
|
||||
<string name="like_upper">LIKE</string>
|
||||
<string name="followers_upper">FOLLOWERS</string>
|
||||
<string name="posts">Posts</string>
|
||||
<string name="favourites_upper">FAVOURITES</string>
|
||||
<string name="favourites_null">Well,nothing </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="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="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="share">share</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_create">Create Agent</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_apply">Apply</string>
|
||||
<string name="chat_ai">Ai</string>
|
||||
@@ -151,9 +156,13 @@
|
||||
<string name="chat_friend">Friends</string>
|
||||
<string name="chatting_now">people chatting now…</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_empty_title">No Agent Chat</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_image">[Image]</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="group_chat_empty">No group chats</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_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_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_load_failed">Failed to load</string>
|
||||
<string name="create_group_chat">Create Group Chat</string>
|
||||
@@ -286,9 +300,28 @@
|
||||
<!-- Edit Profile Extras -->
|
||||
<string name="mbti_type">MBTI</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="choose_mbti">Choose MBTI</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 -->
|
||||
<string name="scan_qr">Scan QR</string>
|
||||
|
||||
Reference in New Issue
Block a user