Merge branch 'main' into zhong_1

This commit is contained in:
2025-11-11 18:49:50 +08:00
34 changed files with 594 additions and 152 deletions

View File

@@ -1,21 +1,22 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
id("com.google.gms.google-services") id("com.google.gms.google-services")
id("com.google.firebase.crashlytics") id("com.google.firebase.crashlytics")
id("com.google.firebase.firebase-perf") id("com.google.firebase.firebase-perf")
id("org.jetbrains.kotlin.kapt") id("org.jetbrains.kotlin.kapt")
id("com.google.devtools.ksp") version "1.9.10-1.0.13" alias(libs.plugins.ksp)
} }
android { android {
namespace = "com.aiosman.ravenow" namespace = "com.aiosman.ravenow"
compileSdk = 34 compileSdk = 35
defaultConfig { defaultConfig {
applicationId = "com.aiosman.ravenow" applicationId = "com.aiosman.ravenow"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 35
versionCode = 1000019 versionCode = 1000019
versionName = "1.0.000.19" versionName = "1.0.000.19"
@@ -46,19 +47,16 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "17"
} }
buildFeatures { buildFeatures {
compose = true compose = true
buildConfig = true buildConfig = true
} }
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
packaging { packaging {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -99,11 +97,13 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.animation) implementation(libs.androidx.animation)
implementation(libs.coil.compose)
implementation(libs.coil) implementation(libs.coil)
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
implementation(libs.play.services.auth) implementation(libs.play.services.auth)
implementation(libs.kotlin.faker) implementation(libs.kotlin.faker)
implementation(libs.androidx.material) implementation(libs.androidx.material)
implementation(libs.androidx.material.icons.extended)
implementation(libs.zoomable) implementation(libs.zoomable)
implementation(libs.retrofit) implementation(libs.retrofit)
implementation(libs.converter.gson) implementation(libs.converter.gson)

View File

@@ -7,11 +7,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter import coil3.ImageLoader
import coil.request.ImageRequest import coil3.compose.rememberAsyncImagePainter
import coil.ImageLoader import coil3.disk.DiskCache
import coil.disk.DiskCache import coil3.memory.MemoryCache
import coil.memory.MemoryCache import coil3.request.ImageRequest
import coil3.request.crossfade
import okio.Path.Companion.toPath
data class ImageItem(val url: String) data class ImageItem(val url: String)
@@ -53,14 +55,15 @@ fun ImageItem(item: ImageItem, imageLoader: ImageLoader, context: Context) { //
fun getImageLoader(context: Context): ImageLoader { fun getImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(context) return ImageLoader.Builder(context)
.memoryCache { .memoryCache {
MemoryCache.Builder(context) MemoryCache.Builder()
.maxSizePercent(0.25) // 设置内存缓存大小为可用内存的 25% .maxSizePercent(context,0.25) // 设置内存缓存大小为可用内存的 25%
.build() .build()
} }
.diskCache { .diskCache {
val cacheDir = context.cacheDir.resolve("image_cache")
DiskCache.Builder() DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache")) .directory(cacheDir.absolutePath.toPath())
.maxSizePercent(0.02) // 设置磁盘缓存大小为可用存储空间的 2% .maxSizeBytes(250L * 1024 * 1024) // 250MB
.build() .build()
} }
.build() .build()

View File

@@ -73,7 +73,7 @@ data class Profile(
@SerializedName("nickname") @SerializedName("nickname")
val nickname: String, val nickname: String,
@SerializedName("trtcUserId") @SerializedName("trtcUserId")
val trtcUserId: String, val trtcUserId: String? = null,
@SerializedName("username") @SerializedName("username")
val username: String val username: String
){ ){
@@ -85,7 +85,7 @@ data class Profile(
avatar = "${ApiClient.BASE_SERVER}$avatar", avatar = "${ApiClient.BASE_SERVER}$avatar",
bio = bio, bio = bio,
banner = "${ApiClient.BASE_SERVER}$banner", banner = "${ApiClient.BASE_SERVER}$banner",
trtcUserId = trtcUserId, trtcUserId = trtcUserId ?: "",
chatAIId = chatAIId, chatAIId = chatAIId,
aiAccount = aiAccount aiAccount = aiAccount
) )

View File

@@ -26,6 +26,22 @@ import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
import com.aiosman.ravenow.entity.UsersEntity import com.aiosman.ravenow.entity.UsersEntity
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
/**
* 房间内的智能体信息PromptTemplate
*/
data class PromptTemplate(
@SerializedName("id")
val id: Int,
@SerializedName("openId")
val openId: String,
@SerializedName("title")
val title: String,
@SerializedName("desc")
val desc: String,
@SerializedName("avatar")
val avatar: String
)
data class Room( data class Room(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@@ -51,12 +67,26 @@ data class Room(
val creator: Creator, val creator: Creator,
@SerializedName("userCount") @SerializedName("userCount")
val userCount: Int, val userCount: Int,
@SerializedName("totalMemberCount")
val totalMemberCount: Int? = null,
@SerializedName("maxMemberLimit") @SerializedName("maxMemberLimit")
val maxMemberLimit: Int, val maxMemberLimit: Int,
@SerializedName("maxTotal")
val maxTotal: Int? = null,
@SerializedName("systemMaxTotal")
val systemMaxTotal: Int? = null,
@SerializedName("canJoin") @SerializedName("canJoin")
val canJoin: Boolean, val canJoin: Boolean,
@SerializedName("canJoinCode") @SerializedName("canJoinCode")
val canJoinCode: Int, val canJoinCode: Int,
@SerializedName("privateFeePaid")
val privateFeePaid: Boolean? = null,
@SerializedName("prompts")
val prompts: List<PromptTemplate>? = null,
@SerializedName("createdAt")
val createdAt: String? = null,
@SerializedName("updatedAt")
val updatedAt: String? = null,
@SerializedName("users") @SerializedName("users")
val users: List<Users> val users: List<Users>
@@ -75,9 +105,24 @@ data class Room(
allowInHot = allowInHot, allowInHot = allowInHot,
creator = creator.toCreatorEntity(), creator = creator.toCreatorEntity(),
userCount = userCount, userCount = userCount,
totalMemberCount = totalMemberCount,
maxMemberLimit = maxMemberLimit, maxMemberLimit = maxMemberLimit,
maxTotal = maxTotal,
systemMaxTotal = systemMaxTotal,
canJoin = canJoin, canJoin = canJoin,
canJoinCode = canJoinCode, canJoinCode = canJoinCode,
privateFeePaid = privateFeePaid ?: false,
prompts = prompts?.map {
com.aiosman.ravenow.entity.PromptTemplateEntity(
id = it.id,
openId = it.openId,
title = it.title,
desc = it.desc,
avatar = it.avatar
)
} ?: emptyList(),
createdAt = createdAt,
updatedAt = updatedAt,
users = users.map { it.toUsersEntity() } users = users.map { it.toUsersEntity() }
) )
} }
@@ -90,7 +135,7 @@ data class Creator(
@SerializedName("userId") @SerializedName("userId")
val userId: String, val userId: String,
@SerializedName("trtcUserId") @SerializedName("trtcUserId")
val trtcUserId: String, val trtcUserId: String? = null,
@SerializedName("profile") @SerializedName("profile")
val profile: Profile val profile: Profile
){ ){
@@ -98,7 +143,7 @@ data class Creator(
return CreatorEntity( return CreatorEntity(
id = id, id = id,
userId = userId, userId = userId,
trtcUserId = trtcUserId, trtcUserId = trtcUserId ?: "",
profile = profile.toProfileEntity() profile = profile.toProfileEntity()
) )
} }
@@ -110,7 +155,7 @@ data class Users(
@SerializedName("userId") @SerializedName("userId")
val userId: String, val userId: String,
@SerializedName("trtcUserId") @SerializedName("trtcUserId")
val trtcUserId: String, val trtcUserId: String? = null,
@SerializedName("profile") @SerializedName("profile")
val profile: Profile val profile: Profile
){ ){

View File

@@ -1423,11 +1423,39 @@ interface RaveNowAPI {
@POST("outside/rooms") @POST("outside/rooms")
suspend fun createGroupChat(@Body body: CreateGroupChatRequestBody): Response<DataContainer<Unit>> suspend fun createGroupChat(@Body body: CreateGroupChatRequestBody): Response<DataContainer<Unit>>
/**
* 获取房间列表
*
* 支持游客和认证用户访问,根据用户类型返回不同的房间数据。
* 游客模式返回公开推荐房间列表,认证用户模式返回用户可访问的群聊列表。
*
* @param page 页码,默认 1
* @param pageSize 每页数量,默认 20游客模式最大50
* @param roomId 房间ID用于精确查询特定房间仅认证用户
* @param includeUsers 是否包含用户列表默认false仅认证用户
* @param isRecommended 是否推荐过滤器1=推荐0=非推荐null=不过滤(仅认证用户)
* @param roomType 房间类型过滤all=公有私有都显示, public=只显示公有, private=只显示私有, created=只显示自己创建的, joined=只显示自己加入的(仅认证用户)
* @param search 搜索关键字,支持房间名称、描述、智能体名称模糊匹配
* @param random 是否随机排序字符串长度不为0则为true传任意非空字符串即可
* @param ownerSessionId 创建者用户IDChatAIID用于过滤特定创建者的房间
* @param showPublic 是否显示公有房间只有明确设置为true时才生效优先级高于roomType仅认证用户
* @param showCreated 是否显示自己创建的房间只有明确设置为true时才生效优先级高于roomType仅认证用户
* @param showJoined 是否显示自己加入的房间只有明确设置为true时才生效优先级高于roomType仅认证用户
*/
@GET("outside/rooms") @GET("outside/rooms")
suspend fun getRooms(@Query("page") page: Int = 1, suspend fun getRooms(
@Query("pageSize") pageSize: Int = 20, @Query("page") page: Int = 1,
@Query("isRecommended") isRecommended: Int = 1, @Query("pageSize") pageSize: Int = 20,
@Query("random") random: Int? = null, @Query("roomId") roomId: Long? = null,
@Query("includeUsers") includeUsers: Boolean? = null,
@Query("isRecommended") isRecommended: Int? = null,
@Query("roomType") roomType: String? = null,
@Query("search") search: String? = null,
@Query("random") random: String? = null,
@Query("ownerSessionId") ownerSessionId: String? = null,
@Query("showPublic") showPublic: Boolean? = null,
@Query("showCreated") showCreated: Boolean? = null,
@Query("showJoined") showJoined: Boolean? = null,
): Response<ListContainer<Room>> ): Response<ListContainer<Room>>
@GET("outside/rooms/detail") @GET("outside/rooms/detail")

View File

@@ -8,6 +8,17 @@ import com.aiosman.ravenow.data.api.ApiClient
* 群聊房间 * 群聊房间
*/ */
/**
* 房间内的智能体信息实体
*/
data class PromptTemplateEntity(
val id: Int,
val openId: String,
val title: String,
val desc: String,
val avatar: String
)
data class RoomEntity( data class RoomEntity(
val id: Int, val id: Int,
val name: String, val name: String,
@@ -21,9 +32,16 @@ data class RoomEntity(
val allowInHot: Boolean, val allowInHot: Boolean,
val creator: CreatorEntity, val creator: CreatorEntity,
val userCount: Int, val userCount: Int,
val totalMemberCount: Int? = null,
val maxMemberLimit: Int, val maxMemberLimit: Int,
val maxTotal: Int? = null,
val systemMaxTotal: Int? = null,
val canJoin: Boolean, val canJoin: Boolean,
val canJoinCode: Int, val canJoinCode: Int,
val privateFeePaid: Boolean = false,
val prompts: List<PromptTemplateEntity> = emptyList(),
val createdAt: String? = null,
val updatedAt: String? = null,
val users: List<UsersEntity>, val users: List<UsersEntity>,
) )

View File

@@ -71,7 +71,7 @@ fun AboutScreen() {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// app version // app version
Text( Text(
text = stringResource(R.string.version_text, versionText), text = stringResource(R.string.version_text, versionText ?: ""),
fontSize = 16.sp, fontSize = 16.sp,
color = appColors.secondaryText, color = appColors.secondaryText,
fontWeight = FontWeight.Normal fontWeight = FontWeight.Normal

View File

@@ -118,8 +118,7 @@ fun CommentModalContent(
skipPartiallyExpanded = true skipPartiallyExpanded = true
), ),
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
windowInsets = WindowInsets(0)
) { ) {
CommentMenuModal( CommentMenuModal(
onDeleteClick = { onDeleteClick = {

View File

@@ -8,9 +8,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import coil.annotation.ExperimentalCoilApi import coil3.annotation.ExperimentalCoilApi
import coil.compose.AsyncImage import coil3.compose.AsyncImage
import coil.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.crossfade
import coil3.request.fallback
import coil3.request.placeholder
import com.aiosman.ravenow.utils.BlurHashDecoder import com.aiosman.ravenow.utils.BlurHashDecoder
import com.aiosman.ravenow.utils.Utils.getImageLoader import com.aiosman.ravenow.utils.Utils.getImageLoader

View File

@@ -2,6 +2,7 @@ package com.aiosman.ravenow.ui.composables
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
@@ -68,7 +69,6 @@ fun Modifier.debouncedClickableWithRipple(
clickable( clickable(
enabled = enabled && isClickable, enabled = enabled && isClickable,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = androidx.compose.material.ripple.rememberRipple()
) { ) {
if (isClickable) { if (isClickable) {
isClickable = false isClickable = false

View File

@@ -123,7 +123,7 @@ fun LazyGridItemScope.DraggableItem(
translationY = dragDropState.previousItemOffset.value.y translationY = dragDropState.previousItemOffset.value.y
} }
} else { } else {
Modifier.animateItemPlacement() Modifier
} }
Box(modifier = modifier.then(draggingModifier).clip(RoundedCornerShape(8.dp)), propagateMinConstraints = true) { Box(modifier = modifier.then(draggingModifier).clip(RoundedCornerShape(8.dp)), propagateMinConstraints = true) {
content(dragging) content(dragging)

View File

@@ -16,11 +16,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import coil.ImageLoader import coil3.ImageLoader
import coil.compose.AsyncImage import coil3.asDrawable
import coil.request.ImageRequest import coil3.asImage
import coil.request.SuccessResult import coil3.compose.AsyncImage
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.crossfade
import com.aiosman.ravenow.utils.Utils.getImageLoader import com.aiosman.ravenow.utils.Utils.getImageLoader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -59,7 +64,11 @@ fun rememberImageBitmap(imageUrl: String, imageLoader: ImageLoader): Bitmap? {
.build() .build()
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
(imageLoader.execute(request) as? SuccessResult)?.drawable?.toBitmap() val successResult = imageLoader.execute(request) as? SuccessResult
successResult?.let {
val drawable = it.image.asDrawable(context.resources)
drawable.toBitmap()
}
} }
bitmap = result bitmap = result
@@ -138,25 +147,33 @@ fun CustomAsyncImage(
} }
// 处理字符串URL // 处理字符串URL
val ctx = context ?: localContext
val placeholderImage = remember(placeholderRes, ctx) {
placeholderRes?.let { resId ->
ContextCompat.getDrawable(ctx, resId)?.asImage()
}
}
val errorImage = remember(errorRes, ctx) {
errorRes?.let { resId ->
ContextCompat.getDrawable(ctx, resId)?.asImage()
}
}
if (showShimmer) { if (showShimmer) {
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
Box(modifier = modifier) { Box(modifier = modifier) {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context ?: localContext) model = ImageRequest.Builder(ctx)
.data(imageUrl) .data(imageUrl)
.crossfade(200) .crossfade(200)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED) .memoryCachePolicy(coil3.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED) .diskCachePolicy(coil3.request.CachePolicy.ENABLED)
.apply { .apply {
// 设置占位符图片 // 设置占位符图片
if (placeholderRes != null) { placeholderImage?.let { placeholder(it) }
placeholder(placeholderRes)
}
// 设置错误时显示的图片 // 设置错误时显示的图片
if (errorRes != null) { errorImage?.let { error(it) }
error(errorRes)
}
} }
.build(), .build(),
contentDescription = contentDescription, contentDescription = contentDescription,
@@ -177,20 +194,16 @@ fun CustomAsyncImage(
} }
} else { } else {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context ?: localContext) model = ImageRequest.Builder(ctx)
.data(imageUrl) .data(imageUrl)
.crossfade(200) .crossfade(200)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED) .diskCachePolicy(CachePolicy.ENABLED)
.apply { .apply {
// 设置占位符图片 // 设置占位符图片
if (placeholderRes != null) { placeholderImage?.let { placeholder(it) }
placeholder(placeholderRes)
}
// 设置错误时显示的图片 // 设置错误时显示的图片
if (errorRes != null) { errorImage?.let { error(it) }
error(errorRes)
}
} }
.build(), .build(),
contentDescription = contentDescription, contentDescription = contentDescription,

View File

@@ -515,7 +515,6 @@ fun MomentBottomOperateRowGroup(
sheetState = rememberModalBottomSheetState( sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true
), ),
windowInsets = WindowInsets(0),
dragHandle = { dragHandle = {
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -71,7 +71,6 @@ fun PolicyCheckbox(
showModal = false showModal = false
}, },
sheetState = modalSheetState, sheetState = modalSheetState,
windowInsets = WindowInsets(0),
containerColor = Color.White, containerColor = Color.White,
) { ) {
WebViewDisplay( WebViewDisplay(

View File

@@ -47,7 +47,6 @@ fun CreateBottomSheet(
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
sheetState = sheetState, sheetState = sheetState,
windowInsets = BottomSheetDefaults.windowInsets,
containerColor = appColors.background, containerColor = appColors.background,
dragHandle = null, dragHandle = null,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)

View File

@@ -265,7 +265,6 @@ fun IndexScreen() {
modifier = Modifier modifier = Modifier
.background(AppColors.background) .background(AppColors.background)
.padding(0.dp), .padding(0.dp),
beyondBoundsPageCount = 4,
userScrollEnabled = false userScrollEnabled = false
) { page -> ) { page ->
when (page) { when (page) {

View File

@@ -54,7 +54,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.GuestLoginCheckOut import com.aiosman.ravenow.GuestLoginCheckOut
@@ -115,7 +114,7 @@ fun Agent() {
var pagerState = rememberPagerState { tabCount } var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = AgentViewModel
// 确保推荐Agent数据已加载 // 确保推荐Agent数据已加载
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -183,7 +182,6 @@ fun Agent() {
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColors.background containerColor = AppColors.background
), ),
windowInsets = WindowInsets(0, 0, 0, 0),
modifier = Modifier modifier = Modifier
.height(44.dp + statusBarPaddingValues.calculateTopPadding()) .height(44.dp + statusBarPaddingValues.calculateTopPadding())
.padding(top = statusBarPaddingValues.calculateTopPadding()) .padding(top = statusBarPaddingValues.calculateTopPadding())
@@ -902,7 +900,7 @@ fun ChatRoomCard(
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val cardSize = 180.dp val cardSize = 180.dp
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = AgentViewModel
val context = LocalContext.current val context = LocalContext.current
// 防抖状态 // 防抖状态

View File

@@ -197,7 +197,7 @@ object AgentViewModel: ViewModel() {
page = 1, page = 1,
pageSize = 20, pageSize = 20,
isRecommended = 1, isRecommended = 1,
random = 1 random = "1"
) )
if (response.isSuccessful) { if (response.isSuccessful) {
val allRooms = response.body()?.list ?: emptyList() val allRooms = response.body()?.list ?: emptyList()

View File

@@ -156,10 +156,10 @@ object HotAgentViewModel : ViewModel() {
try { try {
// 预加载头像图片到缓存 // 预加载头像图片到缓存
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue( com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context) coil3.request.ImageRequest.Builder(context)
.data(agent.avatar) .data(agent.avatar)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED) .memoryCachePolicy(coil3.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED) .diskCachePolicy(coil3.request.CachePolicy.ENABLED)
.build() .build()
) )
preloadedImageIds.add(agent.id) preloadedImageIds.add(agent.id)

View File

@@ -841,20 +841,20 @@ fun Explore() {
if (bannerItem.backgroundImageUrl.isNotEmpty()) { if (bannerItem.backgroundImageUrl.isNotEmpty()) {
// 预加载背景图片 // 预加载背景图片
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue( com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context) coil3.request.ImageRequest.Builder(context)
.data(bannerItem.backgroundImageUrl) .data(bannerItem.backgroundImageUrl)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED) .memoryCachePolicy(coil3.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED) .diskCachePolicy(coil3.request.CachePolicy.ENABLED)
.build() .build()
) )
} }
if (bannerItem.imageUrl.isNotEmpty()) { if (bannerItem.imageUrl.isNotEmpty()) {
// 预加载头像图片 // 预加载头像图片
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue( com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context) coil3.request.ImageRequest.Builder(context)
.data(bannerItem.imageUrl) .data(bannerItem.imageUrl)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED) .memoryCachePolicy(coil3.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED) .diskCachePolicy(coil3.request.CachePolicy.ENABLED)
.build() .build()
) )
} }

View File

@@ -59,7 +59,6 @@ fun FullArticleModal(
.height(sheetHeight), .height(sheetHeight),
containerColor = appColors.background, containerColor = appColors.background,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier

View File

@@ -154,7 +154,6 @@ fun NewsCommentModal(
), ),
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) { ) {
CommentMenuModal( CommentMenuModal(
onDeleteClick = { onDeleteClick = {

View File

@@ -194,7 +194,6 @@ fun NewsScreen() {
.height(sheetHeight), .height(sheetHeight),
containerColor = AppColors.background, containerColor = AppColors.background,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
) { ) {
NewsCommentModal( NewsCommentModal(
postId = selectedMoment?.id, postId = selectedMoment?.id,

View File

@@ -23,6 +23,10 @@ import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.entity.MomentServiceImpl import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.entity.RoomEntity
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.RaveNowAPI
import com.aiosman.ravenow.data.Room
import com.aiosman.ravenow.event.FollowChangeEvent import com.aiosman.ravenow.event.FollowChangeEvent
import com.aiosman.ravenow.event.MomentAddEvent import com.aiosman.ravenow.event.MomentAddEvent
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
@@ -40,6 +44,14 @@ object MyProfileViewModel : ViewModel() {
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
var moments by mutableStateOf<List<MomentEntity>>(emptyList()) var moments by mutableStateOf<List<MomentEntity>>(emptyList())
var agents by mutableStateOf<List<AgentEntity>>(emptyList()) var agents by mutableStateOf<List<AgentEntity>>(emptyList())
var rooms by mutableStateOf<List<RoomEntity>>(emptyList())
var roomsLoading by mutableStateOf(false)
var roomsRefreshing by mutableStateOf(false)
var roomsCurrentPage by mutableStateOf(1)
var roomsHasMore by mutableStateOf(true)
private val roomsPageSize = 20
private val apiClient: RaveNowAPI = ApiClient.api
val momentLoader: MomentLoader = MomentLoader().apply { val momentLoader: MomentLoader = MomentLoader().apply {
pageSize = 20 // 设置与后端一致的页面大小 pageSize = 20 // 设置与后端一致的页面大小
onListChanged = { onListChanged = {
@@ -254,4 +266,115 @@ object MyProfileViewModel : ViewModel() {
fun onFollowChangeEvent(event: FollowChangeEvent) { fun onFollowChangeEvent(event: FollowChangeEvent) {
momentLoader.updateFollowStatus(event.userId, event.isFollow) momentLoader.updateFollowStatus(event.userId, event.isFollow)
} }
/**
* 加载房间列表
* @param filterType 筛选类型0=全部1=公开2=私有
* @param pullRefresh 是否下拉刷新
*/
fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false) {
// 游客模式下不加载房间列表
if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "loadRooms: 游客模式下跳过加载房间列表")
return
}
if (roomsLoading && !pullRefresh) return
viewModelScope.launch {
try {
roomsLoading = true
roomsRefreshing = pullRefresh
val currentPage = if (pullRefresh) {
roomsCurrentPage = 1
roomsHasMore = true
1
} else {
roomsCurrentPage
}
val response = when (filterType) {
0 -> {
// 全部:显示自己创建或加入的所有房间
apiClient.getRooms(
page = currentPage,
pageSize = roomsPageSize,
showCreated = true,
showJoined = true
)
}
1 -> {
// 公开:显示公开房间中自己创建或加入的
apiClient.getRooms(
page = currentPage,
pageSize = roomsPageSize,
roomType = "public",
showCreated = true,
showJoined = true
)
}
2 -> {
// 私有:显示自己创建或加入的私有房间
apiClient.getRooms(
page = currentPage,
pageSize = roomsPageSize,
roomType = "private"
)
}
else -> {
apiClient.getRooms(
page = currentPage,
pageSize = roomsPageSize,
showCreated = true,
showJoined = true
)
}
}
if (response.isSuccessful) {
val roomList = response.body()?.list ?: emptyList()
val total = response.body()?.total ?: 0L
if (pullRefresh || currentPage == 1) {
rooms = roomList.map { it.toRoomtEntity() }
} else {
rooms = rooms + roomList.map { it.toRoomtEntity() }
}
roomsHasMore = rooms.size < total
if (roomsHasMore && !pullRefresh) {
roomsCurrentPage++
}
} else {
Log.e("MyProfileViewModel", "loadRooms failed: ${response.code()}")
}
} catch (e: Exception) {
Log.e("MyProfileViewModel", "loadRooms error: ", e)
} finally {
roomsLoading = false
roomsRefreshing = false
}
}
}
/**
* 加载更多房间
* @param filterType 筛选类型0=全部1=公开2=私有
*/
fun loadMoreRooms(filterType: Int = 0) {
if (roomsLoading || !roomsHasMore) return
loadRooms(filterType = filterType, pullRefresh = false)
}
/**
* 刷新房间列表
* @param filterType 筛选类型0=全部1=公开2=私有
*/
fun refreshRooms(filterType: Int = 0) {
rooms = emptyList()
roomsCurrentPage = 1
roomsHasMore = true
loadRooms(filterType = filterType, pullRefresh = true)
}
} }

View File

@@ -258,7 +258,6 @@ fun ProfileV3(
sheetState = agentMenuModalState, sheetState = agentMenuModalState,
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) { ) {
AgentMenuModal( AgentMenuModal(
agent = contextAgent, agent = contextAgent,
@@ -535,7 +534,7 @@ fun ProfileV3(
containerColor = Color.Transparent, // 设置容器背景透明 containerColor = Color.Transparent, // 设置容器背景透明
contentColor = Color.Transparent, // 设置内容背景透明 contentColor = Color.Transparent, // 设置内容背景透明
dragHandle = null, // 移除拖拽手柄 dragHandle = null, // 移除拖拽手柄
windowInsets = androidx.compose.foundation.layout.WindowInsets(0) // 移除窗口边距 contentWindowInsets = {androidx.compose.foundation.layout.WindowInsets(0)},
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -567,7 +566,7 @@ fun ProfileV3(
containerColor = Color.Transparent, containerColor = Color.Transparent,
contentColor = Color.Transparent, contentColor = Color.Transparent,
dragHandle = null, dragHandle = null,
windowInsets = androidx.compose.foundation.layout.WindowInsets(0) contentWindowInsets = { androidx.compose.foundation.layout.WindowInsets(0) }
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -14,17 +14,28 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -32,13 +43,46 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.RoomEntity
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.navigateToGroupChat
import com.aiosman.ravenow.AppStore
@OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun GroupChatEmptyContent() { fun GroupChatEmptyContent() {
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val context = LocalContext.current
val navController = LocalNavController.current
val viewModel = MyProfileViewModel
val state = rememberPullRefreshState(
refreshing = viewModel.roomsRefreshing,
onRefresh = {
viewModel.refreshRooms(filterType = selectedSegment)
}
)
// 当分段改变时,重新加载数据
LaunchedEffect(selectedSegment) {
// 切换分段时重新加载
viewModel.refreshRooms(filterType = selectedSegment)
}
// 初始加载
LaunchedEffect(Unit) {
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
viewModel.loadRooms(filterType = selectedSegment)
}
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -50,37 +94,192 @@ fun GroupChatEmptyContent() {
// 分段控制器 // 分段控制器
SegmentedControl( SegmentedControl(
selectedIndex = selectedSegment, selectedIndex = selectedSegment,
onSegmentSelected = { selectedSegment = it }, onSegmentSelected = {
selectedSegment = it
// LaunchedEffect 会监听 selectedSegment 的变化并自动刷新
},
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// 空状态内容(居中) Box(
Column( modifier = Modifier
modifier = Modifier.fillMaxWidth(), .fillMaxSize()
horizontalAlignment = Alignment.CenterHorizontally .pullRefresh(state)
) { ) {
// 空状态插图 if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
EmptyStateIllustration() // 空状态内容(居中)
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 空状态插图
EmptyStateIllustration()
Spacer(modifier = Modifier.height(9.dp)) Spacer(modifier = Modifier.height(9.dp))
// 空状态文本 // 空状态文本
Text( Text(
text = stringResource(R.string.empty_nothing), text = stringResource(R.string.empty_nothing),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = AppColors.text, color = AppColors.text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp), modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(
items = viewModel.rooms,
key = { _, item -> item.id }
) { index, room ->
RoomItem(
room = room,
onRoomClick = { roomEntity ->
// 导航到群聊聊天界面
navController.navigateToGroupChat(
id = roomEntity.trtcRoomId,
name = roomEntity.name,
avatar = roomEntity.avatar
)
}
)
if (index < viewModel.rooms.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
}
// 加载更多指示器
if (viewModel.roomsLoading && viewModel.rooms.isNotEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
}
// 加载更多触发
if (viewModel.roomsHasMore && !viewModel.roomsLoading) {
item {
LaunchedEffect(Unit) {
viewModel.loadMoreRooms(filterType = selectedSegment)
}
}
}
}
}
PullRefreshIndicator(
refreshing = viewModel.roomsRefreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
) )
} }
} }
} }
@Composable
fun RoomItem(
room: RoomEntity,
onRoomClick: (RoomEntity) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
val roomDebouncer = rememberDebouncer()
// 构建头像URL
val avatarUrl = if (room.avatar.isNotEmpty()) {
"${ConstVars.BASE_SERVER}/api/v1/outside/${room.avatar}?token=${AppStore.token}"
} else {
""
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp)
.noRippleClickable {
roomDebouncer {
onRoomClick(room)
}
}
) {
Box {
CustomAsyncImage(
context = context,
imageUrl = avatarUrl,
contentDescription = room.name,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(12.dp))
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(start = 12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = room.name,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text,
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = room.description.ifEmpty { "暂无描述" },
fontSize = 14.sp,
color = AppColors.secondaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "${room.userCount}",
fontSize = 12.sp,
color = AppColors.secondaryText
)
}
}
}
}
@Composable @Composable
private fun SegmentedControl( private fun SegmentedControl(
selectedIndex: Int, selectedIndex: Int,

View File

@@ -114,11 +114,14 @@ fun getFeedItems(): List<FeedItem> {
@Composable @Composable
fun LocationDetailScreen(x: Float, y: Float) { fun LocationDetailScreen(x: Float, y: Float) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val density = LocalDensity.current
val scaffoldState = rememberBottomSheetScaffoldState( val scaffoldState = rememberBottomSheetScaffoldState(
SheetState( bottomSheetState = SheetState(
skipPartiallyExpanded = false, skipPartiallyExpanded = false,
density = LocalDensity.current, initialValue = SheetValue.PartiallyExpanded, initialValue = SheetValue.PartiallyExpanded,
skipHiddenState = true skipHiddenState = true,
positionalThreshold = { 0.5f },
velocityThreshold = { with(density) { 125.dp.toPx() } }
) )
) )
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current

View File

@@ -22,6 +22,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -30,7 +31,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter import coil3.compose.rememberAsyncImagePainter
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
@@ -67,7 +68,7 @@ fun NewPostImageGridScreen() {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
Icons.AutoMirrored.Default.ArrowBack, Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back", contentDescription = "back",
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)
@@ -84,7 +85,7 @@ fun NewPostImageGridScreen() {
fontSize = 18.sp, fontSize = 18.sp,
) )
Icon( Icon(
Icons.Default.Delete, Icons.Filled.Delete,
contentDescription = "delete", contentDescription = "delete",
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)

View File

@@ -173,7 +173,6 @@ fun PostScreen(
sheetState = commentModalState, sheetState = commentModalState,
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) { ) {
CommentMenuModal( CommentMenuModal(
onDeleteClick = { onDeleteClick = {
@@ -262,7 +261,6 @@ fun PostScreen(
), ),
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) { ) {
EditCommentBottomModal(replyComment) { EditCommentBottomModal(replyComment) {
viewModel.viewModelScope.launch { viewModel.viewModelScope.launch {
@@ -849,7 +847,6 @@ fun Header(
), ),
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0)
) { ) {
PostMenuModal( PostMenuModal(

View File

@@ -4,14 +4,16 @@ import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import coil.request.ImageRequest import androidx.core.graphics.drawable.toBitmap
import coil.request.SuccessResult import coil3.asDrawable
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.allowHardware
import com.aiosman.ravenow.utils.Utils.getImageLoader import com.aiosman.ravenow.utils.Utils.getImageLoader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -30,8 +32,9 @@ object FileUtil {
.allowHardware(false) // Disable hardware bitmaps. .allowHardware(false) // Disable hardware bitmaps.
.build() .build()
val result = (loader.execute(request) as SuccessResult).drawable val result = loader.execute(request) as? SuccessResult ?: return
val bitmap = (result as BitmapDrawable).bitmap val drawable = result.image.asDrawable(context.resources)
val bitmap = drawable.toBitmap()
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "image_${System.currentTimeMillis()}.jpg") put(MediaStore.Images.Media.DISPLAY_NAME, "image_${System.currentTimeMillis()}.jpg")

View File

@@ -4,8 +4,9 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import coil.ImageLoader import coil3.ImageLoader
import coil.request.CachePolicy import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.CachePolicy
import com.aiosman.ravenow.data.api.AuthInterceptor import com.aiosman.ravenow.data.api.AuthInterceptor
import com.aiosman.ravenow.data.api.getSafeOkHttpClient import com.aiosman.ravenow.data.api.getSafeOkHttpClient
import java.io.File import java.io.File
@@ -32,7 +33,15 @@ object Utils {
val okHttpClient = getSafeOkHttpClient(authInterceptor = AuthInterceptor()) val okHttpClient = getSafeOkHttpClient(authInterceptor = AuthInterceptor())
val loader = ImageLoader.Builder(appContext) val loader = ImageLoader.Builder(appContext)
.okHttpClient(okHttpClient) .components {
add(
OkHttpNetworkFetcherFactory(
callFactory = {
okHttpClient
}
)
)
}
.memoryCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED) .diskCachePolicy(CachePolicy.ENABLED)
.build() .build()

View File

@@ -2,6 +2,7 @@
plugins { plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
id("com.google.gms.google-services") version "4.4.2" apply false id("com.google.gms.google-services") version "4.4.2" apply false
id("com.google.firebase.crashlytics") version "3.0.2" apply false id("com.google.firebase.crashlytics") version "3.0.2" apply false
id("com.google.firebase.firebase-perf") version "1.4.2" apply false id("com.google.firebase.firebase-perf") version "1.4.2" apply false

View File

@@ -1,48 +1,51 @@
[versions] [versions]
accompanistSystemuicontroller = "0.27.0" accompanistSystemuicontroller = "0.34.0"
agp = "8.4.0" agp = "8.9.0"
animation = "1.7.0-beta05" animation = "1.7.6"
coil = "2.7.0" coil = "3.3.0"
composeImageBlurhash = "3.0.2" composeImageBlurhash = "3.0.2"
converterGson = "2.11.0" converterGson = "2.11.0"
imSdk = "3.8.3.2" imSdk = "3.8.3.2"
imcoreSdk = "3.8.3-patch10" imcoreSdk = "3.8.3-patch10"
coreSplashscreen = "1.0.1" coreSplashscreen = "1.2.0"
credentialsPlayServicesAuth = "1.2.2" credentialsPlayServicesAuth = "1.0.0-alpha05"
eventbus = "3.3.1" eventbus = "3.3.1"
firebaseBom = "33.2.0" firebaseBom = "33.7.0"
gson = "2.12.1" gson = "2.12.1"
imagecropview = "3.0.1" imagecropview = "3.0.1"
jpushGoogle = "5.4.0" jpushGoogle = "5.4.0"
jwtdecode = "2.0.2" jwtdecode = "2.0.2"
kotlin = "1.9.10" kotlin = "2.2.21"
coreKtx = "1.10.1" ksp = "2.2.21-2.0.4"
composeCompiler = "2.2.21"
coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.2.1"
espressoCore = "3.5.1" espressoCore = "3.6.1"
kotlinFaker = "2.0.0-rc.5" kotlinFaker = "2.0.0-rc.11"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.8.0" activityCompose = "1.9.2"
composeBom = "2024.06.00" composeBom = "2025.11.00"
lifecycleRuntimeKtxVersion = "2.6.2" lifecycleRuntimeKtxVersion = "2.8.6"
mapsCompose = "4.3.3" mapsCompose = "6.1.0"
material = "1.6.8" material = "1.7.6"
material3Android = "1.2.1" materialIconsExtended = "1.7.6"
media3Exoplayer = "1.3.1" material3Android = "1.3.1"
navigationCompose = "2.7.7" media3Exoplayer = "1.4.1"
pagingRuntime = "3.3.0" navigationCompose = "2.8.6"
activityKtx = "1.9.0" pagingRuntime = "3.3.6"
lifecycleCommonJvm = "2.8.2" activityKtx = "1.9.2"
places = "3.3.0" lifecycleCommonJvm = "2.8.6"
places = "3.4.0"
googleid = "1.1.1" googleid = "1.1.1"
identityCredential = "20231002" identityCredential = "20231002"
lifecycleProcess = "2.8.4" lifecycleProcess = "2.8.6"
playServicesAuth = "21.4.0" playServicesAuth = "21.4.0"
rendering = "1.17.1" rendering = "1.17.1"
zoomable = "1.6.1" zoomable = "1.6.1"
camerax = "1.3.4" camerax = "1.4.0"
mlkitBarcode = "17.3.0" mlkitBarcode = "17.3.0"
room = "2.6.1" room = "2.8.3"
[libraries] [libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
@@ -53,6 +56,7 @@ androidx-credentials = { module = "androidx.credentials:credentials", version.re
androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentialsPlayServicesAuth" } androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentialsPlayServicesAuth" }
androidx-lifecycle-runtime-ktx-v262 = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtxVersion" } androidx-lifecycle-runtime-ktx-v262 = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtxVersion" }
androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } androidx-material = { module = "androidx.compose.material:material", version.ref = "material" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "media3Exoplayer" } androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "media3Exoplayer" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Exoplayer" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Exoplayer" }
@@ -62,8 +66,9 @@ androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.r
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" }
coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
compose-image-blurhash = { module = "com.github.orlando-dev-code:compose-image-blurhash", version.ref = "composeImageBlurhash" } compose-image-blurhash = { module = "com.github.orlando-dev-code:compose-image-blurhash", version.ref = "composeImageBlurhash" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" } eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
@@ -79,7 +84,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.9.0" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
@@ -104,7 +109,7 @@ play-services-auth = { module = "com.google.android.gms:play-services-auth", ver
rendering = { group = "com.google.ar.sceneform", name = "rendering", version.ref = "rendering" } rendering = { group = "com.google.ar.sceneform", name = "rendering", version.ref = "rendering" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" }
zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" } zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" }
lottie = { module="com.airbnb.android:lottie-compose", version="6.6.10"} lottie = { module="com.airbnb.android:lottie-compose", version="6.7.0"}
mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkitBarcode" } mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkitBarcode" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
@@ -112,3 +117,5 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref =
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "composeCompiler" }

View File

@@ -1,6 +1,6 @@
#Fri Jun 14 03:23:01 CST 2024 #Fri Jun 14 03:23:01 CST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists