Simple attachments in messages history (#164)

* new attachments in messages history - photo, video, audio, file, link
* improve attachments in messages history and adjusted font size for logo's text in auth screen
* audio duration, file preview and url preview are now visible in attachments in messages history screen
* make MessageBubble width adapt to attachments container width
* topbar back icon crossfade animation
* implement rich text for message input
* handle click and long click on attachments
* added click and long click handlers for attachments in message bubbles
* enabled opening photos, files, and links when clicked.
* implemented basic long-click logging for photos.
* handled back press to return to Conversations from other tabs.
* corrected the logic for filtering and selecting video images.
* updated string resources for attachments, including a new "Clip" string.
* make MessageBubble mention text underline on out messages
This commit is contained in:
2025-05-10 03:10:07 +03:00
committed by GitHub
parent f45a106ed8
commit 43539139e8
46 changed files with 2296 additions and 369 deletions
@@ -0,0 +1,8 @@
package dev.meloda.fast.model
data class PhotoSize(
val height: Int,
val width: Int,
val type: String,
val url: String
)
@@ -6,8 +6,8 @@ enum class AttachmentType(var value: String) {
UNKNOWN("unknown"),
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
FILE("doc"),
AUDIO("audio"),
LINK("link"),
AUDIO_MESSAGE("audio_message"),
MINI_APP("mini_app"),
@@ -27,7 +27,9 @@ data class VkFileData(
) {
@JsonClass(generateAdapter = true)
data class Photo(val sizes: List<Size>) {
data class Photo(
val sizes: List<Size>
) {
@JsonClass(generateAdapter = true)
data class Size(
@@ -2,6 +2,7 @@ package dev.meloda.fast.model.api.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import dev.meloda.fast.model.PhotoSize
import dev.meloda.fast.model.api.domain.VkPhotoDomain
@JsonClass(generateAdapter = true)
@@ -35,7 +36,14 @@ data class VkPhotoData(
ownerId = ownerId,
hasTags = hasTags == true,
accessKey = accessKey,
sizes = sizes,
sizes = sizes.map { size ->
PhotoSize(
height = size.height,
width = size.width,
type = size.type,
url = size.url
)
},
text = text,
userId = userId
)
@@ -23,7 +23,7 @@ data class VkVideoData(
@Json(name = "is_favorite") val isFavorite: Boolean?,
@Json(name = "image") val image: List<Image>?,
@Json(name = "first_frame") val firstFrame: List<FirstFrame>?,
@Json(name = "files") val files: File?
@Json(name = "files") val files: File?,
) : VkAttachmentData {
@JsonClass(generateAdapter = true)
@@ -73,6 +73,7 @@ data class VkVideoData(
accessKey = accessKey,
title = title,
views = views,
duration = duration
duration = duration,
isShortVideo = type == "short_video"
)
}
@@ -9,10 +9,10 @@ data class VkWallReplyData(
val from_id: Long,
val date: Int,
val text: String,
val post_id: Long,
val owner_id: Long,
val parents_stack: List<Int>,
val likes: Likes,
val post_id: Long?,
val owner_id: Long?,
val parents_stack: List<Int>?,
val likes: Likes?,
val reply_to_user: Int?,
val reply_to_comment: Int?
) {
@@ -3,6 +3,10 @@ package dev.meloda.fast.model.api.domain
enum class FormatDataType {
BOLD, ITALIC, UNDERLINE, URL;
override fun toString(): String {
return super.toString().lowercase()
}
companion object {
fun parse(value: String): FormatDataType? =
entries.firstOrNull { it.name.lowercase() == value }
@@ -1,7 +1,7 @@
package dev.meloda.fast.model.api.domain
import dev.meloda.fast.model.PhotoSize
import dev.meloda.fast.model.api.data.AttachmentType
import dev.meloda.fast.model.api.data.VkPhotoData
import java.util.Stack
@@ -13,7 +13,7 @@ data class VkPhotoDomain(
val ownerId: Long,
val hasTags: Boolean,
val accessKey: String?,
val sizes: List<VkPhotoData.Size>,
val sizes: List<PhotoSize>,
val text: String?,
val userId: Long?
) : VkAttachment {
@@ -35,11 +35,15 @@ data class VkPhotoDomain(
sizesChars.push(SIZE_TYPE_2560_2048)
}
fun getMaxSize(): VkPhotoData.Size? {
fun getMaxSize(): PhotoSize? {
return getSizeOrSmaller(sizesChars.peek())
}
fun getSizeOrNull(type: Char): VkPhotoData.Size? {
fun getDefault(): PhotoSize? {
return getSizeOrSmaller(SIZE_TYPE_1080_1024)
}
fun getSizeOrNull(type: Char): PhotoSize? {
for (size in sizes) {
if (size.type == type.toString()) return size
}
@@ -47,7 +51,7 @@ data class VkPhotoDomain(
return null
}
fun getSizeOrSmaller(type: Char): VkPhotoData.Size? {
fun getSizeOrSmaller(type: Char): PhotoSize? {
val photoStack = sizesChars.clone() as Stack<*>
val sizeIndex = photoStack.search(type)
@@ -13,7 +13,8 @@ data class VkVideoDomain(
val accessKey: String?,
val title: String,
val views: Int,
val duration: Int
val duration: Int,
val isShortVideo: Boolean
) : VkAttachment {
override val type: AttachmentType = AttachmentType.VIDEO
@@ -22,6 +23,10 @@ data class VkVideoDomain(
return images.find { it.width == width }
}
fun getDefault(): VideoImage? {
return imageForWidthAtLeast(720)
}
fun imageForWidthAtLeast(width: Int): VideoImage? {
var certainImages = images.sortedByDescending { it.width }
var containsVertical = false
@@ -36,9 +41,11 @@ data class VkVideoDomain(
certainImages = certainImages.filter { it.shapeKind == ShapeKind.Vertical }
}
certainImages = certainImages.filter { it.width >= width }
val filteredCertainImages = certainImages.filter { it.width >= width }
return certainImages.firstOrNull()
return filteredCertainImages
.ifEmpty { certainImages }
.firstOrNull()
}
@JsonClass(generateAdapter = true)
@@ -2,6 +2,7 @@ package dev.meloda.fast.model.api.requests
import dev.meloda.fast.model.api.asInt
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.model.api.domain.VkMessage
data class MessagesGetHistoryRequest(
val count: Int? = null,
@@ -38,7 +39,8 @@ data class MessagesSendRequest(
val disableMentions: Boolean? = null,
val doNotParseLinks: Boolean? = null,
val silent: Boolean? = null,
val attachments: List<VkAttachment>? = null
val attachments: List<VkAttachment>? = null,
val formatData: VkMessage.FormatData? = null
) {
val map: Map<String, String>
@@ -54,6 +56,13 @@ data class MessagesSendRequest(
disableMentions?.let { this["disable_mentions"] = it.asInt().toString() }
doNotParseLinks?.let { this["dont_parse_links"] = it.asInt().toString() }
silent?.let { this["silent"] = it.toString() }
formatData?.let {
this["format_data"] = "{\"version\":\"${formatData.version}\",\"items\":[" +
formatData.items.joinToString(separator = ", ") { item ->
"{\"type\":\"${item.type}\",\"offset\":${item.offset},\"length\":${item.length}}"
} +
"]}"
}
// TODO: 05/05/2024, Danil Nikolaev: add attachments
// attachments?.let {