dm-29
This commit is contained in:
parent
68c025da5c
commit
b8a5ace88a
2 changed files with 118 additions and 2 deletions
|
|
@ -19,26 +19,35 @@ import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Send
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
import androidx.compose.material.icons.filled.AccountCircle
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Send
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
|
@ -63,6 +72,7 @@ fun MessageMessaging(
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
|
||||||
LaunchedEffect(roomId) {
|
LaunchedEffect(roomId) {
|
||||||
viewModel.loadMessages(roomId)
|
viewModel.loadMessages(roomId)
|
||||||
}
|
}
|
||||||
|
|
@ -256,3 +266,63 @@ fun UserAvatar(
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageInputBar(
|
||||||
|
isLoading: Boolean,
|
||||||
|
onSendClick: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var textInput by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// 當發送成功、外面將 isLoading 從 true 變回 false 時,可以順便清空輸入框
|
||||||
|
// 這裡直接在點擊發送時清空,或由外面控制皆可。
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
tonalElevation = 2.dp,
|
||||||
|
modifier = modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = textInput,
|
||||||
|
onValueChange = { textInput = it },
|
||||||
|
placeholder = { Text("輸入訊息...") },
|
||||||
|
modifier = Modifier.weight(1.0f),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
disabledContainerColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent
|
||||||
|
),
|
||||||
|
maxLines = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (textInput.isNotBlank() && !isLoading) {
|
||||||
|
onSendClick(textInput)
|
||||||
|
textInput = "" // 點擊後立即清空輸入框
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = textInput.isNotBlank() && !isLoading
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Send,
|
||||||
|
contentDescription = "發送訊息",
|
||||||
|
tint = if (textInput.isNotBlank()) Color(0xFFDA7029) else Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ 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.api.api_class.ChatRoom
|
||||||
|
import com.example.login_v3.data.api.api_class.Message
|
||||||
import com.example.login_v3.data.repository.dm.ChatRoomsRepository
|
import com.example.login_v3.data.repository.dm.ChatRoomsRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -15,6 +16,16 @@ import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//message sending state
|
||||||
|
sealed class SendMessageState {
|
||||||
|
object Idle : SendMessageState() // 閒置狀態(預設)
|
||||||
|
object Loading : SendMessageState() // 發送中
|
||||||
|
data class Success(val message: Message) : SendMessageState() // 發送成功,並帶回新訊息
|
||||||
|
data class Error(val message: String) : SendMessageState() // 發送失敗,帶有錯誤訊息
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ChatRoomsViewModel @Inject constructor(
|
class ChatRoomsViewModel @Inject constructor(
|
||||||
private val repository: ChatRoomsRepository
|
private val repository: ChatRoomsRepository
|
||||||
|
|
@ -23,13 +34,16 @@ class ChatRoomsViewModel @Inject constructor(
|
||||||
private val _roomsState = MutableStateFlow<List<ChatRoom>>(emptyList())
|
private val _roomsState = MutableStateFlow<List<ChatRoom>>(emptyList())
|
||||||
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
|
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
|
||||||
|
|
||||||
|
// 1. 新增:發送訊息的 UI 狀態 Flow
|
||||||
|
private val _sendMessageState = MutableStateFlow<SendMessageState>(SendMessageState.Idle)
|
||||||
|
val sendMessageState: StateFlow<SendMessageState> = _sendMessageState
|
||||||
|
|
||||||
fun loadRooms() {
|
fun loadRooms() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = repository.fetchRooms()
|
val result = repository.fetchRooms()
|
||||||
result.onSuccess { response ->
|
result.onSuccess { response ->
|
||||||
_roomsState.value = response.rooms
|
_roomsState.value = response.rooms
|
||||||
|
|
||||||
// 打印偵錯訊息
|
|
||||||
response.rooms.forEach { room ->
|
response.rooms.forEach { room ->
|
||||||
val status = room.partner?.status
|
val status = room.partner?.status
|
||||||
val name = room.partner?.displayName
|
val name = room.partner?.displayName
|
||||||
|
|
@ -40,6 +54,38 @@ class ChatRoomsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 新增:發送訊息的 function
|
||||||
|
fun sendMessage(roomId: String, content: String, replyToId: String? = null) {
|
||||||
|
// 如果內容是空的,就直接攔截不送出
|
||||||
|
if (content.isBlank()) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
// 切換成載入中狀態
|
||||||
|
_sendMessageState.value = SendMessageState.Loading
|
||||||
|
|
||||||
|
// 呼叫 repository
|
||||||
|
val result = repository.sendMessage(roomId, content, replyToId)
|
||||||
|
|
||||||
|
result.onSuccess { newMessage ->
|
||||||
|
// 發送成功
|
||||||
|
_sendMessageState.value = SendMessageState.Success(newMessage)
|
||||||
|
|
||||||
|
// 【可選】如果你的聊天訊息列表也是歸這個 ViewModel 管,
|
||||||
|
// 你可以在這裡順便把新訊息加進當前的訊息列表 Flow 中,這樣畫面就會立刻跳出新訊息。
|
||||||
|
|
||||||
|
}.onFailure { error ->
|
||||||
|
// 發送失敗
|
||||||
|
Log.e("ChatRoomsViewModel", "Send message failed", error)
|
||||||
|
_sendMessageState.value = SendMessageState.Error(error.message ?: "未知錯誤")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 新增:重設發送狀態(通常在 UI 顯示完錯誤跳出視窗、或成功送出清空輸入框後呼叫)
|
||||||
|
fun resetSendMessageState() {
|
||||||
|
_sendMessageState.value = SendMessageState.Idle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class UserStatus(val color: Color) {
|
enum class UserStatus(val color: Color) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue