auth to server-V102
This commit is contained in:
parent
4b3ccf1107
commit
6ecae41571
4 changed files with 148 additions and 97 deletions
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue