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

@ -62,5 +62,3 @@ data class Reaction(
@Json(name = "count") val count: Int, @Json(name = "count") val count: Int,
@Json(name = "me_reacted") val meReacted: Boolean @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.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape 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.material.icons.filled.QrCode
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -37,6 +40,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -51,15 +55,20 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip 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.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController 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_add_contact
import com.example.login_v3.home.Message.UI.Detail.Message_contact_list 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.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 import com.example.login_v3.navigation.BottomBarViewModel
@ -133,15 +142,25 @@ fun Tg_Message(
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Loaded_Tg_Message( 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) } var expanded by remember { mutableStateOf(false) }
// 觀察 ViewModel 的 StateFlow
val rooms by viewModel.roomsState.collectAsState()
// 進入頁面時主動讀取一次
LaunchedEffect(Unit) {
viewModel.loadRooms()
}
Scaffold( Scaffold(
containerColor = Color.Transparent, containerColor = Color.Transparent,
modifier = Modifier modifier = Modifier
@ -230,77 +249,116 @@ fun Loaded_Tg_Message(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
Spacer(modifier = Modifier.height(8.dp)) if (rooms.isEmpty()) {
LazyColumn( // 顯示空狀態或 Loading (這部分可根據需求擴充)
contentPadding = innerPadding Box(
) { modifier = Modifier.fillMaxSize().padding(innerPadding),
items(contacts) { contact -> contentAlignment = Alignment.Center
Card( ) {
modifier = Modifier Text("目前沒有聊天室")
.fillMaxWidth() }
.clip(RoundedCornerShape(16.dp)) } else {
.padding(6.dp), LazyColumn(modifier = Modifier.padding(innerPadding)) {
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), items(
shape = RoundedCornerShape(16.dp), items = rooms,
colors = CardDefaults.cardColors( key = { it.roomId } // 增加效能,防止列表閃爍
containerColor = Color.Transparent // ✅ 透明背景 ) { 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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.login_v3.R 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
data class Contact( @HiltViewModel
val name: String, class ChatRoomsViewModel @Inject constructor(
val avatarResId: Int // 或者用 String 存圖片 URL 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()) fun loadRooms() {
val contacts: StateFlow<List<Contact>> = _contacts
init {
viewModelScope.launch { viewModelScope.launch {
_contacts.value = listOf( val result = repository.fetchRooms()
Contact("Alice", R.drawable.avatar_v1), result.onSuccess { response ->
Contact("Bob", R.drawable.avatar_v1), _roomsState.value = response.rooms
Contact("Charlie", R.drawable.avatar_v1), }.onFailure {
Contact("David", R.drawable.avatar_v1), // 處理錯誤,例如更新一個 error state
Contact("nigga", R.drawable.avatar_v1), }
Contact("gbl", R.drawable.avatar_v1),
Contact("威威夢夢", R.drawable.avatar_v1)
)
} }
} }
} }

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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.Tg_MarketPlace
import com.example.login_v3.home.Message.UI.Tg_Message import com.example.login_v3.home.Message.UI.Tg_Message
import com.example.login_v3.home.Tg_Server import com.example.login_v3.home.Tg_Server
@ -108,12 +107,9 @@ fun Screens_NavGraph(
paddingValues: PaddingValues, paddingValues: PaddingValues,
screensViewModel: ScreensViewModel, screensViewModel: ScreensViewModel,
bottomBarViewModel: BottomBarViewModel, bottomBarViewModel: BottomBarViewModel,
messageViewModel: MessageViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
) { ) {
//nav_wheel_selecting tab //nav_wheel_selecting tab
val selectedScreen by screensViewModel.selected.collectAsState() val selectedScreen by screensViewModel.selected.collectAsState()
//message_contact_list_import
val contacts by messageViewModel.contacts.collectAsState()
when (selectedScreen) { when (selectedScreen) {