auth to server-V102

This commit is contained in:
Eric85710 2026-05-14 15:38:10 +08:00
parent 4b3ccf1107
commit 6ecae41571
4 changed files with 148 additions and 97 deletions

View file

@ -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
)
)

View file

@ -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
)
}
}
}
}
}
}

View file

@ -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<List<ChatRoom>>(emptyList())
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
private val _contacts = MutableStateFlow<List<Contact>>(emptyList())
val contacts: StateFlow<List<Contact>> = _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
}
}
}
}

View file

@ -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) {