This commit is contained in:
Eric85710 2026-05-24 16:02:17 +08:00
parent 68c025da5c
commit b8a5ace88a
2 changed files with 118 additions and 2 deletions

View file

@ -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)
} }
@ -255,4 +265,64 @@ fun UserAvatar(
.clip(CircleShape), .clip(CircleShape),
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
)
}
}
}
}
} }

View file

@ -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) {
@ -60,4 +106,4 @@ enum class UserStatus(val color: Color) {
} }
} }
} }
} }