android: add bulk forward core foundation for multi-select
Some checks failed
CI / test (push) Failing after 2m10s
Some checks failed
CI / test (push) Failing after 2m10s
This commit is contained in:
@@ -162,3 +162,10 @@
|
|||||||
- Added safe area insets handling (`WindowInsets.safeDrawing`) for login, chat list, session-check and chat screens.
|
- Added safe area insets handling (`WindowInsets.safeDrawing`) for login, chat list, session-check and chat screens.
|
||||||
- Added bottom composer protection in chat screen with `navigationBarsPadding()` and `imePadding()`.
|
- Added bottom composer protection in chat screen with `navigationBarsPadding()` and `imePadding()`.
|
||||||
- Fixed UI overlap with status bar and navigation bar on modern Android devices.
|
- Fixed UI overlap with status bar and navigation bar on modern Android devices.
|
||||||
|
|
||||||
|
### Step 26 - Core base / bulk forward foundation
|
||||||
|
- Added message API/data contracts for bulk forward (`POST /api/v1/messages/{message_id}/forward-bulk`).
|
||||||
|
- Extended `MessageRepository` with `forwardMessageBulk(...)`.
|
||||||
|
- Implemented bulk-forward flow in `NetworkMessageRepository` with Room/chat last-message updates.
|
||||||
|
- Added `ForwardMessageBulkUseCase` for future multi-select message actions.
|
||||||
|
- Updated message repository unit test fakes to cover new API surface.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import retrofit2.http.Path
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
||||||
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardBulkRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReactionDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReactionDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
||||||
@@ -51,6 +52,12 @@ interface MessageApiService {
|
|||||||
@Body request: MessageForwardRequestDto,
|
@Body request: MessageForwardRequestDto,
|
||||||
): MessageReadDto
|
): MessageReadDto
|
||||||
|
|
||||||
|
@POST("/api/v1/messages/{message_id}/forward-bulk")
|
||||||
|
suspend fun forwardMessageBulk(
|
||||||
|
@Path("message_id") messageId: Long,
|
||||||
|
@Body request: MessageForwardBulkRequestDto,
|
||||||
|
): List<MessageReadDto>
|
||||||
|
|
||||||
@GET("/api/v1/messages/{message_id}/reactions")
|
@GET("/api/v1/messages/{message_id}/reactions")
|
||||||
suspend fun listReactions(
|
suspend fun listReactions(
|
||||||
@Path("message_id") messageId: Long,
|
@Path("message_id") messageId: Long,
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ data class MessageForwardRequestDto(
|
|||||||
val includeAuthor: Boolean = true,
|
val includeAuthor: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MessageForwardBulkRequestDto(
|
||||||
|
@SerialName("target_chat_ids")
|
||||||
|
val targetChatIds: List<Long>,
|
||||||
|
@SerialName("include_author")
|
||||||
|
val includeAuthor: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MessageReactionDto(
|
data class MessageReactionDto(
|
||||||
val emoji: String,
|
val emoji: String,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import ru.daemonlord.messenger.domain.message.model.MessageReaction
|
|||||||
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
||||||
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardBulkRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageStatusUpdateRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageStatusUpdateRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageUpdateRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageUpdateRequestDto
|
||||||
@@ -313,6 +314,38 @@ class NetworkMessageRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun forwardMessageBulk(
|
||||||
|
messageId: Long,
|
||||||
|
targetChatIds: List<Long>,
|
||||||
|
includeAuthor: Boolean,
|
||||||
|
): AppResult<Unit> = withContext(ioDispatcher) {
|
||||||
|
if (targetChatIds.isEmpty()) return@withContext AppResult.Success(Unit)
|
||||||
|
try {
|
||||||
|
val forwarded = messageApiService.forwardMessageBulk(
|
||||||
|
messageId = messageId,
|
||||||
|
request = MessageForwardBulkRequestDto(
|
||||||
|
targetChatIds = targetChatIds,
|
||||||
|
includeAuthor = includeAuthor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (forwarded.isNotEmpty()) {
|
||||||
|
messageDao.upsertMessages(forwarded.map { it.toEntity() })
|
||||||
|
forwarded.forEach { message ->
|
||||||
|
chatDao.updateLastMessage(
|
||||||
|
chatId = message.chatId,
|
||||||
|
lastMessageText = message.text,
|
||||||
|
lastMessageType = message.type,
|
||||||
|
lastMessageCreatedAt = message.createdAt,
|
||||||
|
updatedSortAt = message.createdAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppResult.Success(Unit)
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun listReactions(messageId: Long): AppResult<List<MessageReaction>> = withContext(ioDispatcher) {
|
override suspend fun listReactions(messageId: Long): AppResult<List<MessageReaction>> = withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
val reactions = messageApiService.listReactions(messageId = messageId).map { it.toDomain() }
|
val reactions = messageApiService.listReactions(messageId = messageId).map { it.toDomain() }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface MessageRepository {
|
|||||||
suspend fun markMessageDelivered(chatId: Long, messageId: Long): AppResult<Unit>
|
suspend fun markMessageDelivered(chatId: Long, messageId: Long): AppResult<Unit>
|
||||||
suspend fun markMessageRead(chatId: Long, messageId: Long): AppResult<Unit>
|
suspend fun markMessageRead(chatId: Long, messageId: Long): AppResult<Unit>
|
||||||
suspend fun forwardMessage(messageId: Long, targetChatId: Long, includeAuthor: Boolean = true): AppResult<Unit>
|
suspend fun forwardMessage(messageId: Long, targetChatId: Long, includeAuthor: Boolean = true): AppResult<Unit>
|
||||||
|
suspend fun forwardMessageBulk(messageId: Long, targetChatIds: List<Long>, includeAuthor: Boolean = true): AppResult<Unit>
|
||||||
suspend fun listReactions(messageId: Long): AppResult<List<MessageReaction>>
|
suspend fun listReactions(messageId: Long): AppResult<List<MessageReaction>>
|
||||||
suspend fun toggleReaction(messageId: Long, emoji: String): AppResult<List<MessageReaction>>
|
suspend fun toggleReaction(messageId: Long, emoji: String): AppResult<List<MessageReaction>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.message.usecase
|
||||||
|
|
||||||
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
|
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ForwardMessageBulkUseCase @Inject constructor(
|
||||||
|
private val repository: MessageRepository,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(
|
||||||
|
messageId: Long,
|
||||||
|
targetChatIds: List<Long>,
|
||||||
|
includeAuthor: Boolean = true,
|
||||||
|
): AppResult<Unit> {
|
||||||
|
return repository.forwardMessageBulk(
|
||||||
|
messageId = messageId,
|
||||||
|
targetChatIds = targetChatIds,
|
||||||
|
includeAuthor = includeAuthor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import ru.daemonlord.messenger.data.media.dto.UploadUrlResponseDto
|
|||||||
import ru.daemonlord.messenger.data.message.api.MessageApiService
|
import ru.daemonlord.messenger.data.message.api.MessageApiService
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardRequestDto
|
||||||
|
import ru.daemonlord.messenger.data.message.dto.MessageForwardBulkRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReactionDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReactionDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReactionToggleRequestDto
|
||||||
@@ -145,6 +146,18 @@ class NetworkMessageRepositoryTest {
|
|||||||
return sendResponse.copy(id = messageId + 1000, chatId = request.targetChatId)
|
return sendResponse.copy(id = messageId + 1000, chatId = request.targetChatId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun forwardMessageBulk(
|
||||||
|
messageId: Long,
|
||||||
|
request: MessageForwardBulkRequestDto,
|
||||||
|
): List<MessageReadDto> {
|
||||||
|
return request.targetChatIds.mapIndexed { index, chatId ->
|
||||||
|
sendResponse.copy(
|
||||||
|
id = messageId + 2000 + index,
|
||||||
|
chatId = chatId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun listReactions(messageId: Long): List<MessageReactionDto> = emptyList()
|
override suspend fun listReactions(messageId: Long): List<MessageReactionDto> = emptyList()
|
||||||
|
|
||||||
override suspend fun toggleReaction(
|
override suspend fun toggleReaction(
|
||||||
|
|||||||
Reference in New Issue
Block a user