add delta sync for dm-v2
This commit is contained in:
parent
6466a36547
commit
9df448b79d
6 changed files with 206 additions and 105 deletions
|
|
@ -2,7 +2,10 @@ package com.example.login_v3.data.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import com.example.login_v3.data.local.dao.BlockedUserDao
|
||||||
|
import com.example.login_v3.data.local.dao.FriendDao
|
||||||
import com.example.login_v3.data.local.dao.MessageDao
|
import com.example.login_v3.data.local.dao.MessageDao
|
||||||
|
import com.example.login_v3.data.local.dao.RoomLocalDao
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -31,7 +34,24 @@ object DatabaseModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideMessageDao(database: AppDatabase): MessageDao {
|
fun provideMessageDao(database: AppDatabase): MessageDao {
|
||||||
// 💡 告訴 Hilt:當有人要 MessageDao 時,就從 database 裡面把 messageDao() 拿出來提供給他!
|
|
||||||
return database.messageDao()
|
return database.messageDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFriendDao(database: AppDatabase): FriendDao {
|
||||||
|
return database.friendDao()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideRoomLocalDao(database: AppDatabase): RoomLocalDao {
|
||||||
|
return database.roomLocalDao()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideBlockedUserDao(database: AppDatabase): BlockedUserDao {
|
||||||
|
return database.blockedUserDao()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,13 +7,31 @@ import com.example.login_v3.data.api.api_class.Friend
|
||||||
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
||||||
import com.example.login_v3.data.api.api_class.UserDetail
|
import com.example.login_v3.data.api.api_class.UserDetail
|
||||||
import com.example.login_v3.data.api.api_class.UserSearchResponse
|
import com.example.login_v3.data.api.api_class.UserSearchResponse
|
||||||
|
import com.example.login_v3.data.local.dao.FriendDao
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FriendsRepository @Inject constructor(
|
class FriendsRepository @Inject constructor(
|
||||||
private val apiService: TecnologiaApi
|
private val apiService: TecnologiaApi,
|
||||||
|
private val friendDao: FriendDao
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// 🌟 核心:監聽本地資料庫的好友列表 Flow
|
||||||
|
val friendsFlow: Flow<List<Friend>> = friendDao.getAllFriendsFlow().map { entities ->
|
||||||
|
entities.map { entity ->
|
||||||
|
Friend(
|
||||||
|
friendId = entity.userId,
|
||||||
|
username = entity.username,
|
||||||
|
displayName = entity.displayName,
|
||||||
|
avatarUrl = entity.avatarUrl,
|
||||||
|
status = entity.status,
|
||||||
|
acceptedAt = null // Sync API 目前沒給這個,先設為 null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getFriendList(): Result<List<Friend>> {
|
suspend fun getFriendList(): Result<List<Friend>> {
|
||||||
return try {
|
return try {
|
||||||
val response = apiService.getFriends()
|
val response = apiService.getFriends()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.example.login_v3.data.repository.basic
|
package com.example.login_v3.data.repository.basic
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import com.example.login_v3.data.api.TecnologiaApi
|
import com.example.login_v3.data.api.TecnologiaApi
|
||||||
import com.example.login_v3.data.di.AppDatabase
|
import com.example.login_v3.data.di.AppDatabase
|
||||||
|
|
@ -8,6 +9,8 @@ import com.example.login_v3.data.local.entities.BlockedUserLocalEntity
|
||||||
import com.example.login_v3.data.local.entities.FriendLocalEntity
|
import com.example.login_v3.data.local.entities.FriendLocalEntity
|
||||||
import com.example.login_v3.data.local.entities.RoomLocalEntity
|
import com.example.login_v3.data.local.entities.RoomLocalEntity
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -21,85 +24,120 @@ class SyncRepository @Inject constructor(
|
||||||
private val roomDao = db.roomLocalDao()
|
private val roomDao = db.roomLocalDao()
|
||||||
private val blockDao = db.blockedUserDao()
|
private val blockDao = db.blockedUserDao()
|
||||||
|
|
||||||
|
// 🔒 使用 Mutex 確保同一時間只有一個同步任務在執行,避免重複請求
|
||||||
|
private val syncMutex = Mutex()
|
||||||
|
|
||||||
suspend fun performSync(): Result<Unit> {
|
suspend fun performSync(): Result<Unit> {
|
||||||
return try {
|
// 如果已經有同步在跑,直接回傳成功(避免重複排隊)
|
||||||
val since = userPrefs.syncSinceFlow.first()
|
if (syncMutex.isLocked) {
|
||||||
val response = api.sync(since)
|
Log.d("SyncDebug", "⏳ [Sync] 偵測到已有同步任務正在進行,跳過本次請求")
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
return syncMutex.withLock {
|
||||||
val body = response.body() ?: return Result.failure(Exception("Sync body is null"))
|
try {
|
||||||
|
val since = userPrefs.syncSinceFlow.first()
|
||||||
|
Log.d("SyncDebug", "🚀 [Sync] 開始同步 | since: $since")
|
||||||
|
|
||||||
db.withTransaction {
|
val response = api.sync(since)
|
||||||
if (body.fullSnapshot) {
|
|
||||||
friendDao.deleteAll()
|
|
||||||
roomDao.deleteAll()
|
|
||||||
blockDao.deleteAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process Blocks
|
if (response.isSuccessful) {
|
||||||
body.blocks.forEach { block ->
|
val body = response.body() ?: return Result.failure(Exception("Sync body is null"))
|
||||||
if (block.removed) {
|
|
||||||
blockDao.deleteById(block.userId)
|
Log.d("SyncDebug", "✅ [Sync] API 成功 | FullSnapshot: ${body.fullSnapshot} | nextSince: ${body.nextSince}")
|
||||||
} else {
|
Log.d("SyncDebug", "📦 [Sync] 收到: Blocks(${body.blocks.size}), Friends(${body.friends.size}), Rooms(${body.rooms.size})")
|
||||||
blockDao.insertOrUpdate(listOf(
|
|
||||||
BlockedUserLocalEntity(
|
db.withTransaction {
|
||||||
userId = block.userId,
|
if (body.fullSnapshot) {
|
||||||
username = block.username,
|
Log.w("SyncDebug", "⚠️ [Sync] 收到 FullSnapshot,正在清空本地舊資料...")
|
||||||
displayName = block.displayName,
|
friendDao.deleteAll()
|
||||||
avatarUrl = block.avatarUrl,
|
roomDao.deleteAll()
|
||||||
blockedAt = block.blockedAt
|
blockDao.deleteAll()
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Process Friends
|
// 1. 處理黑名單
|
||||||
body.friends.forEach { friend ->
|
body.blocks.forEach { block ->
|
||||||
if (friend.removed) {
|
if (block.removed) {
|
||||||
friendDao.deleteById(friend.userId)
|
blockDao.deleteById(block.userId)
|
||||||
} else {
|
} else {
|
||||||
friendDao.insertOrUpdate(listOf(
|
blockDao.insertOrUpdate(listOf(
|
||||||
FriendLocalEntity(
|
BlockedUserLocalEntity(
|
||||||
userId = friend.userId,
|
userId = block.userId,
|
||||||
username = friend.username,
|
username = block.username,
|
||||||
displayName = friend.displayName,
|
displayName = block.displayName,
|
||||||
avatarUrl = friend.avatarUrl,
|
avatarUrl = block.avatarUrl,
|
||||||
status = friend.status,
|
blockedAt = block.blockedAt
|
||||||
presenceStatus = friend.presenceStatus
|
)
|
||||||
)
|
))
|
||||||
))
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Process Rooms
|
// 2. 處理好友
|
||||||
body.rooms.forEach { room ->
|
body.friends.forEach { friend ->
|
||||||
if (room.removed) {
|
if (friend.removed) {
|
||||||
roomDao.deleteById(room.roomId)
|
friendDao.deleteById(friend.userId)
|
||||||
} else {
|
} else {
|
||||||
roomDao.insertOrUpdate(listOf(
|
friendDao.insertOrUpdate(listOf(
|
||||||
RoomLocalEntity(
|
FriendLocalEntity(
|
||||||
roomId = room.roomId,
|
userId = friend.userId,
|
||||||
nickname = room.nickname,
|
username = friend.username,
|
||||||
isHidden = room.isHidden,
|
displayName = friend.displayName,
|
||||||
isMuted = room.isMuted,
|
avatarUrl = friend.avatarUrl,
|
||||||
isPinned = room.isPinned,
|
status = friend.status,
|
||||||
unreadCount = room.unreadCount,
|
presenceStatus = friend.presenceStatus
|
||||||
mentionCount = room.mentionCount,
|
)
|
||||||
partner = room.partner,
|
))
|
||||||
lastMessage = room.lastMessage
|
}
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
userPrefs.saveSyncSince(body.nextSince)
|
// 3. 處理聊天室
|
||||||
|
body.rooms.forEach { room ->
|
||||||
|
if (room.removed) {
|
||||||
|
roomDao.deleteById(room.roomId)
|
||||||
|
} else {
|
||||||
|
roomDao.insertOrUpdate(listOf(
|
||||||
|
RoomLocalEntity(
|
||||||
|
roomId = room.roomId,
|
||||||
|
nickname = room.nickname,
|
||||||
|
isHidden = room.isHidden,
|
||||||
|
isMuted = room.isMuted,
|
||||||
|
isPinned = room.isPinned,
|
||||||
|
unreadCount = room.unreadCount,
|
||||||
|
mentionCount = room.mentionCount,
|
||||||
|
partner = room.partner,
|
||||||
|
lastMessage = room.lastMessage
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userPrefs.saveSyncSince(body.nextSince)
|
||||||
|
Log.d("SyncDebug", "💾 [Sync] 同步完成,已更新 nextSince 為 ${body.nextSince}")
|
||||||
|
}
|
||||||
|
Result.success(Unit)
|
||||||
|
} else {
|
||||||
|
Log.e("SyncDebug", "❌ [Sync] API 失敗 | Code: ${response.code()}")
|
||||||
|
Result.failure(Exception("Sync API failed: ${response.code()}"))
|
||||||
}
|
}
|
||||||
Result.success(Unit)
|
} catch (e: Exception) {
|
||||||
} else {
|
Log.e("SyncDebug", "❌ [Sync] 發生異常", e)
|
||||||
Result.failure(Exception("Sync API failed: ${response.code()}"))
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🛠️ 調試專用:重置同步狀態並清空本地資料
|
||||||
|
* 呼叫此方法後,下一次同步將會從 since=0 開始抓取完整資料
|
||||||
|
*/
|
||||||
|
suspend fun resetSyncAndClearData() {
|
||||||
|
Log.w("SyncDebug", "🧨 [Debug] 正在重置同步狀態並清空資料庫...")
|
||||||
|
db.withTransaction {
|
||||||
|
friendDao.deleteAll()
|
||||||
|
roomDao.deleteAll()
|
||||||
|
blockDao.deleteAll()
|
||||||
|
userPrefs.saveSyncSince(0L)
|
||||||
|
}
|
||||||
|
Log.d("SyncDebug", "✅ [Debug] 重置完成")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ import com.example.login_v3.data.api.api_class.WebSocketEventResponse
|
||||||
import com.example.login_v3.data.di.ChatWebSocketManager
|
import com.example.login_v3.data.di.ChatWebSocketManager
|
||||||
import com.example.login_v3.data.repository.basic.TokenManager
|
import com.example.login_v3.data.repository.basic.TokenManager
|
||||||
import com.example.login_v3.data.local.dao.MessageDao
|
import com.example.login_v3.data.local.dao.MessageDao
|
||||||
|
import com.example.login_v3.data.local.dao.RoomLocalDao
|
||||||
import com.example.login_v3.data.local.entities.MessageEntity
|
import com.example.login_v3.data.local.entities.MessageEntity
|
||||||
import com.example.login_v3.data.local.entities.MessageStatus
|
import com.example.login_v3.data.local.entities.MessageStatus
|
||||||
|
import com.example.login_v3.data.local.entities.RoomLocalEntity
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
|
|
@ -52,12 +54,31 @@ import kotlin.jvm.java
|
||||||
class ChatRoomsRepository @Inject constructor(
|
class ChatRoomsRepository @Inject constructor(
|
||||||
private val api: TecnologiaApi,
|
private val api: TecnologiaApi,
|
||||||
private val messageDao: MessageDao,
|
private val messageDao: MessageDao,
|
||||||
|
private val roomLocalDao: RoomLocalDao, // 👈 新增:注入房間 DAO
|
||||||
private val webSocketManager: ChatWebSocketManager, // 👈 1. 注入 Socket 管理器
|
private val webSocketManager: ChatWebSocketManager, // 👈 1. 注入 Socket 管理器
|
||||||
private val moshi: Moshi, // 👈 用於解析 Socket 傳來的 JSON
|
private val moshi: Moshi, // 👈 用於解析 Socket 傳來的 JSON
|
||||||
private val tokenManager: TokenManager,
|
private val tokenManager: TokenManager,
|
||||||
@ApplicationContext private val context: Context // 注入 ApplicationContext 用於處理 Uri 檔案
|
@ApplicationContext private val context: Context // 注入 ApplicationContext 用於處理 Uri 檔案
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// 🌟 核心:監聽本地資料庫的聊天室列表 Flow
|
||||||
|
val chatRoomsFlow: Flow<List<ChatRoom>> = roomLocalDao.getAllRoomsFlow().map { entities ->
|
||||||
|
entities.map { entity ->
|
||||||
|
ChatRoom(
|
||||||
|
roomId = entity.roomId,
|
||||||
|
roomName = entity.nickname, // 或者 entity.partner?.displayName ? 根據 DTO 結構映射
|
||||||
|
roomType = null, // Entity 暫時沒存,可視需求補上
|
||||||
|
roomIconUrl = null, // Entity 暫時沒存,可視需求補上
|
||||||
|
isMuted = entity.isMuted,
|
||||||
|
isPinned = entity.isPinned,
|
||||||
|
unreadCount = entity.unreadCount,
|
||||||
|
mentionCount = entity.mentionCount,
|
||||||
|
partner = entity.partner,
|
||||||
|
lastMessage = entity.lastMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 建立一個跟著 Repository 生命週期走的 Scope
|
// 建立一個跟著 Repository 生命週期走的 Scope
|
||||||
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.example.login_v3.data.api.api_class.Friend
|
||||||
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
||||||
import com.example.login_v3.data.api.api_class.UserDetail
|
import com.example.login_v3.data.api.api_class.UserDetail
|
||||||
import com.example.login_v3.data.repository.basic.FriendsRepository
|
import com.example.login_v3.data.repository.basic.FriendsRepository
|
||||||
|
import com.example.login_v3.data.repository.basic.SyncRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
@ -31,32 +32,42 @@ data class ContactUiState(
|
||||||
)
|
)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ContactListViewModel @Inject constructor(
|
class ContactListViewModel @Inject constructor(
|
||||||
private val repository: FriendsRepository
|
private val repository: FriendsRepository,
|
||||||
|
private val syncRepository: SyncRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(ContactUiState())
|
private val _uiState = MutableStateFlow(ContactUiState())
|
||||||
val uiState: StateFlow<ContactUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<ContactUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// 1. 監聽本地好友 Flow,一旦資料庫變動,UI 自動刷新
|
||||||
|
observeFriends()
|
||||||
|
// 2. 啟動時跑一次同步
|
||||||
refreshAll()
|
refreshAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeFriends() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.friendsFlow.collect { friends ->
|
||||||
|
_uiState.update { it.copy(friends = friends) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun refreshAll() {
|
fun refreshAll() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// 1. 開始 Loading 並清除舊錯誤
|
|
||||||
_uiState.update { it.copy(isLoading = true, error = null) }
|
_uiState.update { it.copy(isLoading = true, error = null) }
|
||||||
|
|
||||||
// 2. 使用 zip 或單獨 fetch,這裡示範簡單的循序或並行處理
|
// 🎯 同步核心:跑 Delta-Sync
|
||||||
val friendsResult = repository.getFriendList()
|
val syncResult = syncRepository.performSync()
|
||||||
|
// 同時還是要抓一下 Pending Request (目前 Sync API 沒給這塊)
|
||||||
val pendingResult = repository.getPendingRequests()
|
val pendingResult = repository.getPendingRequests()
|
||||||
|
|
||||||
// 3. 統一處理結果
|
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
friends = friendsResult.getOrDefault(state.friends),
|
|
||||||
pendingRequests = pendingResult.getOrDefault(state.pendingRequests),
|
pendingRequests = pendingResult.getOrDefault(state.pendingRequests),
|
||||||
error = (friendsResult.exceptionOrNull() ?: pendingResult.exceptionOrNull())?.message
|
error = (syncResult.exceptionOrNull() ?: pendingResult.exceptionOrNull())?.message
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,49 +9,42 @@ 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.api.api_class.Message
|
||||||
import com.example.login_v3.data.repository.dm.ChatRoomsRepository
|
import com.example.login_v3.data.repository.dm.ChatRoomsRepository
|
||||||
|
import com.example.login_v3.data.repository.basic.SyncRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ChatRoomsViewModel @Inject constructor(
|
class ChatRoomsViewModel @Inject constructor(
|
||||||
private val repository: ChatRoomsRepository
|
private val repository: ChatRoomsRepository,
|
||||||
|
private val syncRepository: SyncRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeRooms()
|
||||||
|
loadRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRooms() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.chatRoomsFlow.collectLatest { rooms ->
|
||||||
|
_roomsState.value = rooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadRooms() {
|
fun loadRooms() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
Log.d("ChatRoomsDebug", "1. 開始呼叫 loadRooms()...")
|
Log.d("ChatRoomsDebug", "📡 [ChatRoomsViewModel] 觸發同步...")
|
||||||
val result = repository.fetchRooms()
|
val result = syncRepository.performSync()
|
||||||
|
result.onFailure { error ->
|
||||||
result.onSuccess { response ->
|
Log.e("ChatRoomsDebug", "❌ 同步失敗", error)
|
||||||
val roomsList = response.rooms
|
|
||||||
Log.d("ChatRoomsDebug", "2. API 請求成功!拿到 ${roomsList?.size ?: 0} 個聊天室")
|
|
||||||
|
|
||||||
if (roomsList.isNullOrEmpty()) {
|
|
||||||
Log.w("ChatRoomsDebug", "⚠️ 注意:後端回傳的 rooms 列表是空的(或為 null)!")
|
|
||||||
} else {
|
|
||||||
roomsList.forEach { room ->
|
|
||||||
Log.d("ChatRoomsDebug", "-> 房間 ID: ${room.roomId}, 名稱: ${room.roomName}, Partner: ${room.partner?.displayName}, 狀態: ${room.partner?.status}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新狀態
|
|
||||||
_roomsState.value = roomsList ?: emptyList()
|
|
||||||
Log.d("ChatRoomsDebug", "3. _roomsState 已更新,當前長度: ${_roomsState.value.size}")
|
|
||||||
|
|
||||||
}.onFailure { error ->
|
|
||||||
// ⭐ 這裡非常重要,一定要印出 stackTrace 才知道為什麼失敗(例如:JSON 解析失敗、404、網路斷線)
|
|
||||||
Log.e("ChatRoomsDebug", "❌ API 請求失敗 (onFailure)!!", error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue