android: use saved chat endpoint in chats menu
This commit is contained in:
@@ -634,3 +634,8 @@
|
|||||||
- Improved `chat_updated` handling in realtime flow:
|
- Improved `chat_updated` handling in realtime flow:
|
||||||
- now refreshes both active and archived chats lists to sync user-scoped flags (`pinned`, `archived`) immediately.
|
- now refreshes both active and archived chats lists to sync user-scoped flags (`pinned`, `archived`) immediately.
|
||||||
- Added parser fallback for realtime chat events to support payloads with either `chat_id` or `id`.
|
- Added parser fallback for realtime chat events to support payloads with either `chat_id` or `id`.
|
||||||
|
|
||||||
|
### Step 99 - Saved chat API parity
|
||||||
|
- Added Android support for `GET /api/v1/chats/saved`.
|
||||||
|
- Wired chats overflow `Saved` action to real backend request (instead of local title heuristic).
|
||||||
|
- Saved chat is now upserted into local Room cache and opened via normal navigation flow.
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ interface ChatApiService {
|
|||||||
@Path("chat_id") chatId: Long,
|
@Path("chat_id") chatId: Long,
|
||||||
): ChatReadDto
|
): ChatReadDto
|
||||||
|
|
||||||
|
@GET("/api/v1/chats/saved")
|
||||||
|
suspend fun getSavedChat(): ChatReadDto
|
||||||
|
|
||||||
@POST("/api/v1/chats/{chat_id}/invite-link")
|
@POST("/api/v1/chats/{chat_id}/invite-link")
|
||||||
suspend fun createInviteLink(
|
suspend fun createInviteLink(
|
||||||
@Path("chat_id") chatId: Long,
|
@Path("chat_id") chatId: Long,
|
||||||
|
|||||||
@@ -87,6 +87,18 @@ class NetworkChatRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getSavedChat(): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val chat = chatApiService.getSavedChat()
|
||||||
|
chatDao.upsertUsers(chat.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val entity = chat.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(entity))
|
||||||
|
AppResult.Success(entity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink> = withContext(ioDispatcher) {
|
override suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink> = withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
AppResult.Success(chatApiService.createInviteLink(chatId = chatId).toDomain())
|
AppResult.Success(chatApiService.createInviteLink(chatId = chatId).toDomain())
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface ChatRepository {
|
|||||||
fun observeChat(chatId: Long): Flow<ChatItem?>
|
fun observeChat(chatId: Long): Flow<ChatItem?>
|
||||||
suspend fun refreshChats(archived: Boolean): AppResult<Unit>
|
suspend fun refreshChats(archived: Boolean): AppResult<Unit>
|
||||||
suspend fun refreshChat(chatId: Long): AppResult<Unit>
|
suspend fun refreshChat(chatId: Long): AppResult<Unit>
|
||||||
|
suspend fun getSavedChat(): AppResult<ChatItem>
|
||||||
suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink>
|
suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink>
|
||||||
suspend fun joinByInvite(token: String): AppResult<ChatItem>
|
suspend fun joinByInvite(token: String): AppResult<ChatItem>
|
||||||
suspend fun createChat(
|
suspend fun createChat(
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ fun ChatListRoute(
|
|||||||
onClearSearchHistory = viewModel::clearSearchHistory,
|
onClearSearchHistory = viewModel::clearSearchHistory,
|
||||||
onRefresh = viewModel::onPullToRefresh,
|
onRefresh = viewModel::onPullToRefresh,
|
||||||
onOpenChat = onOpenChat,
|
onOpenChat = onOpenChat,
|
||||||
|
onOpenSaved = viewModel::openSavedChat,
|
||||||
isMainBarVisible = isMainBarVisible,
|
isMainBarVisible = isMainBarVisible,
|
||||||
onMainBarVisibilityChanged = onMainBarVisibilityChanged,
|
onMainBarVisibilityChanged = onMainBarVisibilityChanged,
|
||||||
onCreateGroup = viewModel::createGroup,
|
onCreateGroup = viewModel::createGroup,
|
||||||
@@ -154,6 +155,7 @@ fun ChatListScreen(
|
|||||||
onClearSearchHistory: () -> Unit,
|
onClearSearchHistory: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onOpenChat: (Long) -> Unit,
|
onOpenChat: (Long) -> Unit,
|
||||||
|
onOpenSaved: () -> Unit,
|
||||||
isMainBarVisible: Boolean,
|
isMainBarVisible: Boolean,
|
||||||
onMainBarVisibilityChanged: (Boolean) -> Unit,
|
onMainBarVisibilityChanged: (Boolean) -> Unit,
|
||||||
onCreateGroup: (String, List<Long>) -> Unit,
|
onCreateGroup: (String, List<Long>) -> Unit,
|
||||||
@@ -414,15 +416,7 @@ fun ChatListScreen(
|
|||||||
leadingIcon = { Icon(Icons.Filled.Inventory2, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Filled.Inventory2, contentDescription = null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showDefaultMenu = false
|
showDefaultMenu = false
|
||||||
val saved = state.chats.firstOrNull {
|
onOpenSaved()
|
||||||
val title = it.displayTitle.lowercase()
|
|
||||||
title.contains("saved") || title.contains("избран")
|
|
||||||
}
|
|
||||||
if (saved != null) {
|
|
||||||
onOpenChat(saved.id)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Saved chat not found.", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|||||||
@@ -150,6 +150,19 @@ class ChatListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openSavedChat() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.getSavedChat()) {
|
||||||
|
is AppResult.Success -> {
|
||||||
|
_uiState.update { it.copy(pendingOpenChatId = result.data.id) }
|
||||||
|
}
|
||||||
|
is AppResult.Error -> _uiState.update {
|
||||||
|
it.copy(errorMessage = result.reason.toUiMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onManagementChatSelected(chatId: Long?) {
|
fun onManagementChatSelected(chatId: Long?) {
|
||||||
_uiState.update { it.copy(selectedManageChatId = chatId) }
|
_uiState.update { it.copy(selectedManageChatId = chatId) }
|
||||||
if (chatId != null) {
|
if (chatId != null) {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ Backend покрывает web-функционал почти полность
|
|||||||
|
|
||||||
## 2) Web endpoints not yet fully used on Android
|
## 2) Web endpoints not yet fully used on Android
|
||||||
|
|
||||||
- `GET /api/v1/chats/saved`
|
|
||||||
- `PATCH /api/v1/chats/{chat_id}/title`
|
- `PATCH /api/v1/chats/{chat_id}/title`
|
||||||
- `PATCH /api/v1/chats/{chat_id}/profile`
|
- `PATCH /api/v1/chats/{chat_id}/profile`
|
||||||
- `GET /api/v1/messages/{message_id}/thread`
|
- `GET /api/v1/messages/{message_id}/thread`
|
||||||
@@ -33,7 +32,7 @@ Backend покрывает web-функционал почти полность
|
|||||||
## 3) Practical status
|
## 3) Practical status
|
||||||
|
|
||||||
- Backend readiness vs Web: `high`
|
- Backend readiness vs Web: `high`
|
||||||
- Android parity vs Web (feature-level): `~80-85%`
|
- Android parity vs Web (feature-level): `~82-87%`
|
||||||
|
|
||||||
## 4) Highest-priority Android parity step
|
## 4) Highest-priority Android parity step
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user