From 6ecae415718ea763cbb42ed75fb1eccb126e036b Mon Sep 17 00:00:00 2001 From: Eric85710 Date: Thu, 14 May 2026 15:38:10 +0800 Subject: [PATCH] auth to server-V102 --- .../login_v3/data/api/api_class/DM_Basic.kt | 4 +- .../login_v3/home/Message/UI/Message.kt | 202 +++++++++++------- .../Message/ViewModel/Message_ViewModel.kt | 35 ++- .../login_v3/navigation/Main_Screen_tab.kt | 4 - 4 files changed, 148 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt index 3280f74..6d08cb1 100644 --- a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt +++ b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt @@ -61,6 +61,4 @@ data class Reaction( @Json(name = "emoji") val emoji: String, @Json(name = "count") val count: Int, @Json(name = "me_reacted") val meReacted: Boolean -) - - +) \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Message.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Message.kt index f795a5d..ffc4320 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Message.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Message.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -14,6 +15,7 @@ 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.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape @@ -25,6 +27,7 @@ import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -37,6 +40,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,15 +55,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import coil.compose.AsyncImage +import com.example.login_v3.data.api.api_class.ChatRoom import com.example.login_v3.home.Message.UI.Detail.Message_add_contact import com.example.login_v3.home.Message.UI.Detail.Message_contact_list import com.example.login_v3.home.Message.UI.Detail.Scan_QRcode -import com.example.login_v3.home.Message.ViewModel.MessageViewModel +import com.example.login_v3.home.Message.ViewModel.ChatRoomsViewModel import com.example.login_v3.navigation.BottomBarViewModel @@ -133,15 +142,25 @@ fun Tg_Message( } } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun Loaded_Tg_Message( - navController: NavController + navController: NavController, + viewModel: ChatRoomsViewModel = hiltViewModel(), + onRoomClick: (String) -> Unit = {} ) { - val viewModel: MessageViewModel = viewModel() - val contacts by viewModel.contacts.collectAsState() var expanded by remember { mutableStateOf(false) } + // 觀察 ViewModel 的 StateFlow + val rooms by viewModel.roomsState.collectAsState() + + // 進入頁面時主動讀取一次 + LaunchedEffect(Unit) { + viewModel.loadRooms() + } + + Scaffold( containerColor = Color.Transparent, modifier = Modifier @@ -230,79 +249,118 @@ fun Loaded_Tg_Message( modifier = Modifier .fillMaxSize() ) { - Spacer(modifier = Modifier.height(8.dp)) - LazyColumn( - contentPadding = innerPadding - ) { - items(contacts) { contact -> - Card( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(16.dp)) - .padding(6.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), - shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors( - containerColor = Color.Transparent // ✅ 透明背景 + if (rooms.isEmpty()) { + // 顯示空狀態或 Loading (這部分可根據需求擴充) + Box( + modifier = Modifier.fillMaxSize().padding(innerPadding), + contentAlignment = Alignment.Center + ) { + Text("目前沒有聊天室") + } + } else { + LazyColumn(modifier = Modifier.padding(innerPadding)) { + items( + items = rooms, + key = { it.roomId } // 增加效能,防止列表閃爍 + ) { room -> + RoomItem( + room = room, + onClick = onRoomClick + ) + Divider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 0.5.dp, + color = Color.LightGray.copy(alpha = 0.5f) ) - - ) { - Box( - modifier = Modifier - .fillMaxSize() - ) { - - //glass effect - Box( - modifier = Modifier - .matchParentSize() - .blur(10.dp) - ) - Box( - modifier = Modifier - .matchParentSize() - .background( - color = MaterialTheme.colorScheme.primary, - RoundedCornerShape(16.dp) - ) - .border( - 1.dp, - Color.White.copy(alpha = 0.3f), - RoundedCornerShape(16.dp) - ) - ) - - // 前景層:文字 + 圖片,不會被模糊 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(12.dp), - ) { - Image( - painter = painterResource(id = contact.avatarResId), - contentDescription = "${contact.name}'s avatar", - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .border(2.dp, Color.White, CircleShape) - ) - Column(modifier = Modifier.padding(start = 12.dp)) { - Text( - text = contact.name, - style = MaterialTheme.typography.titleMedium - ) - Text( - text = "Online", - style = MaterialTheme.typography.bodySmall, - color = Color.Green - ) - } - } - } } + } + } + + } + } +} + +@Composable +fun RoomItem( + room: ChatRoom, + onClick: (String) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick(room.roomId) } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 1. 頭像處理 + val imageUrl = room.roomIconUrl ?: room.partner?.avatarUrl + AsyncImage( + model = imageUrl, + contentDescription = "Avatar", + modifier = Modifier + .size(50.dp) + .clip(CircleShape) + .background(Color.LightGray) // 載入前的底色 + ) + + Spacer(modifier = Modifier.width(12.dp)) + + // 2. 中間內容:名稱與最後訊息 + Column(modifier = Modifier.weight(1f)) { + Text( + text = room.roomName.ifEmpty { room.partner?.displayName ?: "Unknown" }, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + val lastMsgText = when { + room.lastMessage?.isDeleted == true -> "訊息已刪除" + room.lastMessage?.content != null -> room.lastMessage.content + room.lastMessage?.attachment != null -> "[檔案] ${room.lastMessage.attachment.filename}" + else -> "尚未有訊息" + } + + Text( + text = lastMsgText, + style = MaterialTheme.typography.bodyMedium, + color = Color.Gray, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + // 3. 右側:時間與未讀計數 + Column(horizontalAlignment = Alignment.End) { + // 這裡簡單截取時間字串,實際建議用 DateTimeFormatter 轉格式 + val time = room.lastMessage?.createdAt?.take(10) ?: "" + Text( + text = time, + style = MaterialTheme.typography.labelSmall, + color = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + if (room.unreadCount > 0) { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary), + contentAlignment = Alignment.Center + ) { + Text( + text = if (room.unreadCount > 99) "99+" else room.unreadCount.toString(), + color = Color.White, + fontSize = 10.sp, + fontWeight = FontWeight.Bold + ) } } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt index ece9a59..b6cb505 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt @@ -4,32 +4,31 @@ package com.example.login_v3.home.Message.ViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.login_v3.R +import com.example.login_v3.data.api.api_class.ChatRoom +import com.example.login_v3.data.repository.dm.ChatRoomsRepository +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import javax.inject.Inject -data class Contact( - val name: String, - val avatarResId: Int // 或者用 String 存圖片 URL -) +@HiltViewModel +class ChatRoomsViewModel @Inject constructor( + private val repository: ChatRoomsRepository +) : ViewModel() { -class MessageViewModel : ViewModel() { + private val _roomsState = MutableStateFlow>(emptyList()) + val roomsState: StateFlow> = _roomsState - private val _contacts = MutableStateFlow>(emptyList()) - val contacts: StateFlow> = _contacts - - init { + fun loadRooms() { viewModelScope.launch { - _contacts.value = listOf( - Contact("Alice", R.drawable.avatar_v1), - Contact("Bob", R.drawable.avatar_v1), - Contact("Charlie", R.drawable.avatar_v1), - Contact("David", R.drawable.avatar_v1), - Contact("nigga", R.drawable.avatar_v1), - Contact("gbl", R.drawable.avatar_v1), - Contact("威威夢夢", R.drawable.avatar_v1) - ) + val result = repository.fetchRooms() + result.onSuccess { response -> + _roomsState.value = response.rooms + }.onFailure { + // 處理錯誤,例如更新一個 error state + } } } } diff --git a/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt b/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt index f99f54a..46c6cc7 100644 --- a/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt +++ b/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.example.login_v3.home.Message.ViewModel.MessageViewModel import com.example.login_v3.home.Tg_MarketPlace import com.example.login_v3.home.Message.UI.Tg_Message import com.example.login_v3.home.Tg_Server @@ -108,12 +107,9 @@ fun Screens_NavGraph( paddingValues: PaddingValues, screensViewModel: ScreensViewModel, bottomBarViewModel: BottomBarViewModel, - messageViewModel: MessageViewModel = androidx.lifecycle.viewmodel.compose.viewModel() ) { //nav_wheel_selecting tab val selectedScreen by screensViewModel.selected.collectAsState() - //message_contact_list_import - val contacts by messageViewModel.contacts.collectAsState() when (selectedScreen) {