android: add reply-forward preview data foundation
Some checks are pending
CI / test (push) Has started running
Some checks are pending
CI / test (push) Has started running
This commit is contained in:
@@ -186,3 +186,12 @@
|
|||||||
### Step 29 - Core base / multi-select delete execution
|
### Step 29 - Core base / multi-select delete execution
|
||||||
- Fixed multi-select delete behavior in `ChatViewModel`: `Delete` now applies to all selected messages, not only focused one.
|
- Fixed multi-select delete behavior in `ChatViewModel`: `Delete` now applies to all selected messages, not only focused one.
|
||||||
- Added explicit guard for `Delete for all` in multi-select mode (single-message only).
|
- Added explicit guard for `Delete for all` in multi-select mode (single-message only).
|
||||||
|
|
||||||
|
### Step 30 - Core base / reply-forward preview data foundation
|
||||||
|
- Extended message DTO/Room/domain models with optional preview metadata:
|
||||||
|
- `replyPreviewText`, `replyPreviewSenderName`
|
||||||
|
- `forwardedFromDisplayName`
|
||||||
|
- sender profile fields from API payload (`senderDisplayName`, `senderUsername`, `senderAvatarUrl`)
|
||||||
|
- Added Room self-relation in `MessageLocalModel` to resolve reply preview fallback from referenced message.
|
||||||
|
- Updated message mappers and repository/realtime temporary entity creation for new model fields.
|
||||||
|
- Bumped Room schema version to `7`.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
|
|||||||
MessageEntity::class,
|
MessageEntity::class,
|
||||||
MessageAttachmentEntity::class,
|
MessageAttachmentEntity::class,
|
||||||
],
|
],
|
||||||
version = 6,
|
version = 7,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
abstract class MessengerDatabase : RoomDatabase() {
|
abstract class MessengerDatabase : RoomDatabase() {
|
||||||
|
|||||||
@@ -10,10 +10,22 @@ data class MessageReadDto(
|
|||||||
val chatId: Long,
|
val chatId: Long,
|
||||||
@SerialName("sender_id")
|
@SerialName("sender_id")
|
||||||
val senderId: Long,
|
val senderId: Long,
|
||||||
|
@SerialName("sender_display_name")
|
||||||
|
val senderDisplayName: String? = null,
|
||||||
|
@SerialName("sender_username")
|
||||||
|
val senderUsername: String? = null,
|
||||||
|
@SerialName("sender_avatar_url")
|
||||||
|
val senderAvatarUrl: String? = null,
|
||||||
@SerialName("reply_to_message_id")
|
@SerialName("reply_to_message_id")
|
||||||
val replyToMessageId: Long? = null,
|
val replyToMessageId: Long? = null,
|
||||||
|
@SerialName("reply_preview_text")
|
||||||
|
val replyPreviewText: String? = null,
|
||||||
|
@SerialName("reply_preview_sender_name")
|
||||||
|
val replyPreviewSenderName: String? = null,
|
||||||
@SerialName("forwarded_from_message_id")
|
@SerialName("forwarded_from_message_id")
|
||||||
val forwardedFromMessageId: Long? = null,
|
val forwardedFromMessageId: Long? = null,
|
||||||
|
@SerialName("forwarded_from_display_name")
|
||||||
|
val forwardedFromDisplayName: String? = null,
|
||||||
val type: String,
|
val type: String,
|
||||||
val text: String? = null,
|
val text: String? = null,
|
||||||
@SerialName("delivery_status")
|
@SerialName("delivery_status")
|
||||||
|
|||||||
@@ -29,8 +29,14 @@ data class MessageEntity(
|
|||||||
val senderAvatarUrl: String?,
|
val senderAvatarUrl: String?,
|
||||||
@ColumnInfo(name = "reply_to_message_id")
|
@ColumnInfo(name = "reply_to_message_id")
|
||||||
val replyToMessageId: Long?,
|
val replyToMessageId: Long?,
|
||||||
|
@ColumnInfo(name = "reply_preview_text")
|
||||||
|
val replyPreviewText: String?,
|
||||||
|
@ColumnInfo(name = "reply_preview_sender_name")
|
||||||
|
val replyPreviewSenderName: String?,
|
||||||
@ColumnInfo(name = "forwarded_from_message_id")
|
@ColumnInfo(name = "forwarded_from_message_id")
|
||||||
val forwardedFromMessageId: Long?,
|
val forwardedFromMessageId: Long?,
|
||||||
|
@ColumnInfo(name = "forwarded_from_display_name")
|
||||||
|
val forwardedFromDisplayName: String?,
|
||||||
@ColumnInfo(name = "type")
|
@ColumnInfo(name = "type")
|
||||||
val type: String,
|
val type: String,
|
||||||
@ColumnInfo(name = "text")
|
@ColumnInfo(name = "text")
|
||||||
|
|||||||
@@ -13,4 +13,9 @@ data class MessageLocalModel(
|
|||||||
entityColumn = "message_id",
|
entityColumn = "message_id",
|
||||||
)
|
)
|
||||||
val attachments: List<MessageAttachmentEntity>,
|
val attachments: List<MessageAttachmentEntity>,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "reply_to_message_id",
|
||||||
|
entityColumn = "id",
|
||||||
|
)
|
||||||
|
val replyToMessage: MessageEntity?,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ fun MessageReadDto.toEntity(): MessageEntity {
|
|||||||
id = id,
|
id = id,
|
||||||
chatId = chatId,
|
chatId = chatId,
|
||||||
senderId = senderId,
|
senderId = senderId,
|
||||||
senderDisplayName = null,
|
senderDisplayName = senderDisplayName,
|
||||||
senderUsername = null,
|
senderUsername = senderUsername,
|
||||||
senderAvatarUrl = null,
|
senderAvatarUrl = senderAvatarUrl,
|
||||||
replyToMessageId = replyToMessageId,
|
replyToMessageId = replyToMessageId,
|
||||||
|
replyPreviewText = replyPreviewText,
|
||||||
|
replyPreviewSenderName = replyPreviewSenderName,
|
||||||
forwardedFromMessageId = forwardedFromMessageId,
|
forwardedFromMessageId = forwardedFromMessageId,
|
||||||
|
forwardedFromDisplayName = forwardedFromDisplayName,
|
||||||
type = type,
|
type = type,
|
||||||
text = text,
|
text = text,
|
||||||
status = deliveryStatus,
|
status = deliveryStatus,
|
||||||
@@ -33,6 +36,13 @@ fun MessageReadDto.toEntity(): MessageEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun MessageLocalModel.toDomain(currentUserId: Long?): MessageItem {
|
fun MessageLocalModel.toDomain(currentUserId: Long?): MessageItem {
|
||||||
|
val resolvedReplyPreviewText = message.replyPreviewText
|
||||||
|
?: replyToMessage?.text
|
||||||
|
?: replyToMessage?.type?.let { "[$it]" }
|
||||||
|
val resolvedReplyPreviewSenderName = message.replyPreviewSenderName
|
||||||
|
?: replyToMessage?.senderDisplayName
|
||||||
|
?: replyToMessage?.senderUsername?.takeIf { it.isNotBlank() }?.let { "@$it" }
|
||||||
|
?: replyToMessage?.senderId?.let { "User #$it" }
|
||||||
return MessageItem(
|
return MessageItem(
|
||||||
id = message.id,
|
id = message.id,
|
||||||
chatId = message.chatId,
|
chatId = message.chatId,
|
||||||
@@ -45,7 +55,10 @@ fun MessageLocalModel.toDomain(currentUserId: Long?): MessageItem {
|
|||||||
isOutgoing = currentUserId != null && currentUserId == message.senderId,
|
isOutgoing = currentUserId != null && currentUserId == message.senderId,
|
||||||
status = message.status,
|
status = message.status,
|
||||||
replyToMessageId = message.replyToMessageId,
|
replyToMessageId = message.replyToMessageId,
|
||||||
|
replyPreviewText = resolvedReplyPreviewText,
|
||||||
|
replyPreviewSenderName = resolvedReplyPreviewSenderName,
|
||||||
forwardedFromMessageId = message.forwardedFromMessageId,
|
forwardedFromMessageId = message.forwardedFromMessageId,
|
||||||
|
forwardedFromDisplayName = message.forwardedFromDisplayName,
|
||||||
attachmentWaveform = message.attachmentWaveformJson.toWaveformOrNull(),
|
attachmentWaveform = message.attachmentWaveformJson.toWaveformOrNull(),
|
||||||
attachments = attachments.map { it.toDomain() },
|
attachments = attachments.map { it.toDomain() },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -122,7 +122,10 @@ class NetworkMessageRepository @Inject constructor(
|
|||||||
senderUsername = null,
|
senderUsername = null,
|
||||||
senderAvatarUrl = null,
|
senderAvatarUrl = null,
|
||||||
replyToMessageId = replyToMessageId,
|
replyToMessageId = replyToMessageId,
|
||||||
|
replyPreviewText = null,
|
||||||
|
replyPreviewSenderName = null,
|
||||||
forwardedFromMessageId = null,
|
forwardedFromMessageId = null,
|
||||||
|
forwardedFromDisplayName = null,
|
||||||
type = "text",
|
type = "text",
|
||||||
text = text,
|
text = text,
|
||||||
status = "pending",
|
status = "pending",
|
||||||
@@ -208,7 +211,10 @@ class NetworkMessageRepository @Inject constructor(
|
|||||||
senderUsername = null,
|
senderUsername = null,
|
||||||
senderAvatarUrl = null,
|
senderAvatarUrl = null,
|
||||||
replyToMessageId = replyToMessageId,
|
replyToMessageId = replyToMessageId,
|
||||||
|
replyPreviewText = null,
|
||||||
|
replyPreviewSenderName = null,
|
||||||
forwardedFromMessageId = null,
|
forwardedFromMessageId = null,
|
||||||
|
forwardedFromDisplayName = null,
|
||||||
type = messageType,
|
type = messageType,
|
||||||
text = caption,
|
text = caption,
|
||||||
status = "pending",
|
status = "pending",
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ data class MessageItem(
|
|||||||
val isOutgoing: Boolean,
|
val isOutgoing: Boolean,
|
||||||
val status: String?,
|
val status: String?,
|
||||||
val replyToMessageId: Long?,
|
val replyToMessageId: Long?,
|
||||||
|
val replyPreviewText: String?,
|
||||||
|
val replyPreviewSenderName: String?,
|
||||||
val forwardedFromMessageId: Long?,
|
val forwardedFromMessageId: Long?,
|
||||||
|
val forwardedFromDisplayName: String?,
|
||||||
val attachmentWaveform: List<Int>?,
|
val attachmentWaveform: List<Int>?,
|
||||||
val attachments: List<MessageAttachment>,
|
val attachments: List<MessageAttachment>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ class HandleRealtimeEventsUseCase @Inject constructor(
|
|||||||
senderUsername = null,
|
senderUsername = null,
|
||||||
senderAvatarUrl = null,
|
senderAvatarUrl = null,
|
||||||
replyToMessageId = event.replyToMessageId,
|
replyToMessageId = event.replyToMessageId,
|
||||||
|
replyPreviewText = null,
|
||||||
|
replyPreviewSenderName = null,
|
||||||
forwardedFromMessageId = null,
|
forwardedFromMessageId = null,
|
||||||
|
forwardedFromDisplayName = null,
|
||||||
type = event.type ?: "text",
|
type = event.type ?: "text",
|
||||||
text = event.text,
|
text = event.text,
|
||||||
status = null,
|
status = null,
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ class MessageDaoTest {
|
|||||||
senderUsername = "user",
|
senderUsername = "user",
|
||||||
senderAvatarUrl = null,
|
senderAvatarUrl = null,
|
||||||
replyToMessageId = null,
|
replyToMessageId = null,
|
||||||
|
replyPreviewText = null,
|
||||||
|
replyPreviewSenderName = null,
|
||||||
forwardedFromMessageId = null,
|
forwardedFromMessageId = null,
|
||||||
|
forwardedFromDisplayName = null,
|
||||||
type = "text",
|
type = "text",
|
||||||
text = text,
|
text = text,
|
||||||
status = null,
|
status = null,
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ class MessageActionStateTest {
|
|||||||
isOutgoing = true,
|
isOutgoing = true,
|
||||||
status = "sent",
|
status = "sent",
|
||||||
replyToMessageId = null,
|
replyToMessageId = null,
|
||||||
|
replyPreviewText = null,
|
||||||
|
replyPreviewSenderName = null,
|
||||||
forwardedFromMessageId = null,
|
forwardedFromMessageId = null,
|
||||||
|
forwardedFromDisplayName = null,
|
||||||
attachmentWaveform = null,
|
attachmentWaveform = null,
|
||||||
attachments = emptyList(),
|
attachments = emptyList(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
- [ ] Версионирование API и feature flags
|
- [ ] Версионирование API и feature flags
|
||||||
|
|
||||||
## 3. Локальное хранение и sync
|
## 3. Локальное хранение и sync
|
||||||
- [ ] Room для чатов/сообщений/пользователей
|
- [x] Room для чатов/сообщений/пользователей
|
||||||
- [x] DataStore для настроек
|
- [x] DataStore для настроек
|
||||||
- [ ] Кэш медиа (Coil/Exo cache)
|
- [ ] Кэш медиа (Coil/Exo cache)
|
||||||
- [ ] Offline-first чтение истории
|
- [ ] Offline-first чтение истории
|
||||||
|
|||||||
Reference in New Issue
Block a user