diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt index ac618af..8219fd4 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt @@ -279,43 +279,4 @@ fun FriendChatItem( } } } -@Composable -fun ReloadButton( - onClick: () -> Unit -) { - val gradientBrush = Brush.linearGradient( - colors = listOf( - Color(0xFF7c45ed), - Color(0xFF7c68ef), - Color(0xFF7bd8f8) - ) - ) - Button( - onClick = onClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 120.dp) - .height(48.dp), - shape = RoundedCornerShape(30.dp), - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.Transparent - ), - contentPadding = PaddingValues(0.dp) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background(gradientBrush), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource(R.string.Reload), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - textAlign = TextAlign.Center - ) - } - } -} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt index 27e8dd9..fa0d3e3 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt @@ -155,7 +155,8 @@ fun GalleryGrid( modifier = baseModifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(vertical = 60.dp), + .padding(vertical = 60.dp) + .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Image( diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt index a04ed77..8b2be8f 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt @@ -16,15 +16,16 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items 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.ui.graphics.ColorFilter +import androidx.compose.ui.draw.alpha import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.LottieConstants @@ -63,6 +64,8 @@ import com.aiosman.ravenow.ui.navigateToGroupChat import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.data.api.ApiClient import android.util.Base64 +import com.aiosman.ravenow.ui.network.ReloadButton +import com.aiosman.ravenow.utils.NetworkUtils.isNetworkAvailable @OptIn(ExperimentalMaterialApi::class) @Composable @@ -78,6 +81,7 @@ fun GroupChatEmptyContent( val context = LocalContext.current val navController = LocalNavController.current val viewModel = MyProfileViewModel + val networkAvailable = isNetworkAvailable(context) // 如果查看其他用户的房间,固定使用全部类型(filterType = 0) val filterType = if (showSegments) selectedSegment else 0 @@ -133,48 +137,64 @@ fun GroupChatEmptyContent( horizontalAlignment = Alignment.CenterHorizontally ) { // 空状态插图 - EmptyStateIllustration() + EmptyStateIllustration( + isNetworkAvailable = networkAvailable, + onReload = { + MyProfileViewModel.ResetModel() + MyProfileViewModel.loadProfile(pullRefresh = true) + } + ) Spacer(modifier = Modifier.height(9.dp)) - // 空状态文本 - Text( - text = stringResource(R.string.cosmos_awaits), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = AppColors.text, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = 24.dp), - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) + // 网络可用时才展示空状态文本 + if (networkAvailable) { + Text( + text = stringResource(R.string.cosmos_awaits), + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = AppColors.text, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 24.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } } } else { LazyColumn( state = listState, modifier = nestedScrollModifier.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 + // 网格布局:每行显示2个房间卡片 + items( + items = viewModel.rooms.chunked(2), + key = { rowRooms -> rowRooms.firstOrNull()?.id?.toString() ?: "" } + ) { rowRooms -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + rowRooms.forEach { room -> + RoomCard( + room = room, + onRoomClick = { roomEntity -> + // 导航到群聊聊天界面 + navController.navigateToGroupChat( + id = roomEntity.trtcRoomId, + name = roomEntity.name, + avatar = roomEntity.avatar + ) + }, + modifier = Modifier.weight(1f) ) } - ) - - if (index < viewModel.rooms.size - 1) { - HorizontalDivider( - modifier = Modifier.padding(horizontal = 24.dp), - color = AppColors.divider - ) + // 如果这一行只有一个房间,添加一个空的占位符 + if (rowRooms.size == 1) { + Spacer(modifier = Modifier.weight(1f)) + } } } @@ -217,13 +237,15 @@ fun GroupChatEmptyContent( } @Composable -fun RoomItem( +fun RoomCard( room: RoomEntity, - onRoomClick: (RoomEntity) -> Unit = {} + onRoomClick: (RoomEntity) -> Unit = {}, + modifier: Modifier = Modifier ) { val AppColors = LocalAppTheme.current val context = LocalContext.current val roomDebouncer = rememberDebouncer() + val cardSize = 180.dp // 构建头像URL val avatarUrl = if (room.avatar.isNotEmpty()) { @@ -237,6 +259,117 @@ fun RoomItem( "${ApiClient.RETROFIT_URL}group/avatar?groupIdBase64=${groupIdBase64}&token=${AppStore.token}" } + // 优先显示cover,如果没有cover则显示recommendBanner,最后显示avatar + val imageUrl = when { + room.cover.isNotEmpty() -> "${ConstVars.BASE_SERVER}/api/v1/outside/${room.cover}?token=${AppStore.token}" + room.recommendBanner.isNotEmpty() -> "${ConstVars.BASE_SERVER}/api/v1/outside/${room.recommendBanner}?token=${AppStore.token}" + else -> avatarUrl + } + + // 正方形卡片,文字重叠在底部 + Box( + modifier = modifier + .size(cardSize) + .background(AppColors.tabUnselectedBackground, RoundedCornerShape(12.dp)) + .noRippleClickable { + roomDebouncer { + onRoomClick(room) + } + } + ) { + CustomAsyncImage( + context = context, + imageUrl = imageUrl, + contentDescription = room.name, + modifier = Modifier + .width(cardSize) + .height(120.dp) + .clip(RoundedCornerShape( + topStart = 12.dp, + topEnd = 12.dp, + bottomStart = 0.dp, + bottomEnd = 0.dp)), + contentScale = ContentScale.Crop, + defaultRes = R.mipmap.rider_pro_agent + ) + + // 房间名称,重叠在底部 + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(bottom = 32.dp, start = 10.dp, end = 10.dp) + .clip(RoundedCornerShape(12.dp)) + ) { + Text( + text = room.name, + fontSize = 14.sp, + fontWeight = FontWeight.W900, + color = AppColors.text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Left + ) + } + + // 显示人数 + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(bottom = 10.dp, start = 10.dp, end = 10.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(R.drawable.rider_pro_nav_profile), + contentDescription = "chat", + modifier = Modifier.size(16.dp), + colorFilter = ColorFilter.tint(AppColors.secondaryText) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${room.userCount} ${stringResource(R.string.chatting_now)}", + fontSize = 12.sp, + modifier = Modifier + .alpha(0.6f) + .weight(1f), + color = AppColors.text, + fontWeight = FontWeight.W500, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +/** + * 列表样式的房间项,供搜索等场景复用 + */ +@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 { + val groupIdBase64 = Base64.encodeToString( + room.trtcType.toByteArray(), + Base64.NO_WRAP + ) + "${ApiClient.RETROFIT_URL}group/avatar?groupIdBase64=${groupIdBase64}&token=${AppStore.token}" + } + Row( modifier = Modifier .fillMaxWidth() @@ -257,7 +390,7 @@ fun RoomItem( .clip(RoundedCornerShape(12.dp)) ) } - + Column( modifier = Modifier .weight(1f) @@ -277,9 +410,9 @@ fun RoomItem( overflow = TextOverflow.Ellipsis ) } - + Spacer(modifier = Modifier.height(4.dp)) - + Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically @@ -292,9 +425,9 @@ fun RoomItem( overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f) ) - + Spacer(modifier = Modifier.width(8.dp)) - + Text( text = "${room.userCount}人", fontSize = 12.sp, @@ -388,14 +521,46 @@ private fun SegmentButton( } @Composable -private fun EmptyStateIllustration() { - Image( - painter = painterResource(id = R.mipmap.l_empty_img), - contentDescription = "空状态", - modifier = Modifier - .width(181.dp) - .height(153.dp), - contentScale = ContentScale.Fit - ) +private fun EmptyStateIllustration( + isNetworkAvailable: Boolean, + onReload: () -> Unit +) { + val AppColors = LocalAppTheme.current + if (isNetworkAvailable) { + Image( + painter = painterResource(id = R.mipmap.l_empty_img), + contentDescription = "空状态", + modifier = Modifier + .width(181.dp) + .height(153.dp), + contentScale = ContentScale.Fit + ) + } else { + Image( + painter = painterResource(id = R.mipmap.invalid_name_10), + contentDescription = "network error", + modifier = Modifier.size(181.dp), + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = stringResource(R.string.friend_chat_no_network_title), + fontSize = 16.sp, + color = AppColors.text, + fontWeight = FontWeight.W600 + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = stringResource(R.string.friend_chat_no_network_subtitle), + fontSize = 14.sp, + color = AppColors.secondaryText, + fontWeight = FontWeight.W400 + ) + Spacer(modifier = Modifier.height(16.dp)) + ReloadButton(onClick = onReload) + } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/search/SearchScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/search/SearchScreen.kt index eea5e13..edb5edb 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/search/SearchScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/search/SearchScreen.kt @@ -786,42 +786,3 @@ fun SearchPlaceholderContent( } } -@Composable -fun ReloadButton( - onClick: () -> Unit -) { - val gradientBrush = Brush.linearGradient( - colors = listOf( - Color(0xFF7c45ed), - Color(0xFF7c68ef), - Color(0xFF7bd8f8) - ) - ) - - Button( - onClick = onClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 120.dp) - .height(48.dp), - shape = RoundedCornerShape(30.dp), - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.Transparent - ), - contentPadding = PaddingValues(0.dp) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background(gradientBrush), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource(R.string.Reload), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - ) - } - } -} \ No newline at end of file