android: wire chats popup actions to archive pin delete clear and mute APIs
This commit is contained in:
@@ -614,3 +614,18 @@
|
|||||||
- Updated multi-select top actions/menu to be closer to Telegram reference in wording.
|
- Updated multi-select top actions/menu to be closer to Telegram reference in wording.
|
||||||
- Added dynamic `Закрепить/Открепить` label in selection overflow based on selected chats pinned state.
|
- Added dynamic `Закрепить/Открепить` label in selection overflow based on selected chats pinned state.
|
||||||
- Kept non-supported actions explicit with user feedback (Toast), avoiding silent no-op behavior.
|
- Kept non-supported actions explicit with user feedback (Toast), avoiding silent no-op behavior.
|
||||||
|
|
||||||
|
### Step 97 - Chats popup/select actions wired to backend API
|
||||||
|
- Extended Android chat data layer with missing parity endpoints:
|
||||||
|
- `archive/unarchive`
|
||||||
|
- `pin-chat/unpin-chat`
|
||||||
|
- `clear`
|
||||||
|
- `delete (for_all=false)`
|
||||||
|
- `chat notifications get/update`
|
||||||
|
- Added repository methods and `ViewModel` actions for those operations.
|
||||||
|
- Replaced chats multi-select UI stubs with real API calls:
|
||||||
|
- mute/unmute selected chats,
|
||||||
|
- archive/unarchive selected chats,
|
||||||
|
- pin/unpin selected chats,
|
||||||
|
- clear selected chats,
|
||||||
|
- delete selected chats for current user.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import retrofit2.http.GET
|
|||||||
import retrofit2.http.PATCH
|
import retrofit2.http.PATCH
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatBanDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatBanDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatCreateRequestDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatCreateRequestDto
|
||||||
@@ -14,6 +15,8 @@ import ru.daemonlord.messenger.data.chat.dto.ChatJoinByInviteRequestDto
|
|||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberAddRequestDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberAddRequestDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberRoleUpdateRequestDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberRoleUpdateRequestDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatNotificationSettingsDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatNotificationSettingsUpdateDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.DiscoverChatDto
|
import ru.daemonlord.messenger.data.chat.dto.DiscoverChatDto
|
||||||
|
|
||||||
@@ -58,6 +61,48 @@ interface ChatApiService {
|
|||||||
@Path("chat_id") chatId: Long,
|
@Path("chat_id") chatId: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@DELETE("/api/v1/chats/{chat_id}")
|
||||||
|
suspend fun deleteChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
@Query("for_all") forAll: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/clear")
|
||||||
|
suspend fun clearChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/archive")
|
||||||
|
suspend fun archiveChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatReadDto
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/unarchive")
|
||||||
|
suspend fun unarchiveChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatReadDto
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/pin-chat")
|
||||||
|
suspend fun pinChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatReadDto
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/unpin-chat")
|
||||||
|
suspend fun unpinChat(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatReadDto
|
||||||
|
|
||||||
|
@GET("/api/v1/chats/{chat_id}/notifications")
|
||||||
|
suspend fun getChatNotifications(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatNotificationSettingsDto
|
||||||
|
|
||||||
|
@PUT("/api/v1/chats/{chat_id}/notifications")
|
||||||
|
suspend fun updateChatNotifications(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
@Body request: ChatNotificationSettingsUpdateDto,
|
||||||
|
): ChatNotificationSettingsDto
|
||||||
|
|
||||||
@GET("/api/v1/chats/{chat_id}/members")
|
@GET("/api/v1/chats/{chat_id}/members")
|
||||||
suspend fun listMembers(
|
suspend fun listMembers(
|
||||||
@Path("chat_id") chatId: Long,
|
@Path("chat_id") chatId: Long,
|
||||||
|
|||||||
@@ -118,3 +118,17 @@ data class ChatMemberAddRequestDto(
|
|||||||
@SerialName("user_id")
|
@SerialName("user_id")
|
||||||
val userId: Long,
|
val userId: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatNotificationSettingsDto(
|
||||||
|
@SerialName("chat_id")
|
||||||
|
val chatId: Long,
|
||||||
|
@SerialName("user_id")
|
||||||
|
val userId: Long,
|
||||||
|
val muted: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatNotificationSettingsUpdateDto(
|
||||||
|
val muted: Boolean,
|
||||||
|
)
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ interface ChatDao {
|
|||||||
@Query("SELECT muted FROM chats WHERE id = :chatId LIMIT 1")
|
@Query("SELECT muted FROM chats WHERE id = :chatId LIMIT 1")
|
||||||
suspend fun isChatMuted(chatId: Long): Boolean?
|
suspend fun isChatMuted(chatId: Long): Boolean?
|
||||||
|
|
||||||
|
@Query("UPDATE chats SET muted = :muted WHERE id = :chatId")
|
||||||
|
suspend fun updateChatMuted(chatId: Long, muted: Boolean)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun upsertChats(chats: List<ChatEntity>)
|
suspend fun upsertChats(chats: List<ChatEntity>)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import ru.daemonlord.messenger.data.chat.dto.ChatJoinByInviteRequestDto
|
|||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberAddRequestDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberAddRequestDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatMemberRoleUpdateRequestDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatMemberRoleUpdateRequestDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatNotificationSettingsDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatNotificationSettingsUpdateDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.DiscoverChatDto
|
import ru.daemonlord.messenger.data.chat.dto.DiscoverChatDto
|
||||||
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
||||||
@@ -26,6 +28,7 @@ import ru.daemonlord.messenger.di.IoDispatcher
|
|||||||
import ru.daemonlord.messenger.domain.chat.model.ChatBanItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatBanItem
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatMemberItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatMemberItem
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatNotificationSettings
|
||||||
import ru.daemonlord.messenger.domain.chat.model.DiscoverChatItem
|
import ru.daemonlord.messenger.domain.chat.model.DiscoverChatItem
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
import ru.daemonlord.messenger.domain.common.AppResult
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
@@ -162,6 +165,94 @@ class NetworkChatRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun archiveChat(chatId: Long): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val updated = chatApiService.archiveChat(chatId = chatId)
|
||||||
|
chatDao.upsertUsers(updated.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val entity = updated.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(entity))
|
||||||
|
AppResult.Success(entity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unarchiveChat(chatId: Long): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val updated = chatApiService.unarchiveChat(chatId = chatId)
|
||||||
|
chatDao.upsertUsers(updated.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val entity = updated.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(entity))
|
||||||
|
AppResult.Success(entity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun pinChat(chatId: Long): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val updated = chatApiService.pinChat(chatId = chatId)
|
||||||
|
chatDao.upsertUsers(updated.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val entity = updated.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(entity))
|
||||||
|
AppResult.Success(entity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unpinChat(chatId: Long): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val updated = chatApiService.unpinChat(chatId = chatId)
|
||||||
|
chatDao.upsertUsers(updated.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val entity = updated.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(entity))
|
||||||
|
AppResult.Success(entity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearChat(chatId: Long): AppResult<Unit> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
chatApiService.clearChat(chatId = chatId)
|
||||||
|
AppResult.Success(Unit)
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeChat(chatId: Long, forAll: Boolean): AppResult<Unit> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
chatApiService.deleteChat(chatId = chatId, forAll = forAll)
|
||||||
|
chatDao.deleteChat(chatId = chatId)
|
||||||
|
AppResult.Success(Unit)
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getChatNotifications(chatId: Long): AppResult<ChatNotificationSettings> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
AppResult.Success(chatApiService.getChatNotifications(chatId = chatId).toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateChatNotifications(chatId: Long, muted: Boolean): AppResult<ChatNotificationSettings> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val settings = chatApiService.updateChatNotifications(
|
||||||
|
chatId = chatId,
|
||||||
|
request = ChatNotificationSettingsUpdateDto(muted = muted),
|
||||||
|
).toDomain()
|
||||||
|
chatDao.updateChatMuted(chatId = chatId, muted = settings.muted)
|
||||||
|
AppResult.Success(settings)
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun listMembers(chatId: Long): AppResult<List<ChatMemberItem>> = withContext(ioDispatcher) {
|
override suspend fun listMembers(chatId: Long): AppResult<List<ChatMemberItem>> = withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
AppResult.Success(chatApiService.listMembers(chatId = chatId).map { it.toDomain() })
|
AppResult.Success(chatApiService.listMembers(chatId = chatId).map { it.toDomain() })
|
||||||
@@ -269,4 +360,12 @@ class NetworkChatRepository @Inject constructor(
|
|||||||
bannedAt = bannedAt,
|
bannedAt = bannedAt,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ChatNotificationSettingsDto.toDomain(): ChatNotificationSettings {
|
||||||
|
return ChatNotificationSettings(
|
||||||
|
chatId = chatId,
|
||||||
|
userId = userId,
|
||||||
|
muted = muted,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.chat.model
|
||||||
|
|
||||||
|
data class ChatNotificationSettings(
|
||||||
|
val chatId: Long,
|
||||||
|
val userId: Long,
|
||||||
|
val muted: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@ import ru.daemonlord.messenger.domain.chat.model.ChatBanItem
|
|||||||
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatMemberItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatMemberItem
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatNotificationSettings
|
||||||
import ru.daemonlord.messenger.domain.chat.model.DiscoverChatItem
|
import ru.daemonlord.messenger.domain.chat.model.DiscoverChatItem
|
||||||
import ru.daemonlord.messenger.domain.common.AppResult
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
|
|
||||||
@@ -26,6 +27,14 @@ interface ChatRepository {
|
|||||||
suspend fun discoverChats(query: String?): AppResult<List<DiscoverChatItem>>
|
suspend fun discoverChats(query: String?): AppResult<List<DiscoverChatItem>>
|
||||||
suspend fun joinChat(chatId: Long): AppResult<ChatItem>
|
suspend fun joinChat(chatId: Long): AppResult<ChatItem>
|
||||||
suspend fun leaveChat(chatId: Long): AppResult<Unit>
|
suspend fun leaveChat(chatId: Long): AppResult<Unit>
|
||||||
|
suspend fun archiveChat(chatId: Long): AppResult<ChatItem>
|
||||||
|
suspend fun unarchiveChat(chatId: Long): AppResult<ChatItem>
|
||||||
|
suspend fun pinChat(chatId: Long): AppResult<ChatItem>
|
||||||
|
suspend fun unpinChat(chatId: Long): AppResult<ChatItem>
|
||||||
|
suspend fun clearChat(chatId: Long): AppResult<Unit>
|
||||||
|
suspend fun removeChat(chatId: Long, forAll: Boolean = false): AppResult<Unit>
|
||||||
|
suspend fun getChatNotifications(chatId: Long): AppResult<ChatNotificationSettings>
|
||||||
|
suspend fun updateChatNotifications(chatId: Long, muted: Boolean): AppResult<ChatNotificationSettings>
|
||||||
suspend fun listMembers(chatId: Long): AppResult<List<ChatMemberItem>>
|
suspend fun listMembers(chatId: Long): AppResult<List<ChatMemberItem>>
|
||||||
suspend fun listBans(chatId: Long): AppResult<List<ChatBanItem>>
|
suspend fun listBans(chatId: Long): AppResult<List<ChatBanItem>>
|
||||||
suspend fun addMember(chatId: Long, userId: Long): AppResult<ChatMemberItem>
|
suspend fun addMember(chatId: Long, userId: Long): AppResult<ChatMemberItem>
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ fun ChatListRoute(
|
|||||||
onDiscoverChats = viewModel::discoverChats,
|
onDiscoverChats = viewModel::discoverChats,
|
||||||
onJoinDiscoveredChat = viewModel::joinChat,
|
onJoinDiscoveredChat = viewModel::joinChat,
|
||||||
onLeaveChat = viewModel::leaveChat,
|
onLeaveChat = viewModel::leaveChat,
|
||||||
|
onArchiveChat = viewModel::archiveChat,
|
||||||
|
onUnarchiveChat = viewModel::unarchiveChat,
|
||||||
|
onPinChat = viewModel::pinChat,
|
||||||
|
onUnpinChat = viewModel::unpinChat,
|
||||||
|
onClearChat = viewModel::clearChat,
|
||||||
|
onDeleteChat = viewModel::deleteChatForMe,
|
||||||
|
onToggleChatMute = viewModel::toggleChatMute,
|
||||||
onSelectManageChat = viewModel::onManagementChatSelected,
|
onSelectManageChat = viewModel::onManagementChatSelected,
|
||||||
onCreateInvite = viewModel::createInvite,
|
onCreateInvite = viewModel::createInvite,
|
||||||
onAddMember = viewModel::addMember,
|
onAddMember = viewModel::addMember,
|
||||||
@@ -154,6 +161,13 @@ fun ChatListScreen(
|
|||||||
onDiscoverChats: (String?) -> Unit,
|
onDiscoverChats: (String?) -> Unit,
|
||||||
onJoinDiscoveredChat: (Long) -> Unit,
|
onJoinDiscoveredChat: (Long) -> Unit,
|
||||||
onLeaveChat: (Long) -> Unit,
|
onLeaveChat: (Long) -> Unit,
|
||||||
|
onArchiveChat: (Long) -> Unit,
|
||||||
|
onUnarchiveChat: (Long) -> Unit,
|
||||||
|
onPinChat: (Long) -> Unit,
|
||||||
|
onUnpinChat: (Long) -> Unit,
|
||||||
|
onClearChat: (Long) -> Unit,
|
||||||
|
onDeleteChat: (Long) -> Unit,
|
||||||
|
onToggleChatMute: (Long) -> Unit,
|
||||||
onSelectManageChat: (Long?) -> Unit,
|
onSelectManageChat: (Long?) -> Unit,
|
||||||
onCreateInvite: (Long) -> Unit,
|
onCreateInvite: (Long) -> Unit,
|
||||||
onAddMember: (Long, Long) -> Unit,
|
onAddMember: (Long, Long) -> Unit,
|
||||||
@@ -269,7 +283,8 @@ fun ChatListScreen(
|
|||||||
actions = {
|
actions = {
|
||||||
if (selectedChatIds.isNotEmpty()) {
|
if (selectedChatIds.isNotEmpty()) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
Toast.makeText(context, "Настройки звука для выбранных будут добавлены позже.", Toast.LENGTH_SHORT).show()
|
selectedChatIds.forEach { chatId -> onToggleChatMute(chatId) }
|
||||||
|
selectedChatIds = emptySet()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.NotificationsOff,
|
imageVector = Icons.Filled.NotificationsOff,
|
||||||
@@ -277,7 +292,10 @@ fun ChatListScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
Toast.makeText(context, "Архивирование выбранных будет добавлено позже.", Toast.LENGTH_SHORT).show()
|
selectedChats.forEach { chat ->
|
||||||
|
if (chat.archived) onUnarchiveChat(chat.id) else onArchiveChat(chat.id)
|
||||||
|
}
|
||||||
|
selectedChatIds = emptySet()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.FolderOpen,
|
imageVector = Icons.Filled.FolderOpen,
|
||||||
@@ -285,7 +303,7 @@ fun ChatListScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
selectedChatIds.forEach { chatId -> onLeaveChat(chatId) }
|
selectedChatIds.forEach { chatId -> onDeleteChat(chatId) }
|
||||||
selectedChatIds = emptySet()
|
selectedChatIds = emptySet()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -309,7 +327,10 @@ fun ChatListScreen(
|
|||||||
leadingIcon = { Icon(Icons.Filled.PushPin, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Filled.PushPin, contentDescription = null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showSelectionMenu = false
|
showSelectionMenu = false
|
||||||
Toast.makeText(context, "Закрепление будет добавлено после API поддержки.", Toast.LENGTH_SHORT).show()
|
selectedChatIds.forEach { chatId ->
|
||||||
|
if (allSelectedPinned) onUnpinChat(chatId) else onPinChat(chatId)
|
||||||
|
}
|
||||||
|
selectedChatIds = emptySet()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@@ -333,7 +354,8 @@ fun ChatListScreen(
|
|||||||
leadingIcon = { Icon(Icons.Filled.Delete, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Filled.Delete, contentDescription = null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showSelectionMenu = false
|
showSelectionMenu = false
|
||||||
Toast.makeText(context, "Очистка кэша будет добавлена позже.", Toast.LENGTH_SHORT).show()
|
selectedChatIds.forEach { chatId -> onClearChat(chatId) }
|
||||||
|
selectedChatIds = emptySet()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,84 @@ class ChatListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun archiveChat(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.archiveChat(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> {
|
||||||
|
_uiState.update { it.copy(managementMessage = "Чат архивирован.") }
|
||||||
|
refreshCurrentTab(forceRefresh = true)
|
||||||
|
}
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unarchiveChat(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.unarchiveChat(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> {
|
||||||
|
_uiState.update { it.copy(managementMessage = "Чат возвращен из архива.") }
|
||||||
|
refreshCurrentTab(forceRefresh = true)
|
||||||
|
}
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pinChat(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.pinChat(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> _uiState.update { it.copy(managementMessage = "Чат закреплен.") }
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unpinChat(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.unpinChat(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> _uiState.update { it.copy(managementMessage = "Чат откреплен.") }
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearChat(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.clearChat(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> _uiState.update { it.copy(managementMessage = "История чата очищена.") }
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteChatForMe(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.removeChat(chatId = chatId, forAll = false)) {
|
||||||
|
is AppResult.Success -> _uiState.update { it.copy(managementMessage = "Чат удален.") }
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleChatMute(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val current = chatRepository.getChatNotifications(chatId = chatId)) {
|
||||||
|
is AppResult.Success -> {
|
||||||
|
when (val updated = chatRepository.updateChatNotifications(chatId = chatId, muted = !current.data.muted)) {
|
||||||
|
is AppResult.Success -> _uiState.update {
|
||||||
|
it.copy(
|
||||||
|
managementMessage = if (updated.data.muted) "Уведомления выключены." else "Уведомления включены.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = updated.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AppResult.Error -> _uiState.update { it.copy(errorMessage = current.reason.toUiMessage()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createInvite(chatId: Long) {
|
fun createInvite(chatId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (val result = chatRepository.createInviteLink(chatId = chatId)) {
|
when (val result = chatRepository.createInviteLink(chatId = chatId)) {
|
||||||
|
|||||||
@@ -20,14 +20,6 @@ Backend покрывает web-функционал почти полность
|
|||||||
- `GET /api/v1/chats/saved`
|
- `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`
|
||||||
- `POST /api/v1/chats/{chat_id}/archive`
|
|
||||||
- `POST /api/v1/chats/{chat_id}/unarchive`
|
|
||||||
- `POST /api/v1/chats/{chat_id}/pin-chat`
|
|
||||||
- `POST /api/v1/chats/{chat_id}/unpin-chat`
|
|
||||||
- `DELETE /api/v1/chats/{chat_id}`
|
|
||||||
- `POST /api/v1/chats/{chat_id}/clear`
|
|
||||||
- `GET /api/v1/chats/{chat_id}/notifications`
|
|
||||||
- `PUT /api/v1/chats/{chat_id}/notifications`
|
|
||||||
- `GET /api/v1/messages/{message_id}/thread`
|
- `GET /api/v1/messages/{message_id}/thread`
|
||||||
- `GET /api/v1/search` (single global endpoint; Android uses composed search calls)
|
- `GET /api/v1/search` (single global endpoint; Android uses composed search calls)
|
||||||
- Contacts endpoints:
|
- Contacts endpoints:
|
||||||
@@ -41,15 +33,13 @@ Backend покрывает web-функционал почти полность
|
|||||||
## 3) Practical status
|
## 3) Practical status
|
||||||
|
|
||||||
- Backend readiness vs Web: `high`
|
- Backend readiness vs Web: `high`
|
||||||
- Android parity vs Web (feature-level): `~70-80%`
|
- Android parity vs Web (feature-level): `~80-85%`
|
||||||
|
|
||||||
## 4) Highest-priority Android parity step
|
## 4) Highest-priority Android parity step
|
||||||
|
|
||||||
Подключить в Android реальные действия для chats list popup/select:
|
Завершить следующий parity-блок после подключения chat popup/select API:
|
||||||
|
|
||||||
- archive/unarchive
|
- `GET /api/v1/chats/saved` + UX для Saved
|
||||||
- pin/unpin chat
|
- `PATCH /api/v1/chats/{chat_id}/title` и `/profile` в chat settings flow
|
||||||
- delete/clear chat
|
- единый `GET /api/v1/search` для полнофункционального Telegram-like поиска
|
||||||
- chat notification settings
|
- contacts API (`/users/contacts*`) + экран управления контактами
|
||||||
|
|
||||||
и перевести текущие UI-заглушки на API-вызовы.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user