From e127501889ef2e17790eefb2266861b149fafb3f Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Fri, 24 Sep 2021 10:55:07 +0300 Subject: [PATCH] simplifying base models new attachments --- app/build.gradle.kts | 8 +- app/src/main/AndroidManifest.xml | 9 - .../com/meloda/fast/activity/MainActivity.kt | 5 + .../kotlin/com/meloda/fast/api/VkUtils.kt | 383 +++++++---------- .../fast/api/model/attachments/VkAudio.kt | 6 +- .../fast/api/model/attachments/VkCurator.kt | 8 + .../fast/api/model/attachments/VkEvent.kt | 8 + .../fast/api/model/attachments/VkFile.kt | 6 +- .../fast/api/model/attachments/VkLink.kt | 7 +- .../fast/api/model/attachments/VkPhoto.kt | 6 +- .../fast/api/model/attachments/VkSticker.kt | 3 +- .../fast/api/model/attachments/VkStory.kt | 11 + .../fast/api/model/attachments/VkVideo.kt | 2 +- .../fast/api/model/attachments/VkWall.kt | 14 +- .../fast/api/model/base/BaseVkConversation.kt | 167 +++----- .../meloda/fast/api/model/base/BaseVkGroup.kt | 34 +- .../fast/api/model/base/BaseVkMessage.kt | 37 +- .../meloda/fast/api/model/base/BaseVkUser.kt | 53 +-- .../base/attachments/BaseVkAttachmentItem.kt | 24 +- .../api/model/base/attachments/BaseVkAudio.kt | 69 ++- .../api/model/base/attachments/BaseVkCall.kt | 7 +- .../model/base/attachments/BaseVkCurator.kt | 27 ++ .../api/model/base/attachments/BaseVkEvent.kt | 22 + .../api/model/base/attachments/BaseVkFile.kt | 37 +- .../api/model/base/attachments/BaseVkGift.kt | 10 +- .../model/base/attachments/BaseVkGraffiti.kt | 7 +- .../model/base/attachments/BaseVkGroupCall.kt | 7 +- .../api/model/base/attachments/BaseVkLink.kt | 24 +- .../model/base/attachments/BaseVkMiniApp.kt | 3 +- .../api/model/base/attachments/BaseVkPhoto.kt | 46 +- .../api/model/base/attachments/BaseVkPoll.kt | 31 +- .../model/base/attachments/BaseVkSticker.kt | 23 +- .../api/model/base/attachments/BaseVkStory.kt | 52 +++ .../api/model/base/attachments/BaseVkVideo.kt | 126 +++--- .../base/attachments/BaseVkVoiceMessage.kt | 16 +- .../api/model/base/attachments/BaseVkWall.kt | 56 +-- .../model/base/attachments/BaseVkWallReply.kt | 28 +- .../fast/api/network/ResultCallFactory.kt | 9 +- .../com/meloda/fast/common/AppGlobal.kt | 23 +- .../com/meloda/fast/common/AppSettings.kt | 23 + .../conversations/ConversationsAdapter.kt | 15 +- .../conversations/ConversationsFragment.kt | 33 +- .../screens/messages/AttachmentInflater.kt | 309 ++++++++++++++ .../messages/MessagesHistoryAdapter.kt | 403 ++---------------- .../messages/MessagesHistoryFragment.kt | 4 + .../messages/MessagesHistoryViewModel.kt | 2 +- .../fast/screens/messages/MessagesManager.kt | 179 -------- .../screens/messages/MessagesPreparator.kt | 206 +++++++++ .../com/meloda/fast/util/AndroidUtils.kt | 7 + .../fast/widget/RoundedCornerLayout.java | 79 ++++ .../meloda/fast/widget/RoundedFrameLayout.kt | 95 +++++ .../main/res/drawable/ic_attachment_story.xml | 9 + .../res/layout/fragment_messages_history.xml | 3 +- .../layout/item_message_attachment_audio.xml | 59 +++ .../layout/item_message_attachment_file.xml | 59 +++ .../layout/item_message_attachment_link.xml | 51 +++ .../item_message_attachment_photos_in.xml | 99 ----- .../item_message_attachment_photos_out.xml | 18 - .../item_message_attachment_sticker.xml | 16 + .../item_message_attachment_sticker_in.xml | 41 -- .../item_message_attachment_sticker_out.xml | 34 -- .../item_message_attachment_videos_in.xml | 99 ----- .../item_message_attachment_wall_post.xml | 69 +++ app/src/main/res/layout/item_message_in.xml | 37 +- app/src/main/res/layout/item_message_out.xml | 30 +- app/src/main/res/values/attrs.xml | 7 + app/src/main/res/values/plurals.xml | 32 ++ app/src/main/res/values/strings.xml | 60 ++- 68 files changed, 1933 insertions(+), 1559 deletions(-) create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCurator.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkEvent.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkStory.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCurator.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkEvent.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkStory.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/common/AppSettings.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/screens/messages/AttachmentInflater.kt delete mode 100644 app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesPreparator.kt create mode 100644 app/src/main/kotlin/com/meloda/fast/widget/RoundedCornerLayout.java create mode 100644 app/src/main/kotlin/com/meloda/fast/widget/RoundedFrameLayout.kt create mode 100644 app/src/main/res/drawable/ic_attachment_story.xml create mode 100644 app/src/main/res/layout/item_message_attachment_audio.xml create mode 100644 app/src/main/res/layout/item_message_attachment_file.xml create mode 100644 app/src/main/res/layout/item_message_attachment_link.xml delete mode 100644 app/src/main/res/layout/item_message_attachment_photos_in.xml delete mode 100644 app/src/main/res/layout/item_message_attachment_photos_out.xml create mode 100644 app/src/main/res/layout/item_message_attachment_sticker.xml delete mode 100644 app/src/main/res/layout/item_message_attachment_sticker_in.xml delete mode 100644 app/src/main/res/layout/item_message_attachment_sticker_out.xml delete mode 100644 app/src/main/res/layout/item_message_attachment_videos_in.xml create mode 100644 app/src/main/res/layout/item_message_attachment_wall_post.xml create mode 100644 app/src/main/res/values/plurals.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a22982c8..f29f3a69 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,7 +19,7 @@ android { defaultConfig { applicationId = "com.meloda.fast" minSdk = 23 - targetSdk = 31 + targetSdk = 30 versionCode = 1 versionName = "1.0" @@ -75,15 +75,17 @@ kapt { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") implementation("androidx.work:work-runtime-ktx:2.6.0") + implementation("androidx.datastore:datastore-preferences:1.0.0") + implementation("androidx.appcompat:appcompat:1.4.0-alpha03") implementation("com.google.android.material:material:1.5.0-alpha03") - implementation("androidx.core:core-ktx:1.7.0-alpha02") + implementation("androidx.core:core-ktx:1.7.0-beta01") implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01") implementation("androidx.recyclerview:recyclerview:1.2.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fc9dd56..1486ffe8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,15 +28,6 @@ - - - - - - = 300) - fun prepareMessageText(text: String?): String? { - if (text == null) return null - - return text - .replace("\n", " ") - .replace("&", "&") - } + fun isPreviousMessageFromDifferentSender(prevMessage: VkMessage?, message: VkMessage?) = + prevMessage != null && message != null && prevMessage.fromId != message.fromId fun parseForwards(baseForwards: List?): List? { if (baseForwards.isNullOrEmpty()) return null @@ -64,21 +59,15 @@ object VkUtils { } BaseVkAttachmentItem.AttachmentType.AUDIO -> { val audio = baseAttachment.audio ?: continue - attachments += VkAudio( - link = audio.url - ) + attachments += audio.asVkAudio() } BaseVkAttachmentItem.AttachmentType.FILE -> { val file = baseAttachment.file ?: continue - attachments += VkFile( - link = file.url - ) + attachments += file.asVkFile() } BaseVkAttachmentItem.AttachmentType.LINK -> { val link = baseAttachment.link ?: continue - attachments += VkLink( - link = link.url - ) + attachments += link.asVkLink() } BaseVkAttachmentItem.AttachmentType.MINI_APP -> { val miniApp = baseAttachment.miniApp ?: continue @@ -89,7 +78,7 @@ object VkUtils { BaseVkAttachmentItem.AttachmentType.VOICE -> { val voiceMessage = baseAttachment.voiceMessage ?: continue attachments += VkVoiceMessage( - link = voiceMessage.linkMp3 + link = voiceMessage.link_mp3 ) } BaseVkAttachmentItem.AttachmentType.STICKER -> { @@ -99,14 +88,12 @@ object VkUtils { BaseVkAttachmentItem.AttachmentType.GIFT -> { val gift = baseAttachment.gift ?: continue attachments += VkGift( - link = gift.thumb48 + link = gift.thumb_48 ) } BaseVkAttachmentItem.AttachmentType.WALL -> { val wall = baseAttachment.wall ?: continue - attachments += VkWall( - id = wall.id - ) + attachments += wall.asVkWall() } BaseVkAttachmentItem.AttachmentType.GRAFFITI -> { val graffiti = baseAttachment.graffiti ?: continue @@ -129,15 +116,27 @@ object VkUtils { BaseVkAttachmentItem.AttachmentType.CALL -> { val call = baseAttachment.call ?: continue attachments += VkCall( - initiatorId = call.initiatorId + initiatorId = call.initiator_id ) } BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> { val groupCall = baseAttachment.groupCall ?: continue attachments += VkGroupCall( - initiatorId = groupCall.initiatorId + initiatorId = groupCall.initiator_id ) } + BaseVkAttachmentItem.AttachmentType.CURATOR -> { + val curator = baseAttachment.curator ?: continue + attachments += curator.asVkCurator() + } + BaseVkAttachmentItem.AttachmentType.EVENT -> { + val event = baseAttachment.event ?: continue + attachments += event.asVkEvent() + } + BaseVkAttachmentItem.AttachmentType.STORY -> { + val story = baseAttachment.story ?: continue + attachments += story.asVkStory() + } else -> continue } } @@ -145,177 +144,12 @@ object VkUtils { return attachments } - fun getActionConversationText( - message: VkMessage, - youPrefix: String, - profiles: HashMap? = null, - groups: HashMap? = null, - messageUser: VkUser? = null, - messageGroup: VkGroup? = null - ): String? { - return when (message.getPreparedAction()) { - VkMessage.Action.CHAT_CREATE -> { - val text = message.actionText ?: return null - - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix created «$text»" - } - VkMessage.Action.CHAT_TITLE_UPDATE -> { - val text = message.actionText ?: return null - - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix renamed chat to «$text»" - } - VkMessage.Action.CHAT_PHOTO_UPDATE -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix updated the chat photo" - } - VkMessage.Action.CHAT_PHOTO_REMOVE -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix deleted the chat photo" - } - VkMessage.Action.CHAT_KICK_USER -> { - val memberId = message.actionMemberId ?: return null - val isUser = memberId > 0 - val isGroup = memberId < 0 - - val actionUser = profiles?.get(memberId) - val actionGroup = groups?.get(memberId) - - if (isUser && actionUser == null) return null - if (isGroup && actionGroup == null) return null - - if (memberId == message.fromId) { - val prefix = if (memberId == UserConfig.userId) youPrefix - else actionUser.toString() - "$prefix left the chat" - } else { - val prefix = - if (message.fromId == UserConfig.userId) youPrefix - else messageUser?.toString() ?: messageGroup?.toString() ?: "..." - val postfix = - if (memberId == UserConfig.userId) youPrefix.lowercase() - else actionUser.toString() - "$prefix kicked $postfix" - } - } - VkMessage.Action.CHAT_INVITE_USER -> { - val memberId = message.actionMemberId ?: 0 - val isUser = memberId > 0 - val isGroup = memberId < 0 - - val actionUser = profiles?.get(memberId) - val actionGroup = groups?.get(memberId) - - if (isUser && actionUser == null) return null - if (isGroup && actionGroup == null) return null - - if (memberId == message.fromId) { - val prefix = if (memberId == UserConfig.userId) youPrefix - else actionUser.toString() - "$prefix returned the chat" - } else { - val prefix = if (message.fromId == UserConfig.userId) youPrefix - else messageUser?.toString() ?: messageGroup?.toString() ?: "..." - val postfix = - if (memberId == UserConfig.userId) youPrefix.lowercase() - else actionUser.toString() - "$prefix invited $postfix" - } - } - VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix joined the chat via link" - } - VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix joined the call via link" - } - VkMessage.Action.CHAT_PIN_MESSAGE -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - val actionMessage = message.actionMessage - - "$prefix pinned message ${if (actionMessage == null) "" else "«$actionMessage»"}".trim() - } - VkMessage.Action.CHAT_UNPIN_MESSAGE -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix unpinned message" - } - VkMessage.Action.CHAT_SCREENSHOT -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isGroup() -> messageGroup?.name - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix took a screenshot" - } - VkMessage.Action.CHAT_STYLE_UPDATE -> { - val prefix = when { - message.fromId == UserConfig.userId -> youPrefix - message.isUser() -> messageUser?.toString() - else -> return null - } ?: return null - - "$prefix changed chat theme" - } - null -> null - else -> "[${message.action}]" - } - } - fun getActionMessageText( + context: Context, message: VkMessage, youPrefix: String, - profiles: HashMap? = null, - groups: HashMap? = null, + profiles: Map? = null, + groups: Map? = null, messageUser: VkUser? = null, messageGroup: VkGroup? = null ): SpannableString? { @@ -330,7 +164,8 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix created «$text»" + val spanText = + context.getString(R.string.message_action_chat_created, prefix, text) SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) @@ -351,8 +186,8 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix renamed chat to «$text»" - + val spanText = + context.getString(R.string.message_action_chat_renamed, prefix, text) val startIndex = spanText.indexOf(text) SpannableString(spanText).also { @@ -370,7 +205,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix updated the chat photo" + val spanText = + context.getString(R.string.message_action_chat_photo_update, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -383,7 +220,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix deleted the chat photo" + val spanText = + context.getString(R.string.message_action_chat_photo_remove, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -402,7 +241,10 @@ object VkUtils { if (memberId == message.fromId) { val prefix = if (memberId == UserConfig.userId) youPrefix else actionUser.toString() - val spanText = "$prefix left the chat" + + val spanText = + context.getString(R.string.message_action_chat_user_left, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -410,11 +252,17 @@ object VkUtils { val prefix = if (message.fromId == UserConfig.userId) youPrefix else messageUser?.toString() ?: messageGroup?.toString() ?: "..." + val postfix = if (memberId == UserConfig.userId) youPrefix.lowercase() else actionUser.toString() - val spanText = "$prefix kicked $postfix" + val spanText = + context.getString( + R.string.message_action_chat_user_kicked, + prefix, + postfix + ) val startIndex = spanText.indexOf(postfix) SpannableString(spanText).also { @@ -439,18 +287,27 @@ object VkUtils { if (memberId == message.fromId) { val prefix = if (memberId == UserConfig.userId) youPrefix else actionUser.toString() - val spanText = "$prefix returned the chat" + + val spanText = + context.getString(R.string.message_action_chat_user_returned, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } } else { val prefix = if (message.fromId == UserConfig.userId) youPrefix else messageUser?.toString() ?: messageGroup?.toString() ?: "..." + val postfix = if (memberId == UserConfig.userId) youPrefix.lowercase() else actionUser.toString() - val spanText = "$prefix invited $postfix" + val spanText = + context.getString( + R.string.message_action_chat_user_invited, + prefix, + postfix + ) val startIndex = spanText.indexOf(postfix) SpannableString(spanText).also { @@ -468,7 +325,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix joined the chat via link" + val spanText = + context.getString(R.string.message_action_chat_user_joined_by_link, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -480,7 +339,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix joined the call via link" + val spanText = + context.getString(R.string.message_action_chat_user_joined_by_call_link, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -493,16 +354,11 @@ object VkUtils { else -> return null } ?: return null - val actionMessage = message.actionMessage ?: return null - - val spanText = "$prefix pinned message «$actionMessage»" - val startIndex = spanText.indexOf(actionMessage) + val spanText = + context.getString(R.string.message_action_chat_pin_message, prefix).trim() SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) - it.setSpan( - StyleSpan(Typeface.BOLD), startIndex, startIndex + actionMessage.length, 0 - ) } } VkMessage.Action.CHAT_UNPIN_MESSAGE -> { @@ -513,7 +369,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix unpinned message" + val spanText = + context.getString(R.string.message_action_chat_unpin_message, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -526,7 +384,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix took a screenshot" + val spanText = + context.getString(R.string.message_action_chat_screenshot, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -538,7 +398,9 @@ object VkUtils { else -> return null } ?: return null - val spanText = "$prefix changed chat theme" + val spanText = + context.getString(R.string.message_action_chat_style_update, prefix) + SpannableString(spanText).also { it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0) } @@ -548,6 +410,26 @@ object VkUtils { } } + fun getActionConversationText( + context: Context, + message: VkMessage, + youPrefix: String, + profiles: HashMap? = null, + groups: HashMap? = null, + messageUser: VkUser? = null, + messageGroup: VkGroup? = null + ): String? { + return getActionMessageText( + context = context, + message = message, + youPrefix = youPrefix, + profiles = profiles, + groups = groups, + messageUser = messageUser, + messageGroup = messageGroup + )?.toString() + } + fun getForwardsConversationText(context: Context, message: VkMessage): String? { if (message.forwards.isNullOrEmpty()) return null @@ -570,10 +452,19 @@ object VkUtils { return message.attachments?.let { attachments -> if (attachments.size == 1) { - getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) } + getAttachmentTypeByClass(attachments[0])?.let { + getAttachmentTextByType( + context, + it + ) + } } else { if (isAttachmentsHaveOneType(attachments)) { - getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) } + getAttachmentTypeByClass(attachments[0])?.let { + getAttachmentTextByType( + context, it, attachments.size + ) + } } else { context.getString(R.string.message_attachments_many) } @@ -622,6 +513,8 @@ object VkUtils { BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> R.drawable.ic_attachment_wall_reply BaseVkAttachmentItem.AttachmentType.CALL -> R.drawable.ic_attachment_call BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_attachment_group_call + BaseVkAttachmentItem.AttachmentType.STORY -> R.drawable.ic_attachment_story + else -> return null } return ContextCompat.getDrawable(context, resId) @@ -657,14 +550,56 @@ object VkUtils { is VkWallReply -> BaseVkAttachmentItem.AttachmentType.WALL_REPLY is VkCall -> BaseVkAttachmentItem.AttachmentType.CALL is VkGroupCall -> BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS + is VkEvent -> BaseVkAttachmentItem.AttachmentType.EVENT + is VkCurator -> BaseVkAttachmentItem.AttachmentType.CURATOR + is VkStory -> BaseVkAttachmentItem.AttachmentType.STORY else -> null } } - fun getAttachmentTextByType(attachmentType: BaseVkAttachmentItem.AttachmentType): String? { + fun getAttachmentTextByType( + context: Context, + attachmentType: BaseVkAttachmentItem.AttachmentType, + size: Int = 1 + ): String { return when (attachmentType) { + BaseVkAttachmentItem.AttachmentType.PHOTO -> + context.resources.getQuantityString(R.plurals.attachment_photos, size, size) + BaseVkAttachmentItem.AttachmentType.VIDEO -> + context.resources.getQuantityString(R.plurals.attachment_videos, size, size) + BaseVkAttachmentItem.AttachmentType.AUDIO -> + context.resources.getQuantityString(R.plurals.attachment_audios, size, size) + BaseVkAttachmentItem.AttachmentType.FILE -> + context.resources.getQuantityString(R.plurals.attachment_files, size, size) + BaseVkAttachmentItem.AttachmentType.LINK -> + context.resources.getString(R.string.message_attachments_link) + BaseVkAttachmentItem.AttachmentType.VOICE -> + context.resources.getString(R.string.message_attachments_voice) + BaseVkAttachmentItem.AttachmentType.MINI_APP -> + context.resources.getString(R.string.message_attachments_mini_app) + BaseVkAttachmentItem.AttachmentType.STICKER -> + context.resources.getString(R.string.message_attachments_sticker) + BaseVkAttachmentItem.AttachmentType.GIFT -> + context.resources.getString(R.string.message_attachments_gift) + BaseVkAttachmentItem.AttachmentType.WALL -> + context.resources.getString(R.string.message_attachments_wall) + BaseVkAttachmentItem.AttachmentType.GRAFFITI -> + context.resources.getString(R.string.message_attachments_graffiti) + BaseVkAttachmentItem.AttachmentType.POLL -> + context.resources.getString(R.string.message_attachments_poll) + BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> + context.resources.getString(R.string.message_attachments_wall_reply) + BaseVkAttachmentItem.AttachmentType.CALL -> + context.resources.getString(R.string.message_attachments_call) + BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> + context.resources.getString(R.string.message_attachments_call_in_progress) + BaseVkAttachmentItem.AttachmentType.EVENT -> + context.resources.getString(R.string.message_attachments_event) + BaseVkAttachmentItem.AttachmentType.CURATOR -> + context.resources.getString(R.string.message_attachments_curator) + BaseVkAttachmentItem.AttachmentType.STORY -> + context.resources.getString(R.string.message_attachments_story) else -> attachmentType.value } } - } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAudio.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAudio.kt index 3a23f37a..cdf4e450 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAudio.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAudio.kt @@ -5,7 +5,11 @@ import kotlinx.parcelize.Parcelize @Parcelize data class VkAudio( - val link: String + val id: Int, + val title: String, + val artist: String, + val url: String, + val duration: Int ) : VkAttachment() { @IgnoredOnParcel diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCurator.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCurator.kt new file mode 100644 index 00000000..b0a2089e --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCurator.kt @@ -0,0 +1,8 @@ +package com.meloda.fast.api.model.attachments + +import kotlinx.parcelize.Parcelize + +@Parcelize +data class VkCurator( + val id: Int +) : VkAttachment() \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkEvent.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkEvent.kt new file mode 100644 index 00000000..8a1f9801 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkEvent.kt @@ -0,0 +1,8 @@ +package com.meloda.fast.api.model.attachments + +import kotlinx.parcelize.Parcelize + +@Parcelize +data class VkEvent( + val id: Int +) : VkAttachment() \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkFile.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkFile.kt index a0fc7ad9..e1e91542 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkFile.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkFile.kt @@ -5,7 +5,11 @@ import kotlinx.parcelize.Parcelize @Parcelize data class VkFile( - val link: String + val id: Int, + val title: String, + val ext: String, + val size: Int, + val url: String ) : VkAttachment() { @IgnoredOnParcel diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkLink.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkLink.kt index 55729b31..2a4a9cd0 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkLink.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkLink.kt @@ -5,7 +5,12 @@ import kotlinx.parcelize.Parcelize @Parcelize data class VkLink( - val link: String + val url: String, + val title: String?, + val caption: String?, + val photo: VkPhoto?, + val target: String, + val isFavorite: Boolean ) : VkAttachment() { @IgnoredOnParcel diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPhoto.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPhoto.kt index eeab6ea5..2531d5a8 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPhoto.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPhoto.kt @@ -1,6 +1,6 @@ package com.meloda.fast.api.model.attachments -import com.meloda.fast.api.model.base.attachments.Size +import com.meloda.fast.api.model.base.attachments.BaseVkPhoto import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -12,7 +12,7 @@ data class VkPhoto( val ownerId: Int, val hasTags: Boolean, val accessKey: String?, - val sizes: List, + val sizes: List, val text: String, val userId: Int? ) : VkAttachment() { @@ -20,7 +20,7 @@ data class VkPhoto( @IgnoredOnParcel val className: String = this::class.java.name - fun sizeOfType(type: Char): Size? { + fun sizeOfType(type: Char): BaseVkPhoto.Size? { for (size in sizes) { if (size.type == type.toString()) return size diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkSticker.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkSticker.kt index d906b657..da6c4c82 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkSticker.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkSticker.kt @@ -1,7 +1,6 @@ package com.meloda.fast.api.model.attachments import com.meloda.fast.api.model.base.attachments.BaseVkSticker -import com.meloda.fast.api.model.base.attachments.StickerSize import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -16,7 +15,7 @@ data class VkSticker( @IgnoredOnParcel val className: String = this::class.java.name - fun urlForSize(@StickerSize size: Int): String? { + fun urlForSize(size: Int): String? { for (image in images) { if (image.width == size) return image.url } diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkStory.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkStory.kt new file mode 100644 index 00000000..48a19402 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkStory.kt @@ -0,0 +1,11 @@ +package com.meloda.fast.api.model.attachments + +import kotlinx.parcelize.Parcelize + +@Parcelize +data class VkStory( + val id: Int, + val ownerId: Int, + val date: Int, + val photo: VkPhoto +) : VkAttachment() \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVideo.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVideo.kt index c3b1e40f..33424408 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVideo.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVideo.kt @@ -8,7 +8,7 @@ import kotlinx.parcelize.Parcelize data class VkVideo( val id: Int, val images: List, - val firstFrames: List + val firstFrames: List? ) : VkAttachment() { @IgnoredOnParcel diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWall.kt index d96db976..c660472e 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWall.kt @@ -1,11 +1,23 @@ package com.meloda.fast.api.model.attachments +import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkWall( - val id: Int + val id: Int, + val fromId: Int, + val toId: Int, + val date: Int, + val text: String, + val attachments: List?, + val comments: Int, + val likes: Int, + val reposts: Int, + val views: Int, + val isFavorite: Boolean, + val accessKey: String ) : VkAttachment() { @IgnoredOnParcel diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkConversation.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkConversation.kt index 91e54dd9..25490fa1 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkConversation.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkConversation.kt @@ -1,90 +1,70 @@ package com.meloda.fast.api.model.base import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.VkMessage +import com.meloda.fast.api.model.base.attachments.BaseVkGroupCall import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkConversation( val peer: Peer, - @SerializedName("last_message_id") - val lastMessageId: Int, - @SerializedName("in_read") - val inRead: Int, - @SerializedName("out_read") - val outRead: Int, - @SerializedName("sort_id") - val sortId: SortId, - @SerializedName("last_conversation_message_id") - val lastConversationMessageId: Int, - @SerializedName("is_marked_unread") - val isMarkedUnread: Boolean, + val last_message_id: Int, + val in_read: Int, + val out_read: Int, + val sort_id: SortId, + val last_conversation_message_id: Int, + val is_marked_unread: Boolean, val important: Boolean, - @SerializedName("push_settings") - val pushSettings: PushSettings, - @SerializedName("can_write") - val canWrite: CanWrite, - @SerializedName("can_send_money") - val canSendMoney: Boolean, - @SerializedName("can_receive_money") - val canReceiveMoney: Boolean, - @SerializedName("chat_settings") - val chatSettings: ChatSettings?, - @SerializedName("call_in_progress") - val callInProgress: CallInProgress?, - @SerializedName("unread_count") - val unreadCount: Int? + val push_settings: PushSettings, + val can_write: CanWrite, + val can_send_money: Boolean, + val can_receive_money: Boolean, + val chat_settings: ChatSettings?, + val call_in_progress: CallInProgress?, + val unread_count: Int? ) : Parcelable { fun asVkConversation(lastMessage: VkMessage? = null) = VkConversation( id = peer.id, - title = chatSettings?.title, - photo200 = chatSettings?.photo?.photo200, + title = chat_settings?.title, + photo200 = chat_settings?.photo?.photo_200, type = peer.type, - callInProgress = callInProgress != null, - isPhantom = chatSettings?.isDisappearing == true, - lastConversationMessageId = lastConversationMessageId, - inRead = inRead, - outRead = outRead, - isMarkedUnread = isMarkedUnread, - lastMessageId = lastMessageId, - unreadCount = unreadCount, - membersCount = chatSettings?.membersCount, - ownerId = chatSettings?.ownerId, - isPinned = sortId.majorId > 0 + callInProgress = call_in_progress != null, + isPhantom = chat_settings?.is_disappearing == true, + lastConversationMessageId = last_conversation_message_id, + inRead = in_read, + outRead = out_read, + isMarkedUnread = is_marked_unread, + lastMessageId = last_message_id, + unreadCount = unread_count, + membersCount = chat_settings?.members_count, + ownerId = chat_settings?.owner_id, + isPinned = sort_id.major_id > 0 ).apply { this.lastMessage = lastMessage - this.pinnedMessage = chatSettings?.pinnedMessage?.asVkMessage() + this.pinnedMessage = chat_settings?.pinned_message?.asVkMessage() } @Parcelize data class Peer( val id: Int, val type: String, - @SerializedName("local_id") - val localId: Int + val local_id: Int ) : Parcelable @Parcelize data class SortId( - @SerializedName("major_id") - val majorId: Int, - @SerializedName("minor_id") - val minorId: Int + val major_id: Int, + val minor_id: Int ) : Parcelable @Parcelize data class PushSettings( - @SerializedName("disabled_forever") - val disabledForever: Boolean, - @SerializedName("no_sound") - val noSound: Boolean, - @SerializedName("disabled_mentions") - val disabledMentions: Boolean, - @SerializedName("disabled_mass_mentions") - val disabledMassMentions: Boolean + val disabled_forever: Boolean, + val no_sound: Boolean, + val disabled_mentions: Boolean, + val disabled_mass_mentions: Boolean ) : Parcelable @Parcelize @@ -94,75 +74,50 @@ data class BaseVkConversation( @Parcelize data class ChatSettings( - @SerializedName("owner_id") - val ownerId: Int, + val owner_id: Int, val title: String, val state: String, val acl: Acl, - @SerializedName("members_count") - val membersCount: Int, - @SerializedName("friends_count") - val friendsCount: Int, + val members_count: Int, + val friends_count: Int, val photo: Photo?, - @SerializedName("admin_ids") - val adminsIds: List, - @SerializedName("active_ids") - val activeIds: List, - @SerializedName("is_group_channel") - val isGroupChannel: Boolean, - @SerializedName("is_disappearing") - val isDisappearing: Boolean, - @SerializedName("is_service") - val isService: Boolean, + val admin_ids: List, + val active_ids: List, + val is_group_channel: Boolean, + val is_disappearing: Boolean, + val is_service: Boolean, val theme: String?, - @SerializedName("pinned_message") - val pinnedMessage: BaseVkMessage? + val pinned_message: BaseVkMessage? ) : Parcelable { @Parcelize data class Acl( - @SerializedName("can_change_info") - val canChangeInfo: Boolean, - @SerializedName("can_change_invite_link") - val canChangeInviteLink: Boolean, - @SerializedName("can_change_pin") - val canChangePin: Boolean, - @SerializedName("can_invite") - val canInvite: Boolean, - @SerializedName("can_promote_users") - val canPromoteUsers: Boolean, - @SerializedName("can_see_invite_link") - val canSeeInviteLink: Boolean, - @SerializedName("can_moderate") - val canModerate: Boolean, - @SerializedName("can_copy_chat") - val canCopyChat: Boolean, - @SerializedName("can_call") - val canCall: Boolean, - @SerializedName("can_use_mass_mentions") - val canUseMassMentions: Boolean, - @SerializedName("can_change_style") - val canChangeStyle: Boolean + val can_change_info: Boolean, + val can_change_invite_link: Boolean, + val can_change_pin: Boolean, + val can_invite: Boolean, + val can_promote_users: Boolean, + val can_see_invite_link: Boolean, + val can_moderate: Boolean, + val can_copy_chat: Boolean, + val can_call: Boolean, + val can_use_mass_mentions: Boolean, + val can_change_style: Boolean ) : Parcelable @Parcelize data class Photo( - @SerializedName("photo_50") - val photo50: String?, - @SerializedName("photo_100") - val photo100: String?, - @SerializedName("photo_200") - val photo200: String?, - @SerializedName("is_default_photo") - val isDefaultPhoto: Boolean + val photo_50: String?, + val photo_100: String?, + val photo_200: String?, + val is_default_photo: Boolean ) : Parcelable } @Parcelize data class CallInProgress( - val participants: Participants, - @SerializedName("join_link") - val joinLink: String + val participants: BaseVkGroupCall.Participants, + val join_link: String ) : Parcelable { @Parcelize diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkGroup.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkGroup.kt index fbbcc684..eb2b0a6a 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkGroup.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkGroup.kt @@ -1,7 +1,6 @@ package com.meloda.fast.api.model.base import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.VkGroup import kotlinx.parcelize.Parcelize @@ -9,33 +8,24 @@ import kotlinx.parcelize.Parcelize data class BaseVkGroup( val id: Int, val name: String, - @SerializedName("screen_name") - val screenName: String, - @SerializedName("is_closed") - val isClosed: Int, + val screen_name: String, + val is_closed: Int, val type: String, - @SerializedName("is_admin") - val isAdmin: Int, - @SerializedName("is_member") - val isMember: Int, - @SerializedName("is_advertiser") - val isAdvertiser: Int, - @SerializedName("photo_50") - val photo50: String?, - @SerializedName("photo_100") - val photo100: String?, - @SerializedName("photo_200") - val photo200: String?, - @SerializedName("members_count") - val membersCount: Int? + val is_admin: Int, + val is_member: Int, + val is_advertiser: Int, + val photo_50: String?, + val photo_100: String?, + val photo_200: String?, + val members_count: Int? ) : Parcelable { fun asVkGroup() = VkGroup( id = -id, name = name, - screenName = screenName, - photo200 = photo200, - membersCount = membersCount + screenName = screen_name, + photo200 = photo_200, + membersCount = members_count ) } diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkMessage.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkMessage.kt index e03a9bdf..8d2a57cb 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkMessage.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkMessage.kt @@ -1,7 +1,6 @@ package com.meloda.fast.api.model.base import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.VkUtils import com.meloda.fast.api.model.VkMessage import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem @@ -10,23 +9,17 @@ import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkMessage( val date: Int, - @SerializedName("from_id") - val fromId: Int, + val from_id: Int, val id: Int, val out: Int, - @SerializedName("peer_id") - val peerId: Int, + val peer_id: Int, val text: String, - @SerializedName("conversation_message_id") - val conversationMessageId: Int, - @SerializedName("fwd_messages") - val fwdMessages: List? = listOf(), + val conversation_message_id: Int, + val fwd_messages: List? = listOf(), val important: Boolean, - @SerializedName("random_id") - val randomId: Int, + val random_id: Int, val attachments: List = listOf(), - @SerializedName("is_hidden") - val isHidden: Boolean, + val is_hidden: Boolean, val payload: String, val geo: Geo?, val action: Action?, @@ -37,20 +30,20 @@ data class BaseVkMessage( id = id, text = if (text.isBlank()) null else text, isOut = out == 1, - peerId = peerId, - fromId = fromId, + peerId = peer_id, + fromId = from_id, date = date, - randomId = randomId, + randomId = random_id, action = action?.type, - actionMemberId = action?.memberId, + actionMemberId = action?.member_id, actionText = action?.text, - actionConversationMessageId = action?.conversationMessageId, + actionConversationMessageId = action?.conversation_message_id, actionMessage = action?.message, geoType = geo?.type, important = important ).also { it.attachments = VkUtils.parseAttachments(attachments) - it.forwards = VkUtils.parseForwards(fwdMessages) + it.forwards = VkUtils.parseForwards(fwd_messages) } @Parcelize @@ -71,11 +64,9 @@ data class BaseVkMessage( @Parcelize data class Action( val type: String, - @SerializedName("member_id") - val memberId: Int?, + val member_id: Int?, val text: String?, - @SerializedName("conversation_message_id") - val conversationMessageId: Int?, + val conversation_message_id: Int?, val message: String? ) : Parcelable diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkUser.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkUser.kt index 0f16b727..e3cfb731 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkUser.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/BaseVkUser.kt @@ -1,35 +1,24 @@ package com.meloda.fast.api.model.base import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.VkUser import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkUser( val id: Int, - @SerializedName("first_name") - val firstName: String, - @SerializedName("last_name") - val lastName: String, - @SerializedName("can_access_closed") - val canAccessClosed: Boolean, - @SerializedName("is_closed") - val isClosed: Boolean, - @SerializedName("can_invite_to_chats") - val canInviteToChats: Boolean, + val first_name: String, + val last_name: String, + val can_access_closed: Boolean, + val is_closed: Boolean, + val can_invite_to_chats: Boolean, val sex: Int?, - @SerializedName("photo_50") - val photo50: String?, - @SerializedName("photo_100") - val photo100: String?, - @SerializedName("photo_200") - val photo200: String?, + val photo_50: String?, + val photo_100: String?, + val photo_200: String?, val online: Int?, - @SerializedName("online_info") - val onlineInfo: OnlineInfo?, - @SerializedName("screen_name") - val screenName: String + val online_info: OnlineInfo?, + val screen_name: String //...other fields ) : Parcelable { @@ -37,24 +26,20 @@ data class BaseVkUser( data class OnlineInfo( val visible: Boolean, val status: String, - @SerializedName("last_seen") - val lastSeen: Int?, - @SerializedName("is_online") - val isOnline: Boolean?, - @SerializedName("online_mobile") - val isOnlineMobile: Boolean?, - @SerializedName("app_id") - val appId: Int? + val last_seen: Int?, + val is_online: Boolean?, + val online_mobile: Boolean?, + val app_id: Int? ) : Parcelable fun asVkUser() = VkUser( id = id, - firstName = firstName, - lastName = lastName, + firstName = first_name, + lastName = last_name, online = online == 1, - photo200 = photo200, - lastSeen = onlineInfo?.lastSeen, - lastSeenStatus = onlineInfo?.status + photo200 = photo_200, + lastSeen = online_info?.last_seen, + lastSeenStatus = online_info?.status ) } diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAttachmentItem.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAttachmentItem.kt index 5b975b16..cee8ee92 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAttachmentItem.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAttachmentItem.kt @@ -1,6 +1,7 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable +import android.util.Log import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @@ -26,12 +27,16 @@ data class BaseVkAttachmentItem( val wallReply: BaseVkWallReply?, val call: BaseVkCall?, @SerializedName("group_call_in_progress") - val groupCall: BaseVkGroupCall? + val groupCall: BaseVkGroupCall?, + val curator: BaseVkCurator?, + val event: BaseVkEvent?, + val story: BaseVkStory? ) : Parcelable { fun getPreparedType() = AttachmentType.parse(type) - enum class AttachmentType(val value: String) { + enum class AttachmentType(var value: String) { + UNKNOWN("unknown"), PHOTO("photo"), VIDEO("video"), AUDIO("audio"), @@ -46,11 +51,22 @@ data class BaseVkAttachmentItem( POLL("poll"), WALL_REPLY("wall_reply"), CALL("call"), - GROUP_CALL_IN_PROGRESS("group_call_in_progress") + GROUP_CALL_IN_PROGRESS("group_call_in_progress"), + CURATOR("curator"), + EVENT("event"), + STORY("story") ; companion object { - fun parse(value: String) = values().firstOrNull { it.value == value } + fun parse(value: String): AttachmentType? { + val parsedValue = values().firstOrNull { it.value == value } ?: UNKNOWN + + if (parsedValue == UNKNOWN) { + Log.e("AttachmentType", "Unknown attachment type: $value") + } + + return parsedValue + } } } diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAudio.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAudio.kt index e25a276f..a497bd44 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAudio.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkAudio.kt @@ -1,7 +1,7 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName +import com.meloda.fast.api.model.attachments.VkAudio import kotlinx.parcelize.Parcelize @Parcelize @@ -12,37 +12,33 @@ data class BaseVkAudio( val duration: Int, val url: String, val date: Int, - @SerializedName("owner_id") - val ownerId: Int, - @SerializedName("access_key") - val accessKey: String, - @SerializedName("is_explicit") - val isExplicit: Boolean, - @SerializedName("is_focus_track") - val isFocusTrack: Boolean, - @SerializedName("is_licensed") - val isLicensed: Boolean, - @SerializedName("track_code") - val trackCode: String, - @SerializedName("genre_id") - val genreId: Int, + val owner_id: Int, + val access_key: String, + val is_explicit: Boolean, + val is_focus_track: Boolean, + val is_licensed: Boolean, + val track_code: String, + val genre_id: Int, val album: Album, - @SerializedName("short_videos_allowed") - val shortVideosAllowed: Boolean, - @SerializedName("stories_allowed") - val storiesAllowed: Boolean, - @SerializedName("stories_cover_allowed") - val storiesCoverAllowed: Boolean + val short_videos_allowed: Boolean, + val stories_allowed: Boolean, + val stories_cover_allowed: Boolean ) : BaseVkAttachment() { + fun asVkAudio() = VkAudio( + id = id, + title = title, + artist = artist, + url = url, + duration = duration + ) + @Parcelize data class Album( val id: Int, val title: String, - @SerializedName("owner_id") - val ownerId: Int, - @SerializedName("access_key") - val accessKey: String, + val owner_id: Int, + val access_key: String, val thumb: Thumb ) : Parcelable { @@ -50,22 +46,13 @@ data class BaseVkAudio( data class Thumb( val width: Int, val height: Int, - @SerializedName("photo_34") - val photo34: String, - @SerializedName("photo_68") - val photo68: String, - @SerializedName("photo_135") - val photo135: String, - @SerializedName("photo_270") - val photo270: String, - @SerializedName("photo_300") - val photo300: String, - @SerializedName("photo_600") - val photo600: String, - @SerializedName("photo_1200") - val photo1200: String + val photo_34: String, + val photo_68: String, + val photo_135: String, + val photo_270: String, + val photo_300: String, + val photo_600: String, + val photo_1200: String ) : Parcelable - } - } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCall.kt index 70cd1b80..9911e79a 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCall.kt @@ -1,15 +1,12 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkCall( - @SerializedName("initiator_id") - val initiatorId: Int, - @SerializedName("receiver_id") - val receiverId: Int, + val initiator_id: Int, + val receiver_id: Int, val state: String, val time: Int, val duration: Int, diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCurator.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCurator.kt new file mode 100644 index 00000000..a84b6e85 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkCurator.kt @@ -0,0 +1,27 @@ +package com.meloda.fast.api.model.base.attachments + +import android.os.Parcelable +import com.meloda.fast.api.model.attachments.VkCurator +import kotlinx.parcelize.Parcelize + +@Parcelize +data class BaseVkCurator( + val id: Int, + val name: String, + val description: String, + val url: String, + val photo: List +) : BaseVkAttachment() { + + fun asVkCurator() = VkCurator( + id = id + ) + + @Parcelize + data class Photo( + val height: Int, + val url: String, + val width: String + ) : Parcelable + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkEvent.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkEvent.kt new file mode 100644 index 00000000..d756c024 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkEvent.kt @@ -0,0 +1,22 @@ +package com.meloda.fast.api.model.base.attachments + +import com.meloda.fast.api.model.attachments.VkEvent +import kotlinx.parcelize.Parcelize + +@Parcelize +data class BaseVkEvent( + val button_text: String, + val id: Int, + val is_favorite: Boolean, + val text: String, + val address: String, + val friends: List = listOf(), + val member_status: Int, + val time: Int +) : BaseVkAttachment() { + + fun asVkEvent() = VkEvent( + id = id + ) + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkFile.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkFile.kt index 1791b814..07b2a967 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkFile.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkFile.kt @@ -1,14 +1,13 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName +import com.meloda.fast.api.model.attachments.VkFile import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkFile( val id: Int, - @SerializedName("owner_id") - val ownerId: Int, + val owner_id: Int, val title: String, val size: Int, val ext: String, @@ -16,14 +15,19 @@ data class BaseVkFile( val type: Int, val url: String, val preview: Preview?, - @SerializedName("is_licensed") - val isLicensed: Int, - @SerializedName("access_key") - val accessKey: String, - @SerializedName("web_preview_url") - val webPreviewUrl: String? + val ic_licensed: Int, + val access_key: String, + val web_preview_url: String? ) : BaseVkAttachment() { + fun asVkFile() = VkFile( + id = id, + title = title, + ext = ext, + url = url, + size = size + ) + @Parcelize data class Preview( val photo: Photo?, @@ -31,15 +35,24 @@ data class BaseVkFile( ) : Parcelable { @Parcelize - data class Photo(val sizes: List) : Parcelable + data class Photo(val sizes: List) : Parcelable { + + @Parcelize + data class Size( + val height: Int, + val width: Int, + val type: String, + val src: String + ) : Parcelable + + } @Parcelize data class Video( val src: String, val width: Int, val height: Int, - @SerializedName("file_size") - val fileSize: Int + val file_size: Int ) : Parcelable } diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGift.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGift.kt index bba4b6ea..2ed684ff 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGift.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGift.kt @@ -1,16 +1,12 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkGift( val id: Int, - @SerializedName("thumb_256") - val thumb256: String?, - @SerializedName("thumb_96") - val thumb96: String?, - @SerializedName("thumb_48") - val thumb48: String + val thumb_256: String?, + val thumb_96: String?, + val thumb_48: String ) : Parcelable \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGraffiti.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGraffiti.kt index d044e3fe..da07731a 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGraffiti.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGraffiti.kt @@ -1,17 +1,14 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkGraffiti( val id: Int, - @SerializedName("owner_id") - val ownerId: Int, + val owner_id: Int, val url: String, val width: Int, val height: Int, - @SerializedName("access_key") - val accessKey: String + val access_key: String ) : Parcelable \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGroupCall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGroupCall.kt index 72ed5124..41818361 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGroupCall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkGroupCall.kt @@ -1,15 +1,12 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkGroupCall( - @SerializedName("initiator_id") - val initiatorId: Int, - @SerializedName("join_link") - val joinLink: String, + val initiator_id: Int, + val join_link: String, val participants: Participants ) : Parcelable { diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkLink.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkLink.kt index baee2679..c127f0c2 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkLink.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkLink.kt @@ -1,15 +1,25 @@ package com.meloda.fast.api.model.base.attachments -import com.google.gson.annotations.SerializedName +import com.meloda.fast.api.model.attachments.VkLink import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkLink( val url: String, - val title: String, - val caption: String, - val photo: BaseVkPhoto, + val title: String?, + val caption: String?, + val photo: BaseVkPhoto?, val target: String, - @SerializedName("is_favorite") - val isFavorite: Boolean -) : BaseVkAttachment() \ No newline at end of file + val is_favorite: Boolean +) : BaseVkAttachment() { + + fun asVkLink() = VkLink( + url = url, + title = title, + caption = caption, + photo = photo?.asVkPhoto(), + target = target, + isFavorite = is_favorite + ) + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkMiniApp.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkMiniApp.kt index 4c91922b..927f4404 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkMiniApp.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkMiniApp.kt @@ -10,8 +10,7 @@ data class BaseVkMiniApp( val description: String, val app: App, val images: List?, - @SerializedName("button_text") - val buttonText: String + val button_text: String ) : Parcelable { @Parcelize diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPhoto.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPhoto.kt index 6e89e263..218f7109 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPhoto.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPhoto.kt @@ -1,47 +1,43 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.attachments.VkPhoto import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkPhoto( - @SerializedName("album_id") - val albumId: Int, + val album_id: Int, val date: Int, val id: Int, - @SerializedName("owner_id") - val ownerId: Int, - @SerializedName("has_tags") - val hasTags: Boolean, - @SerializedName("access_key") - val accessKey: String?, + val owner_id: Int, + val has_tags: Boolean, + val access_key: String?, val sizes: List, val text: String, - @SerializedName("user_id") - val userId: Int? + val user_id: Int?, + val lat: Double?, + val long: Double?, + val post_id: Int? ) : BaseVkAttachment() { fun asVkPhoto() = VkPhoto( - albumId = albumId, + albumId = album_id, date = date, id = id, - ownerId = ownerId, - hasTags = hasTags, - accessKey = accessKey, + ownerId = owner_id, + hasTags = has_tags, + accessKey = access_key, sizes = sizes, text = text, - userId = userId + userId = user_id ) -} + @Parcelize + data class Size( + val height: Int, + val width: Int, + val type: String, + val url: String + ) : Parcelable -@Parcelize -data class Size( - val height: Int, - val width: Int, - val type: String, - @SerializedName("url", alternate = ["src"]) - val url: String, -) : Parcelable \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPoll.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPoll.kt index c41aa349..f5ec7d80 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPoll.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkPoll.kt @@ -1,7 +1,6 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize @@ -11,30 +10,20 @@ data class BaseVkPoll( val votes: Int, val anonymous: Boolean, val closed: Boolean, - @SerializedName("end_date") - val endDate: Int, - @SerializedName("is_board") - val isBoard: Boolean, - @SerializedName("can_vote") - val canVote: Boolean, - @SerializedName("can_edit") - val canEdit: Boolean, - @SerializedName("can_report") - val canReport: Boolean, - @SerializedName("can_share") - val canShare: Boolean, + val end_date: Int, + val is_board: Boolean, + val can_vote: Boolean, + val can_edit: Boolean, + val can_report: Boolean, + val can_share: Boolean, val created: Int, - @SerializedName("owner_id") - val ownerId: Int, + val owner_id: Int, val question: String, - @SerializedName("disable_unvote") - val disableUnVote: Boolean, + val disable_unvote: Boolean, val friends: List?, - @SerializedName("embed_hash") - val embedHash: String, + val embed_hash: String, val answers: List, - @SerializedName("author_id") - val authorId: Int, + val author_id: Int, val background: Background? ) : Parcelable { diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkSticker.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkSticker.kt index c162e57f..79e053d1 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkSticker.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkSticker.kt @@ -1,30 +1,24 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import androidx.annotation.IntDef -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.attachments.VkSticker import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkSticker( - @SerializedName("product_id") - val productId: Int, - @SerializedName("sticker_id") - val stickerId: Int, + val product_id: Int, + val sticker_id: Int, val images: List, - @SerializedName("images_with_background") - val imagesWithBackground: List, - @SerializedName("animation_url") - val animationUrl: String?, + val images_with_background: List, + val animation_url: String?, val animations: List? ) : Parcelable { fun asVkSticker() = VkSticker( - id = stickerId, - productId = productId, + id = sticker_id, + productId = product_id, images = images, - backgroundImages = imagesWithBackground + backgroundImages = images_with_background ) @Parcelize @@ -41,6 +35,3 @@ data class BaseVkSticker( ) : Parcelable } - -@IntDef(64, 128, 256, 352) -annotation class StickerSize diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkStory.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkStory.kt new file mode 100644 index 00000000..846cf970 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkStory.kt @@ -0,0 +1,52 @@ +package com.meloda.fast.api.model.base.attachments + +import android.os.Parcelable +import com.meloda.fast.api.model.attachments.VkStory +import kotlinx.parcelize.Parcelize + +@Parcelize +data class BaseVkStory( + val id: Int, + val owner_id: Int, + val access_key: String, + val can_comment: Int, + val can_reply: Int, + val can_like: Boolean, + val can_share: Int, + val can_hide: Int, + val date: Int, + val expires_at: Int, + val is_ads: Boolean, + val photo: BaseVkPhoto, + val replies: Replies, + val is_one_time: Boolean, + val track_code: String, + val type: String, + val views: Int, + val likes_count: Int, + val reaction_set_id: String, + val is_restricted: Boolean, + val no_sound: Boolean, + val need_mute: Boolean, + val mute_reply: Boolean, + val can_ask: Int, + val can_ask_anonymous: Int, + val preloading_enabled: Boolean, + val narratives_count: Int, + val can_use_in_narrative: Boolean +) : BaseVkAttachment() { + + fun asVkStory() = VkStory( + id = id, + ownerId = owner_id, + date = date, + photo = photo.asVkPhoto() + ) + + @Parcelize + data class Replies( + val count: Int, + val new: Int + ) : Parcelable + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVideo.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVideo.kt index e827e67d..c659c095 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVideo.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVideo.kt @@ -1,11 +1,9 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import com.meloda.fast.api.model.attachments.VkVideo import kotlinx.parcelize.Parcelize -//not all fields @Parcelize data class BaseVkVideo( val id: Int, @@ -20,45 +18,30 @@ data class BaseVkVideo( val added: Int, val type: String, val views: Int, - @SerializedName("can_comment") - val canComment: Int, - @SerializedName("can_edit") - val canEdit: Int, - @SerializedName("can_like") - val canLike: Int, - @SerializedName("can_repost") - val canRepost: Int, - @SerializedName("can_subscribe") - val canSubscribe: Int, - @SerializedName("can_add_to_faves") - val canAddToFaves: Int, - @SerializedName("can_add") - val canAdd: Int, - @SerializedName("can_attach_link") - val canAttachLink: Int, - @SerializedName("access_key") - val accessKey: String, - @SerializedName("owner_id") - val ownerId: Int, - @SerializedName("ov_id") - val ovId: String, - @SerializedName("is_favorite") - val isFavorite: Boolean, - @SerializedName("track_code") - val trackCode: String, + val can_comment: Int, + val can_edit: Int, + val can_like: Int, + val can_repost: Int, + val can_subscribe: Int, + val can_add_to_faves: Int, + val can_add: Int, + val can_attach_link: Int, + val access_key: String, + val owner_id: Int, + val ov_id: String, + val is_favorite: Boolean, + val track_code: String, val image: List, - @SerializedName("first_frame") - val firstFrame: List, + val first_frame: List, val files: File, - @SerializedName("timeline_thumbs") - val timelineThumbs: TimelineThumbs - //ads + val timeline_thumbs: TimelineThumbs, + val ads: Ads ) : BaseVkAttachment() { fun asVkVideo() = VkVideo( id = id, images = image, - firstFrames = firstFrame + firstFrames = first_frame ) @Parcelize @@ -66,8 +49,7 @@ data class BaseVkVideo( val height: Int, val width: Int, val url: String, - @SerializedName("with_padding") - val withPadding: Int + val with_padding: Int ) : Parcelable @Parcelize @@ -86,34 +68,62 @@ data class BaseVkVideo( val mp4_1080: String?, val mp4_1440: String?, val hls: String, - @SerializedName("dash_uni") - val dashUni: String, - @SerializedName("dash_sep") - val dashSep: String, - @SerializedName("hls_ondemand") - val hlsOnDemand: String, - @SerializedName("dash_ondemand") - val dashOnDemand: String, - @SerializedName("failover_host") - val failOverHost: String + val dash_uni: String, + val dash_sep: String, + val hls_ondemand: String, + val dash_ondemand: String, + val failover_host: String ) : Parcelable @Parcelize data class TimelineThumbs( - @SerializedName("count_per_image") - val countPerImage: Int, - @SerializedName("count_per_row") - val countPerRow: Int, - @SerializedName("count_total") - val countTotal: Int, - @SerializedName("frame_height") - val frameHeight: Int, - @SerializedName("frame_width") - val frameWidth: Float, + val count_per_image: Int, + val count_per_row: Int, + val count_total: Int, + val frame_height: Int, + val frame_width: Float, val links: List, - @SerializedName("is_uv") - val isUv: Boolean, + val is_uv: Boolean, val frequency: Int ) : Parcelable + @Parcelize + data class Ads( + val slot_id: Int, + val timeout: Int, + val can_play: Int, + val params: Params, + val sections: List, + val midroll_percents: List + ) : Parcelable { + + @Parcelize + data class Params( + val vk_id: Int, + val duration: Int, + val video_id: String, + val pl: Int, + val content_id: String, + val lang: Int, + val puid1: String, + val puid2: Int, + val puid3: Int, + val puid5: Int, + val puid6: Int, + val puid7: Int, + val puid9: Int, + val puid10: Int, + val puid12: Int, + val puid13: Int, + val puid14: Int, + val puid15: Int, + val puid18: Int, + val puid21: Int, + val sign: String, + val groupId: Int, + val vk_catid: Int, + val is_xz_video: Int + ) : Parcelable + } + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVoiceMessage.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVoiceMessage.kt index 745fe162..88b37355 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVoiceMessage.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkVoiceMessage.kt @@ -1,23 +1,17 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkVoiceMessage( val id: Int, - @SerializedName("owner_id") - val ownerId: Int, + val owner_id: Int, val duration: Int, val waveform: List, - @SerializedName("link_ogg") - val linkOgg: String, - @SerializedName("link_mp3") - val linkMp3: String, - @SerializedName("access_key") - val accessKey: String, - @SerializedName("transcript_state") - val transcriptState: String, + val link_ogg: String, + val link_mp3: String, + val access_key: String, + val transcript_state: String, val transcript: String ) : Parcelable \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWall.kt index 8637ccce..dd831744 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWall.kt @@ -1,34 +1,43 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName +import com.meloda.fast.api.model.attachments.VkWall import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkWall( val id: Int, - @SerializedName("from_id") - val fromId: Int, - @SerializedName("to_id") - val toId: Int, + val from_id: Int, + val to_id: Int, val date: Int, val text: String, val attachments: List?, - @SerializedName("post_source") - val postSource: PostSource, + val post_source: PostSource, val comments: Comments, val likes: Likes, val reposts: Reposts, val views: Views, - @SerializedName("is_favorite") - val isFavorite: Boolean, + val is_favorite: Boolean, val donut: Donut, - @SerializedName("access_key") - val accessKey: String, - @SerializedName("short_text_rate") - val shortTextRate: Double + val access_key: String, + val short_text_rate: Double ) : Parcelable { + fun asVkWall() = VkWall( + id = id, + fromId = from_id, + toId = to_id, + date = date, + text = text, + attachments = attachments, + comments = comments.count, + likes = likes.count, + reposts = reposts.count, + views = views.count, + isFavorite = is_favorite, + accessKey = access_key + ) + @Parcelize data class PostSource( val type: String, @@ -38,28 +47,22 @@ data class BaseVkWall( @Parcelize data class Comments( val count: Int, - @SerializedName("can_post") - val canPost: Int, - @SerializedName("groups_can_post") - val groupsCanPost: Boolean + val can_post: Int, + val groups_can_post: Boolean ) : Parcelable @Parcelize data class Likes( val count: Int, - @SerializedName("user_likes") - val userLikes: Int, - @SerializedName("can_like") - val canLike: Int, - @SerializedName("can_publish") - val canPublish: Int, + val user_likes: Int, + val can_like: Int, + val can_publish: Int, ) : Parcelable @Parcelize data class Reposts( val count: Int, - @SerializedName("user_reposted") - val userReposted: Int + val user_reposted: Int ) : Parcelable @Parcelize @@ -69,8 +72,7 @@ data class BaseVkWall( @Parcelize data class Donut( - @SerializedName("is_donut") - val isDonut: Boolean + val is_donut: Boolean ) : Parcelable } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWallReply.kt b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWallReply.kt index 5ec039be..9abfb992 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWallReply.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/base/attachments/BaseVkWallReply.kt @@ -1,39 +1,29 @@ package com.meloda.fast.api.model.base.attachments import android.os.Parcelable -import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @Parcelize data class BaseVkWallReply( val id: Int, - @SerializedName("from_id") - val fromId: Int, + val from_id: Int, val date: Int, val text: String, - @SerializedName("post_id") - val postId: Int, - @SerializedName("owner_id") - val ownerId: Int, - @SerializedName("parents_stack") - val parentsStack: List, + val post_id: Int, + val owner_id: Int, + val parents_stack: List, val likes: Likes, - @SerializedName("reply_to_user") - val replyToUser: Int?, - @SerializedName("reply_to_comment") - val replyToComment: Int? + val reply_to_user: Int?, + val reply_to_comment: Int? ) : Parcelable { @Parcelize data class Likes( val count: Int, - @SerializedName("can_like") - val canLike: Int, - @SerializedName("user_likes") - val userLikes: Int, - @SerializedName("can_publish") - val canPublish: Int + val can_like: Int, + val user_likes: Int, + val can_publish: Int ) : Parcelable } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/network/ResultCallFactory.kt b/app/src/main/kotlin/com/meloda/fast/api/network/ResultCallFactory.kt index 5f4314d4..1950b7b9 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/network/ResultCallFactory.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/network/ResultCallFactory.kt @@ -77,18 +77,21 @@ internal class ResultCall(proxy: Call) : CallDelegate>(proxy) ) : Callback { override fun onResponse(call: Call, response: Response) { + var isVkException = true + val result: Answer = if (response.isSuccessful) { val baseBody = response.body() if (baseBody !is ApiResponse<*>) Answer.Success(baseBody as T) else { val body = baseBody as ApiResponse<*> - if (body.error != null) Answer.Error(body.error) - else Answer.Success(body as T) + if (body.error != null) { + Answer.Error(body.error) + } else Answer.Success(body as T) } } else Answer.Error(IOException(response.errorBody()?.string() ?: "")) - if (result is Answer.Error) if (checkErrors(call, result)) return + if (result is Answer.Error && isVkException) if (checkErrors(call, result)) return callback.onResponse(proxy, Response.success(result)) diff --git a/app/src/main/kotlin/com/meloda/fast/common/AppGlobal.kt b/app/src/main/kotlin/com/meloda/fast/common/AppGlobal.kt index eb093fdc..279cc944 100644 --- a/app/src/main/kotlin/com/meloda/fast/common/AppGlobal.kt +++ b/app/src/main/kotlin/com/meloda/fast/common/AppGlobal.kt @@ -7,15 +7,16 @@ import android.content.SharedPreferences import android.content.pm.PackageManager import android.content.res.Resources import android.net.ConnectivityManager +import android.util.Log import android.view.inputmethod.InputMethodManager import androidx.core.content.pm.PackageInfoCompat import androidx.preference.PreferenceManager import androidx.room.Room import com.meloda.fast.BuildConfig import com.meloda.fast.database.AppDatabase -import com.meloda.fast.util.AndroidUtils import dagger.hilt.android.HiltAndroidApp import org.acra.ACRA +import kotlin.math.sqrt @HiltAndroidApp class AppGlobal : Application() { @@ -66,8 +67,24 @@ class AppGlobal : Application() { Companion.packageName = packageName Companion.packageManager = packageManager - screenWidth = AndroidUtils.getDisplayWidth() - screenHeight = AndroidUtils.getDisplayHeight() + screenWidth = resources.displayMetrics.widthPixels + screenHeight = resources.displayMetrics.heightPixels + + val density = resources.displayMetrics.density + val densityDpi = resources.displayMetrics.densityDpi + val densityScaled = resources.displayMetrics.scaledDensity + val xDpi = resources.displayMetrics.xdpi + val yDpi = resources.displayMetrics.ydpi + + val diagonal = sqrt( + (screenWidth * screenWidth - screenHeight * screenHeight).toFloat() + ) + + Log.i( + "Fast::DeviceInfo", + "width: $screenWidth; height: $screenHeight; density: $density; diagonal: $diagonal; dpiDensity: $densityDpi; scaledDensity: $densityScaled; xDpi: $xDpi; yDpi: $yDpi" + ) + inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager diff --git a/app/src/main/kotlin/com/meloda/fast/common/AppSettings.kt b/app/src/main/kotlin/com/meloda/fast/common/AppSettings.kt new file mode 100644 index 00000000..1f9d8b6b --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/common/AppSettings.kt @@ -0,0 +1,23 @@ +package com.meloda.fast.common + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job + + +object AppSettings { + + val keyIsMultilineEnabled = booleanPreferencesKey("isMultilineEnabled") +} + +val Context.dataStore: DataStore by preferencesDataStore( + name = "settings", + corruptionHandler = null, + scope = CoroutineScope(Dispatchers.IO + Job()) +) + diff --git a/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsAdapter.kt b/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsAdapter.kt index b52c8069..2f829598 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsAdapter.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.ColorDrawable import android.text.SpannableString +import android.text.TextUtils import android.text.style.ForegroundColorSpan import android.view.ViewGroup import androidx.core.content.ContextCompat @@ -27,7 +28,8 @@ class ConversationsAdapter constructor( context: Context, values: MutableList, val profiles: HashMap = hashMapOf(), - val groups: HashMap = hashMapOf() + val groups: HashMap = hashMapOf(), + var isMultilineEnabled: Boolean = true ) : BaseAdapter( context, values, COMPARATOR ) { @@ -41,6 +43,11 @@ class ConversationsAdapter constructor( private val dateColor = ContextCompat.getColor(context, R.color.n2_500) private val youPrefix = context.getString(R.string.you_message_prefix) + init { + binding.title.ellipsize = TextUtils.TruncateAt.END + binding.message.ellipsize = TextUtils.TruncateAt.END + } + override fun bind(position: Int) { val conversation = getItem(position) @@ -48,6 +55,11 @@ class ConversationsAdapter constructor( binding.callIcon.isVisible = conversation.callInProgress binding.phantomIcon.isVisible = conversation.isPhantom + val maxLines = if (isMultilineEnabled) 2 else 1 + + binding.title.maxLines = maxLines + binding.message.maxLines = maxLines + val message = if (conversation.lastMessage != null) conversation.lastMessage!! else { binding.title.text = conversation.title @@ -129,6 +141,7 @@ class ConversationsAdapter constructor( binding.pin.isVisible = conversation.isPinned val actionMessage = VkUtils.getActionConversationText( + context = context, message = message, youPrefix = youPrefix, profiles = profiles, diff --git a/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsFragment.kt b/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsFragment.kt index 095cc49a..b497f821 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsFragment.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/conversations/ConversationsFragment.kt @@ -6,10 +6,13 @@ import android.viewbinding.library.fragment.viewBinding import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.isVisible +import androidx.datastore.preferences.core.edit import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import coil.load import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.snackbar.Snackbar import com.meloda.fast.R import com.meloda.fast.api.UserConfig import com.meloda.fast.api.model.VkConversation @@ -17,9 +20,14 @@ import com.meloda.fast.base.BaseViewModelFragment import com.meloda.fast.base.viewmodel.StartProgressEvent import com.meloda.fast.base.viewmodel.StopProgressEvent import com.meloda.fast.base.viewmodel.VKEvent +import com.meloda.fast.common.AppSettings +import com.meloda.fast.common.dataStore import com.meloda.fast.databinding.FragmentConversationsBinding import com.meloda.fast.util.AndroidUtils import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlin.math.abs @AndroidEntryPoint @@ -59,7 +67,18 @@ class ConversationsFragment : binding.recyclerView.adapter = adapter - binding.createChat.setOnClickListener {} + lifecycleScope.launch { + requireContext().dataStore.data.map { + adapter.isMultilineEnabled = it[AppSettings.keyIsMultilineEnabled] ?: true + adapter.notifyItemRangeChanged(0, adapter.itemCount) + }.collect { } + } + + binding.createChat.setOnClickListener { + Snackbar.make(it, "Test snackbar", Snackbar.LENGTH_SHORT) + .setAction("Action") {} + .show() + } UserConfig.vkUser.observe(viewLifecycleOwner) { it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } } @@ -87,6 +106,18 @@ class ConversationsFragment : viewModel.loadProfileUser() viewModel.loadConversations() + + binding.avatar.setOnClickListener { + lifecycleScope.launch { + requireContext().dataStore.edit { settings -> + val isMultilineEnabled = settings[AppSettings.keyIsMultilineEnabled] ?: true + settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled + + adapter.isMultilineEnabled = !isMultilineEnabled + adapter.notifyItemRangeChanged(0, adapter.itemCount) + } + } + } } override fun onEvent(event: VKEvent) { diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/AttachmentInflater.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/AttachmentInflater.kt new file mode 100644 index 00000000..581ec9a3 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/AttachmentInflater.kt @@ -0,0 +1,309 @@ +package com.meloda.fast.screens.messages + +import android.content.Context +import android.content.res.ColorStateList +import android.util.Log +import android.view.Gravity +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.Space +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.core.content.ContextCompat +import androidx.core.view.isNotEmpty +import androidx.core.view.isVisible +import androidx.core.view.setPadding +import coil.load +import com.google.android.material.imageview.ShapeableImageView +import com.meloda.fast.R +import com.meloda.fast.api.VkUtils +import com.meloda.fast.api.model.VkGroup +import com.meloda.fast.api.model.VkMessage +import com.meloda.fast.api.model.VkUser +import com.meloda.fast.api.model.attachments.* +import com.meloda.fast.databinding.* +import com.meloda.fast.util.AndroidUtils +import com.meloda.fast.widget.RoundedFrameLayout +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.roundToInt + +class AttachmentInflater constructor( + private val context: Context, + private val container: LinearLayoutCompat, + private val message: VkMessage, + private val profiles: Map, + private val groups: Map +) { + private lateinit var attachments: List + + private val inflater = LayoutInflater.from(context) + + private val playColor = ContextCompat.getColor(context, R.color.a3_700) + private val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200) + + fun inflate() { + if (message.attachments.isNullOrEmpty()) return + attachments = message.attachments!! + + container.removeAllViews() + + if (attachments.size == 1) { + when (val attachment = attachments[0]) { + is VkSticker -> return sticker(attachment) + is VkWall -> return wall(attachment) + } + } + + if (attachments.size > 1) { + if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkPhoto) { + return attachments.forEach { photo(it as VkPhoto) } + } + + if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkVideo) { + return attachments.forEach { video(it as VkVideo) } + } + + if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkAudio) { + return attachments.forEach { audio(it as VkAudio) } + } + + if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkFile) { + return attachments.forEach { file(it as VkFile) } + } + } + + attachments.forEach { attachment -> + when (attachment) { + is VkPhoto -> photo(attachment) + is VkVideo -> video(attachment) + is VkAudio -> audio(attachment) + is VkFile -> file(attachment) + is VkLink -> link(attachment) + is VkStory -> story(attachment) + + else -> Log.e( + "Attachment inflater", + "Unknown attachment type: ${attachment.javaClass.name}" + ) + } + } + + } + + private fun photo(photo: VkPhoto) { + val size = photo.sizeOfType('m') ?: return + + val newPhoto = ShapeableImageView(context).apply { + layoutParams = LinearLayoutCompat.LayoutParams( + AndroidUtils.px(size.width).roundToInt(), + AndroidUtils.px(size.height).roundToInt() + ) + + shapeAppearanceModel = + shapeAppearanceModel.withCornerSize { + AndroidUtils.px(5) + } + + scaleType = ImageView.ScaleType.CENTER_CROP + } + + val spacer = Space(context).also { + it.layoutParams = LinearLayoutCompat.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + AndroidUtils.px(5).roundToInt() + ) + } + + if (container.isNotEmpty()) + container.addView(spacer) + + if (attachments.size == 1) { + val roundedLayout = RoundedFrameLayout(context).apply { + setTopRightCornerRadius((if (message.isOut) 30 else 40).toFloat()) + setTopLeftCornerRadius((if (message.isOut) 40 else 30).toFloat()) + setBottomRightCornerRadius((if (message.isOut) 5 else 40).toFloat()) + setBottomLeftCornerRadius((if (message.isOut) 40 else 5).toFloat()) + } + + roundedLayout.addView(newPhoto) + container.addView(roundedLayout) + } else { + container.addView(newPhoto) + } + + newPhoto.load(size.url) { crossfade(100) } + } + + private fun video(video: VkVideo) { + val size = video.images[1] + + val layout = FrameLayout(context).apply { + layoutParams = LinearLayoutCompat.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + val newPhoto = ShapeableImageView(context).apply { + layoutParams = FrameLayout.LayoutParams( + AndroidUtils.px(size.width).roundToInt(), + AndroidUtils.px(size.height).roundToInt() + ) + + shapeAppearanceModel = + shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) } + scaleType = ImageView.ScaleType.CENTER_CROP + } + + val play = AppCompatImageView(context).apply { + layoutParams = FrameLayout.LayoutParams( + AndroidUtils.px(50).roundToInt(), + AndroidUtils.px(50).roundToInt() + ).also { + it.gravity = Gravity.CENTER + } + + backgroundTintList = ColorStateList.valueOf(playBackgroundColor) + imageTintList = ColorStateList.valueOf(playColor) + + setBackgroundResource(R.drawable.ic_play_button_circle_background) + setImageResource(R.drawable.ic_round_play_arrow_24) + + setPadding(12) + } + + layout.addView(newPhoto) + layout.addView(play) + + val spacer = Space(context).apply { + layoutParams = LinearLayoutCompat.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + AndroidUtils.px(5).roundToInt() + ) + } + + if (container.isNotEmpty()) + container.addView(spacer) + + container.addView(layout) + + newPhoto.load(size.url) { crossfade(100) } + } + + private fun audio(audio: VkAudio) { + val binding = ItemMessageAttachmentAudioBinding.inflate(inflater, container, true) + + binding.title.text = audio.title + binding.artist.text = "%s | %s".format( + audio.artist, + SimpleDateFormat("mm:ss", Locale.getDefault()).format(audio.duration * 1000L) + ) + } + + private fun file(file: VkFile) { + val binding = ItemMessageAttachmentFileBinding.inflate(inflater, container, true) + + binding.title.text = file.title + binding.size.text = "%s | %s".format( + AndroidUtils.bytesToHumanReadableSize(file.size.toDouble()), + file.ext.uppercase() + ) + } + + private fun link(link: VkLink) { + val binding = ItemMessageAttachmentLinkBinding.inflate(inflater, container, true) + + binding.title.text = link.title + binding.title.isVisible = !link.title.isNullOrBlank() + + binding.caption.text = link.caption + binding.caption.isVisible = !link.caption.isNullOrBlank() + + binding.preview.shapeAppearanceModel.toBuilder() + .setAllCornerSizes(40f) + .build() + .let { + binding.preview.shapeAppearanceModel = it + } + + link.photo?.sizeOfType('m')?.let { + binding.preview.load(it.url) { crossfade(150) } + binding.preview.isVisible = true + return + } + + binding.preview.isVisible = false + } + + private fun sticker(sticker: VkSticker) { + val binding = ItemMessageAttachmentStickerBinding.inflate(inflater, container, true) + + val url = sticker.urlForSize(352) + + with(binding.image) { + layoutParams = LinearLayoutCompat.LayoutParams( + AndroidUtils.px(180).roundToInt(), + AndroidUtils.px(180).roundToInt() + ) + + load(url) { crossfade(150) } + } + } + + private fun wall(wall: VkWall) { + val binding = ItemMessageAttachmentWallPostBinding.inflate(inflater, container, true) + + val group = if (wall.fromId > 0) null else groups[wall.fromId] + val user = if (wall.fromId < 0) null else profiles[wall.fromId] + + val postTitleRes = when { + group != null && user == null -> R.string.post_type_community + user != null && group == null -> R.string.post_type_user + else -> R.string.post_type_unknown + } + + val avatar = when { + group == null && user != null -> user.photo200 + user == null && group != null -> group.photo200 + else -> null + } + + val title = when { + group == null && user != null -> user.fullName + user == null && group != null -> group.name + else -> "..." + } + + binding.postTitle.text = context.getString(postTitleRes) + binding.postTitle.isVisible = false + + binding.avatar.isVisible = group != null || user != null + binding.avatar.shapeAppearanceModel.toBuilder() + .setAllCornerSizes(40f) + .build() + .let { + binding.avatar.shapeAppearanceModel = it + } + + if (binding.avatar.isVisible) { + binding.avatar.load(avatar) { crossfade(150) } + } else { + binding.avatar.setImageDrawable(null) + } + + binding.title.text = title + + binding.date.text = SimpleDateFormat( + "dd.MM.yyyy HH:mm", + Locale.getDefault() + ).format(wall.date * 1000L) + } + + private fun story(story: VkStory) { + + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryAdapter.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryAdapter.kt index acdd3607..c296733b 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryAdapter.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryAdapter.kt @@ -3,12 +3,9 @@ package com.meloda.fast.screens.messages import android.content.Context import android.graphics.Color import android.graphics.drawable.ColorDrawable -import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.appcompat.widget.LinearLayoutCompat -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import coil.load @@ -19,13 +16,10 @@ import com.meloda.fast.api.model.VkGroup import com.meloda.fast.api.model.VkMessage import com.meloda.fast.api.model.VkUser import com.meloda.fast.api.model.attachments.VkPhoto -import com.meloda.fast.api.model.attachments.VkSticker -import com.meloda.fast.api.model.attachments.VkVideo import com.meloda.fast.base.adapter.BaseAdapter import com.meloda.fast.base.adapter.BaseHolder import com.meloda.fast.databinding.* import com.meloda.fast.util.AndroidUtils -import java.text.SimpleDateFormat import java.util.* import kotlin.math.roundToInt @@ -45,21 +39,6 @@ class MessagesHistoryAdapter constructor( getItem(position).let { message -> if (message.action != null) return SERVICE - - if (!message.attachments.isNullOrEmpty()) { - val attachments = message.attachments ?: return@let - if (VkUtils.isAttachmentsHaveOneType(attachments) && - attachments[0] is VkPhoto - ) return if (message.isOut) ATTACHMENT_PHOTOS_OUT - else ATTACHMENT_PHOTOS_IN - - if (attachments[0] is VkVideo) return if (message.isOut) ATTACHMENT_VIDEOS_OUT - else ATTACHMENT_VIDEOS_IN - - if (attachments[0] is VkSticker) return if (message.isOut) ATTACHMENT_STICKER_OUT - else ATTACHMENT_STICKER_IN - } - if (message.isOut) return OUTGOING if (!message.isOut) return INCOMING } @@ -72,26 +51,12 @@ class MessagesHistoryAdapter constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { return when (viewType) { + // magick numbers is great! HEADER -> Header(createEmptyView(60)) FOOTER -> Footer(createEmptyView(36)) SERVICE -> ServiceMessage( ItemMessageServiceBinding.inflate(inflater, parent, false) ) - ATTACHMENT_STICKER_IN -> AttachmentStickerIncoming( - ItemMessageAttachmentStickerInBinding.inflate(inflater, parent, false) - ) - ATTACHMENT_STICKER_OUT -> AttachmentStickerOutgoing( - ItemMessageAttachmentStickerOutBinding.inflate(inflater, parent, false) - ) - ATTACHMENT_PHOTOS_IN -> AttachmentPhotosIncoming( - ItemMessageAttachmentPhotosInBinding.inflate(inflater, parent, false) - ) - ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing( - ItemMessageAttachmentPhotosOutBinding.inflate(inflater, parent, false) - ) - ATTACHMENT_VIDEOS_IN, ATTACHMENT_VIDEOS_OUT -> AttachmentVideosIncoming( - ItemMessageAttachmentVideosInBinding.inflate(inflater, parent, false) - ) OUTGOING -> OutgoingMessage( ItemMessageOutBinding.inflate(inflater, parent, false) ) @@ -130,81 +95,31 @@ class MessagesHistoryAdapter constructor( private val binding: ItemMessageInBinding ) : Holder(binding.root) { - private val backgroundNormal = - ContextCompat.getDrawable(context, R.drawable.ic_message_in_background) - private val backgroundMiddle = - ContextCompat.getDrawable(context, R.drawable.ic_message_in_background_middle) - - init { - MessagesManager.setRootMaxWidth(binding.bubble) - } - override fun bind(position: Int) { val message = getItem(position) val prevMessage = getOrNull(position - 1) val nextMessage = getOrNull(position + 1) - binding.unread.isVisible = message.isRead(conversation) - - binding.bubble.background = - if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormal - else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddle - else backgroundNormal - - if (!message.isPeerChat()) { - binding.title.isVisible = false - binding.avatar.isVisible = false - - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - } else { - binding.title.isVisible = - if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat() - else message.date - prevMessage.date >= 60 - - binding.spacer.isVisible = binding.title.isVisible - - binding.avatar.visibility = - if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE - else if (nextMessage.date - message.date >= 60) View.VISIBLE - else View.INVISIBLE - } - - val messageUser: VkUser? = if (message.isUser()) { - profiles[message.fromId] - } else null - - val messageGroup: VkGroup? = if (message.isGroup()) { - groups[message.fromId] - } else null - - MessagesManager.loadMessageAvatar( + MessagesPreparator( + context = context, + conversation = conversation, message = message, - messageUser = messageUser, - messageGroup = messageGroup, - imageView = binding.avatar - ) + prevMessage = prevMessage, + nextMessage = nextMessage, - val title = when { - message.isUser() && messageUser != null -> messageUser.firstName - message.isGroup() && messageGroup != null -> messageGroup.name - else -> null - } + avatar = binding.avatar, + bubble = binding.bubble, + text = binding.text, + spacer = binding.spacer, + time = binding.time, + unread = binding.unread, + attachmentContainer = binding.attachmentContainer, + attachmentSpacer = binding.attachmentSpacer, - binding.title.text = title - binding.title.measure(0, 0) - - if (binding.title.isVisible) { - binding.bubble.minimumWidth = binding.title.measuredWidth + 60 - } else { - binding.bubble.minimumWidth = 0 - } - - MessagesManager.setMessageText( - message = message, - textView = binding.text - ) + profiles = profiles, + groups = groups + ).prepare() } } @@ -212,23 +127,8 @@ class MessagesHistoryAdapter constructor( private val binding: ItemMessageOutBinding ) : Holder(binding.root) { - private val backgroundNormal = - ContextCompat.getDrawable(context, R.drawable.ic_message_out_background) - private val backgroundMiddle = - ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle) - private val backgroundStroke = - ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke) - private val backgroundMiddleStroke = - ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke) - init { - MessagesManager.setRootMaxWidth(binding.bubble) - binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() } - - binding.bubble.setOnClickListener { - binding.time.isVisible = !binding.time.isVisible - } } override fun bind(position: Int) { @@ -236,27 +136,25 @@ class MessagesHistoryAdapter constructor( val prevMessage = getOrNull(position - 1) - binding.text.text = message.text ?: "[no_message]" + MessagesPreparator( + context = context, + conversation = conversation, + message = message, + prevMessage = prevMessage, - binding.unread.isVisible = message.isRead(conversation) + bubble = binding.bubble, + bubbleStroke = binding.bubbleStroke, + text = binding.text, + spacer = binding.spacer, + time = binding.time, + unread = binding.unread, + attachmentContainer = binding.attachmentContainer, + attachmentSpacer = binding.attachmentSpacer, - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - - binding.bubble.background = - if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormal - else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddle - else backgroundNormal - - binding.bubbleStroke.background = - if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStroke - else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStroke - else backgroundStroke - - binding.time.text = - SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L) + profiles = profiles, + groups = groups + ).prepare() } - } inner class ServiceMessage( @@ -285,6 +183,7 @@ class MessagesHistoryAdapter constructor( message.action ?: return binding.message.text = VkUtils.getActionMessageText( + context = context, message = message, youPrefix = youPrefix, profiles = profiles, @@ -314,233 +213,6 @@ class MessagesHistoryAdapter constructor( } } - inner class AttachmentPhotosIncoming( - private val binding: ItemMessageAttachmentPhotosInBinding - ) : Holder(binding.root) { - - override fun bind(position: Int) { - val message = getItem(position) - val prevMessage = getOrNull(position - 1) - val nextMessage = getOrNull(position + 1) - - val messageUser = - if (message.isUser()) profiles[message.fromId] - else null - - val messageGroup = - if (message.isGroup()) groups[message.fromId] - else null - - MessagesManager.loadMessageAvatar( - message = message, - messageUser = messageUser, - messageGroup = messageGroup, - imageView = binding.avatar - ) - - if (!message.isPeerChat()) { - binding.avatar.isVisible = false - - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - } else { - binding.spacer.isVisible = - if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat() - else message.date - prevMessage.date >= 60 - - binding.avatar.visibility = - if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE - else if (nextMessage.date - message.date >= 60) View.VISIBLE - else View.INVISIBLE - } - - MessagesManager.loadPhotos( - context = context, - message = message, - photosContainer = binding.photosContainer - ) - - MessagesManager.setMessageText( - message = message, - textView = binding.text - ) - - binding.bubble.isVisible = binding.text.text.toString().isNotEmpty() - } - } - - inner class AttachmentPhotosOutgoing( - private val binding: ItemMessageAttachmentPhotosOutBinding - ) : Holder(binding.root) { - - override fun bind(position: Int) { - val message = getItem(position) - - MessagesManager.loadPhotos( - context = context, - message = message, - photosContainer = binding.photosContainer - ) - - } - } - - inner class AttachmentVideosIncoming( - private val binding: ItemMessageAttachmentVideosInBinding - ) : Holder(binding.root) { - - override fun bind(position: Int) { - val message = getItem(position) - val prevMessage = getOrNull(position - 1) - val nextMessage = getOrNull(position + 1) - - val messageUser = - if (message.isUser()) profiles[message.fromId] - else null - - val messageGroup = - if (message.isGroup()) groups[message.fromId] - else null - - MessagesManager.loadMessageAvatar( - message = message, - messageUser = messageUser, - messageGroup = messageGroup, - imageView = binding.avatar - ) - - if (!message.isPeerChat()) { - binding.avatar.isVisible = false - - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - } else { - binding.spacer.isVisible = - if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat() - else message.date - prevMessage.date >= 60 - - binding.avatar.visibility = - if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE - else if (nextMessage.date - message.date >= 60) View.VISIBLE - else View.INVISIBLE - } - - MessagesManager.loadVideos( - context = context, - message = message, - videosContainer = binding.videosContainer - ) - - MessagesManager.setMessageText( - message = message, - textView = binding.text - ) - - binding.bubble.isVisible = binding.text.text.toString().isNotEmpty() - } - - } - - inner class AttachmentStickerOutgoing( - private val binding: ItemMessageAttachmentStickerOutBinding - ) : Holder(binding.root) { - - override fun bind(position: Int) { - val message = getItem(position) - val prevMessage = getOrNull(position - 1) - val nextMessage = getOrNull(position + 1) - - if (!message.isPeerChat()) { - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - } else { - binding.spacer.isVisible = - if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat() - else message.date - prevMessage.date >= 60 - } - - val sticker = message.attachments?.get(0) as? VkSticker ?: return - val url = sticker.urlForSize(352)!! - - binding.photo.layoutParams.also { - it.width = 352 - it.height = 352 - } - - binding.photo.load(url) { crossfade(150) } - } - } - - inner class AttachmentStickerIncoming( - private val binding: ItemMessageAttachmentStickerInBinding - ) : Holder(binding.root) { - - override fun bind(position: Int) { - val message = getItem(position) - val prevMessage = getOrNull(position - 1) - val nextMessage = getOrNull(position + 1) - - if (!message.isPeerChat()) { - binding.avatar.isVisible = false - - binding.spacer.isVisible = - !(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) - } else { - binding.spacer.isVisible = - if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat() - else message.date - prevMessage.date >= 60 - - binding.avatar.visibility = - if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE - else if (nextMessage.date - message.date >= 60) View.VISIBLE - else View.INVISIBLE - } - - val messageUser: VkUser? = if (message.isUser()) { - profiles[message.fromId] - } else null - - val messageGroup: VkGroup? = if (message.isGroup()) { - groups[message.fromId] - } else null - - val avatar = when { - message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200 - message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200 - else -> null - } - - binding.avatar.load(avatar) { crossfade(100) } - - val title = when { - message.isUser() && messageUser != null -> messageUser.fullName - message.isGroup() && messageGroup != null -> messageGroup.name - else -> null - } - - binding.avatar.setOnLongClickListener { - Toast.makeText(context, title, Toast.LENGTH_SHORT).apply { - setGravity( - Gravity.START or Gravity.BOTTOM, - 0, - -50 - ) - }.show() - true - } - - val sticker = message.attachments?.get(0) as? VkSticker ?: return - val url = sticker.urlForSize(352)!! - - binding.photo.layoutParams.also { - it.width = 352 - it.height = 352 - } - - binding.photo.load(url) { crossfade(150) } - } - } - private val actualSize get() = values.size override fun getItemCount(): Int { @@ -556,13 +228,6 @@ class MessagesHistoryAdapter constructor( private const val OUTGOING = 4 - private const val ATTACHMENT_PHOTOS_IN = 101 - private const val ATTACHMENT_PHOTOS_OUT = 102 - private const val ATTACHMENT_VIDEOS_IN = 111 - private const val ATTACHMENT_VIDEOS_OUT = 112 - private const val ATTACHMENT_STICKER_IN = 121 - private const val ATTACHMENT_STICKER_OUT = 122 - private val COMPARATOR = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: VkMessage, diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryFragment.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryFragment.kt index 6809ee16..5c9be18b 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryFragment.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryFragment.kt @@ -3,6 +3,7 @@ package com.meloda.fast.screens.messages import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle +import android.text.TextUtils import android.view.View import android.viewbinding.library.fragment.viewBinding import androidx.core.view.isVisible @@ -76,6 +77,9 @@ class MessagesHistoryFragment : else -> null } + binding.title.ellipsize = TextUtils.TruncateAt.END + binding.status.ellipsize = TextUtils.TruncateAt.END + binding.title.text = title ?: "..." val status = when { diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryViewModel.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryViewModel.kt index ce5dad2b..2f75959a 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryViewModel.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesHistoryViewModel.kt @@ -64,7 +64,7 @@ class MessagesHistoryViewModel @Inject constructor( response.conversations?.let { baseConversations -> baseConversations.forEach { baseConversation -> baseConversation.asVkConversation( - messages[baseConversation.lastMessageId] + messages[baseConversation.last_message_id] ).let { conversation -> conversations[conversation.id] = conversation } } } diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt deleted file mode 100644 index abac6b88..00000000 --- a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt +++ /dev/null @@ -1,179 +0,0 @@ -package com.meloda.fast.screens.messages - -import android.content.Context -import android.content.res.ColorStateList -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.Space -import android.widget.TextView -import androidx.appcompat.widget.AppCompatImageView -import androidx.appcompat.widget.LinearLayoutCompat -import androidx.core.content.ContextCompat -import androidx.core.view.isNotEmpty -import androidx.core.view.setPadding -import coil.load -import com.google.android.material.imageview.ShapeableImageView -import com.meloda.fast.R -import com.meloda.fast.api.VkUtils -import com.meloda.fast.api.model.VkGroup -import com.meloda.fast.api.model.VkMessage -import com.meloda.fast.api.model.VkUser -import com.meloda.fast.api.model.attachments.VkPhoto -import com.meloda.fast.api.model.attachments.VkVideo -import com.meloda.fast.common.AppGlobal -import com.meloda.fast.util.AndroidUtils -import com.meloda.fast.widget.BoundedFrameLayout -import com.meloda.fast.widget.BoundedLinearLayout -import kotlin.math.roundToInt - -object MessagesManager { - - fun setRootMaxWidth( - layout: View - ) { - val maxWidth = (AppGlobal.screenWidth * 0.7).roundToInt() - - if (layout is BoundedFrameLayout) { - layout.maxWidth = maxWidth - } else if (layout is BoundedLinearLayout) { - layout.maxWidth = maxWidth - } - } - - fun loadPhotos( - context: Context, - message: VkMessage, - photosContainer: LinearLayoutCompat - ) { - photosContainer.removeAllViews() - - message.attachments?.let { attachments -> - val photos = attachments.map { it as VkPhoto } - - photos.forEach { photo -> - val size = photo.sizeOfType('m') ?: return - - val newPhoto = ShapeableImageView(context).also { - it.layoutParams = LinearLayoutCompat.LayoutParams( - AndroidUtils.px(size.width).roundToInt(), - AndroidUtils.px(size.height).roundToInt() - ) - it.shapeAppearanceModel = - it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) } - it.scaleType = ImageView.ScaleType.CENTER_CROP - } - - val spacer = Space(context).also { - it.layoutParams = LinearLayoutCompat.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - AndroidUtils.px(5).roundToInt() - ) - } - - if (photosContainer.isNotEmpty()) - photosContainer.addView(spacer) - - photosContainer.addView(newPhoto) - - newPhoto.load(size.url) { crossfade(100) } - } - } - } - - fun loadVideos( - context: Context, - message: VkMessage, - videosContainer: LinearLayoutCompat - ) { - videosContainer.removeAllViews() - val playColor = ContextCompat.getColor(context, R.color.a3_700) - val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200) - - message.attachments?.let { attachments -> - val photos = attachments.map { it as VkVideo } - - photos.forEach { video -> - val size = video.images[1] ?: return - - val layout = FrameLayout(context).apply { - layoutParams = LinearLayoutCompat.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - - val newPhoto = ShapeableImageView(context).apply { - layoutParams = FrameLayout.LayoutParams( - AndroidUtils.px(size.width).roundToInt(), - AndroidUtils.px(size.height).roundToInt() - ) - - shapeAppearanceModel = - shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) } - scaleType = ImageView.ScaleType.CENTER_CROP - } - - val play = AppCompatImageView(context).apply { - layoutParams = FrameLayout.LayoutParams( - AndroidUtils.px(50).roundToInt(), - AndroidUtils.px(50).roundToInt() - ).also { - it.gravity = Gravity.CENTER - } - - backgroundTintList = ColorStateList.valueOf(playBackgroundColor) - imageTintList = ColorStateList.valueOf(playColor) - - setBackgroundResource(R.drawable.ic_play_button_circle_background) - setImageResource(R.drawable.ic_round_play_arrow_24) - - setPadding(12) - } - - layout.addView(newPhoto) - layout.addView(play) - - val spacer = Space(context).apply { - layoutParams = LinearLayoutCompat.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - AndroidUtils.px(5).roundToInt() - ) - } - - if (videosContainer.isNotEmpty()) - videosContainer.addView(spacer) - - videosContainer.addView(layout) - - newPhoto.load(size.url) { crossfade(100) } - } - } - } - - fun loadMessageAvatar( - message: VkMessage, - messageUser: VkUser?, - messageGroup: VkGroup?, - imageView: ImageView - ) { - val avatar = when { - message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200 - message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200 - else -> null - } - - imageView.load(avatar) { crossfade(100) } - } - - fun setMessageText( - message: VkMessage, - textView: TextView - ) { - textView.text = VkUtils.prepareMessageText(message.text) - } - - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesPreparator.kt b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesPreparator.kt new file mode 100644 index 00000000..88f3b77d --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesPreparator.kt @@ -0,0 +1,206 @@ +package com.meloda.fast.screens.messages + +import android.content.Context +import android.util.Log +import android.view.View +import android.widget.ImageView +import android.widget.Space +import android.widget.TextView +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.core.content.ContextCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.core.view.setPadding +import coil.load +import com.meloda.fast.R +import com.meloda.fast.api.VkUtils +import com.meloda.fast.api.model.VkConversation +import com.meloda.fast.api.model.VkGroup +import com.meloda.fast.api.model.VkMessage +import com.meloda.fast.api.model.VkUser +import com.meloda.fast.api.model.attachments.VkSticker +import com.meloda.fast.common.AppGlobal +import com.meloda.fast.util.AndroidUtils +import com.meloda.fast.widget.BoundedLinearLayout +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.roundToInt + + +class MessagesPreparator constructor( + private val context: Context, + + private val conversation: VkConversation, + private val message: VkMessage, + private val prevMessage: VkMessage? = null, + private val nextMessage: VkMessage? = null, + + private val bubble: BoundedLinearLayout? = null, + private val bubbleStroke: View? = null, + private val text: TextView? = null, + private val avatar: ImageView? = null, + private val title: TextView? = null, + private val spacer: Space? = null, + private val unread: ImageView? = null, + private val time: TextView? = null, + private val attachmentContainer: LinearLayoutCompat? = null, + private val attachmentSpacer: Space? = null, + + private val profiles: Map, + private val groups: Map +) { + + init { + val maxWidth = (AppGlobal.screenWidth * 0.7).roundToInt() + + if (bubble != null) bubble.maxWidth = maxWidth + } + + private val backgroundNormalIn = + ContextCompat.getDrawable(context, R.drawable.ic_message_in_background) + private val backgroundMiddleIn = + ContextCompat.getDrawable(context, R.drawable.ic_message_in_background_middle) + + private val backgroundNormalOut = + ContextCompat.getDrawable(context, R.drawable.ic_message_out_background) + private val backgroundMiddleOut = + ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle) + private val backgroundStrokeOut = + ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke) + private val backgroundMiddleStrokeOut = + ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke) + + fun prepare() { + val messageUser: VkUser? = if (message.isUser()) { + profiles[message.fromId] + } else null + + val messageGroup: VkGroup? = if (message.isGroup()) { + groups[message.fromId] + } else null + + if (unread != null) { + unread.isVisible = message.isRead(conversation) + } + + if (bubble != null && time != null) { + bubble.setOnClickListener { time.isVisible = !time.isVisible } + } + + if (attachmentContainer != null) { + if (message.attachments.isNullOrEmpty()) { + attachmentContainer.isVisible = false + attachmentContainer.removeAllViews() + } else { + attachmentContainer.isVisible = true + AttachmentInflater( + context = context, + container = attachmentContainer, + message = message, + groups = groups, + profiles = profiles + ).inflate() + } + } + + if (bubble != null) { + val padding = + AndroidUtils.px(if (!message.attachments.isNullOrEmpty()) 4 else 15).roundToInt() + + bubble.setPadding(padding) + + + // TODO: 9/23/2021 use external function + bubble.background = + if (!message.attachments.isNullOrEmpty() && message.attachments!![0] is VkSticker) null + else { + if (message.isOut) { + if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalOut + else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleOut + else backgroundNormalOut + } else { + if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalIn + else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleIn + else backgroundNormalIn + } + } + } + + // TODO: 9/23/2021 use external function + bubbleStroke?.background = + if (bubble?.background == null) null else { + if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStrokeOut + else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStrokeOut + else backgroundStrokeOut + } + + if (bubble != null && text != null) { + if (message.text == null) { + text.isVisible = false + bubble.isVisible = !message.attachments.isNullOrEmpty() + bubbleStroke?.isVisible = bubble.isVisible + } else { + text.isVisible = true + bubble.isVisible = true + bubbleStroke?.isVisible = true + text.text = VkUtils.prepareMessageText(message.text) + } + } + + if (avatar != null) { + val avatarUrl = when { + message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200 + message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200 + else -> null + } + + avatar.load(avatarUrl) { crossfade(100) } + } + + spacer?.isVisible = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message) + + if (message.isPeerChat()) { + + val fromDiffSender = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message) + val fiveMinAgo = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message) + + val change = (prevMessage?.date ?: 0) - message.date + + Log.d( + "Fast::MessagesPreparator", + "text: ${message.text}; prevText: ${prevMessage?.text}; time change: $change; fromDiffSender: $fromDiffSender; fiveMinAgo: $fiveMinAgo; " + ) + + title?.isVisible = fromDiffSender || fiveMinAgo + + avatar?.isInvisible = fromDiffSender && fiveMinAgo + } else { + title?.isVisible = false + avatar?.isVisible = false + } + + if (title != null) { + val titleString = when { + message.isUser() && messageUser != null -> messageUser.firstName + message.isGroup() && messageGroup != null -> messageGroup.name + else -> null + } + + title.text = titleString + title.measure(0, 0) + + if (bubble != null) { + if (title.isVisible) { + bubble.minimumWidth = title.measuredWidth + 60 + } else { + bubble.minimumWidth = 0 + } + } + } + + attachmentSpacer?.isVisible = + !message.attachments.isNullOrEmpty() && text?.isVisible == true + + time?.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L) + } +} diff --git a/app/src/main/kotlin/com/meloda/fast/util/AndroidUtils.kt b/app/src/main/kotlin/com/meloda/fast/util/AndroidUtils.kt index d7e36886..aab07505 100644 --- a/app/src/main/kotlin/com/meloda/fast/util/AndroidUtils.kt +++ b/app/src/main/kotlin/com/meloda/fast/util/AndroidUtils.kt @@ -73,4 +73,11 @@ object AndroidUtils { return color } + fun bytesToHumanReadableSize(bytes: Double) = when { + bytes >= 1 shl 30 -> "%.1f GB".format(bytes / (1 shl 30)) + bytes >= 1 shl 20 -> "%.1f MB".format(bytes / (1 shl 20)) + bytes >= 1 shl 10 -> "%.0f KB".format(bytes / (1 shl 10)) + else -> "$bytes B" + } + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/widget/RoundedCornerLayout.java b/app/src/main/kotlin/com/meloda/fast/widget/RoundedCornerLayout.java new file mode 100644 index 00000000..b97454e9 --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/widget/RoundedCornerLayout.java @@ -0,0 +1,79 @@ +package com.meloda.fast.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.widget.FrameLayout; + +public class RoundedCornerLayout extends FrameLayout { + private final static float CORNER_RADIUS = 40.0f; + + private Bitmap maskBitmap; + private Paint paint, maskPaint; + private float cornerRadius; + + public RoundedCornerLayout(Context context) { + super(context); + init(context, null, 0); + } + + public RoundedCornerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public RoundedCornerLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics); + + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + setWillNotDraw(false); + } + + @Override + public void draw(Canvas canvas) { + Bitmap offscreenBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + Canvas offscreenCanvas = new Canvas(offscreenBitmap); + + super.draw(offscreenCanvas); + + if (maskBitmap == null) { + maskBitmap = createMask(getWidth(), getHeight()); + } + + offscreenCanvas.drawBitmap(maskBitmap, 0f, 0f, maskPaint); + canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint); + } + + private Bitmap createMask(int width, int height) { + Bitmap mask = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); + Canvas canvas = new Canvas(mask); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(Color.WHITE); + + canvas.drawRect(0, 0, width, height, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + canvas.drawRoundRect(new RectF(0, 0, width, height), cornerRadius, cornerRadius, paint); + + return mask; + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/widget/RoundedFrameLayout.kt b/app/src/main/kotlin/com/meloda/fast/widget/RoundedFrameLayout.kt new file mode 100644 index 00000000..fe2ee3ba --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/widget/RoundedFrameLayout.kt @@ -0,0 +1,95 @@ +package com.meloda.fast.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Path +import android.graphics.RectF +import android.graphics.Region +import android.util.AttributeSet +import android.widget.FrameLayout +import com.meloda.fast.R + +class RoundedFrameLayout : FrameLayout { + /** + * The corners than can be changed + */ + private var topLeftCornerRadius = 0f + private var topRightCornerRadius = 0f + private var bottomLeftCornerRadius = 0f + private var bottomRightCornerRadius = 0f + + constructor(context: Context) : super(context) { + init(context, null, 0) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs, 0) + } + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) { + init(context, attrs, defStyleAttr) + } + + private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) { + val typedArray = context.obtainStyledAttributes( + attrs, + R.styleable.RoundedFrameLayout, 0, 0 + ) + + topLeftCornerRadius = + typedArray.getDimension(R.styleable.RoundedFrameLayout_topLeftCornerRadius, 0f) + topRightCornerRadius = + typedArray.getDimension(R.styleable.RoundedFrameLayout_topRightCornerRadius, 0f) + bottomLeftCornerRadius = + typedArray.getDimension(R.styleable.RoundedFrameLayout_bottomLeftCornerRadius, 0f) + bottomRightCornerRadius = + typedArray.getDimension(R.styleable.RoundedFrameLayout_bottomRightCornerRadius, 0f) + + typedArray.recycle() + setLayerType(LAYER_TYPE_HARDWARE, null) + } + + override fun dispatchDraw(canvas: Canvas) { + val count: Int = canvas.save() + val path = Path() + val cornerDimensions = floatArrayOf( + topLeftCornerRadius, topLeftCornerRadius, + topRightCornerRadius, topRightCornerRadius, + bottomRightCornerRadius, bottomRightCornerRadius, + bottomLeftCornerRadius, bottomLeftCornerRadius + ) + path.addRoundRect( + RectF(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat()), + cornerDimensions, + Path.Direction.CW + ) + canvas.clipPath(path, Region.Op.INTERSECT) + canvas.clipPath(path) + super.dispatchDraw(canvas) + canvas.restoreToCount(count) + } + + fun setTopLeftCornerRadius(topLeftCornerRadius: Float) { + this.topLeftCornerRadius = topLeftCornerRadius + invalidate() + } + + fun setTopRightCornerRadius(topRightCornerRadius: Float) { + this.topRightCornerRadius = topRightCornerRadius + invalidate() + } + + fun setBottomLeftCornerRadius(bottomLeftCornerRadius: Float) { + this.bottomLeftCornerRadius = bottomLeftCornerRadius + invalidate() + } + + fun setBottomRightCornerRadius(bottomRightCornerRadius: Float) { + this.bottomRightCornerRadius = bottomRightCornerRadius + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_attachment_story.xml b/app/src/main/res/drawable/ic_attachment_story.xml new file mode 100644 index 00000000..18f1e579 --- /dev/null +++ b/app/src/main/res/drawable/ic_attachment_story.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_messages_history.xml b/app/src/main/res/layout/fragment_messages_history.xml index a6637556..ef50f623 100644 --- a/app/src/main/res/layout/fragment_messages_history.xml +++ b/app/src/main/res/layout/fragment_messages_history.xml @@ -144,7 +144,7 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:singleLine="true" + android:maxLines="1" android:textColor="@color/n1_900" android:textSize="24sp" tools:text="@tools:sample/full_names" /> @@ -154,6 +154,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:alpha="0.7" + android:maxLines="1" android:textColor="@color/n1_900" tools:text="Online" /> diff --git a/app/src/main/res/layout/item_message_attachment_audio.xml b/app/src/main/res/layout/item_message_attachment_audio.xml new file mode 100644 index 00000000..39d34689 --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_audio.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_file.xml b/app/src/main/res/layout/item_message_attachment_file.xml new file mode 100644 index 00000000..74117d53 --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_file.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_link.xml b/app/src/main/res/layout/item_message_attachment_link.xml new file mode 100644 index 00000000..fab98353 --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_link.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_photos_in.xml b/app/src/main/res/layout/item_message_attachment_photos_in.xml deleted file mode 100644 index 24957ca9..00000000 --- a/app/src/main/res/layout/item_message_attachment_photos_in.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_photos_out.xml b/app/src/main/res/layout/item_message_attachment_photos_out.xml deleted file mode 100644 index f3f33124..00000000 --- a/app/src/main/res/layout/item_message_attachment_photos_out.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_sticker.xml b/app/src/main/res/layout/item_message_attachment_sticker.xml new file mode 100644 index 00000000..5cc4fe57 --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_sticker.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_sticker_in.xml b/app/src/main/res/layout/item_message_attachment_sticker_in.xml deleted file mode 100644 index 0866ccb4..00000000 --- a/app/src/main/res/layout/item_message_attachment_sticker_in.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_sticker_out.xml b/app/src/main/res/layout/item_message_attachment_sticker_out.xml deleted file mode 100644 index 53cd425e..00000000 --- a/app/src/main/res/layout/item_message_attachment_sticker_out.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_videos_in.xml b/app/src/main/res/layout/item_message_attachment_videos_in.xml deleted file mode 100644 index 1195e25b..00000000 --- a/app/src/main/res/layout/item_message_attachment_videos_in.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_attachment_wall_post.xml b/app/src/main/res/layout/item_message_attachment_wall_post.xml new file mode 100644 index 00000000..1df04137 --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_wall_post.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_in.xml b/app/src/main/res/layout/item_message_in.xml index 2cb64bf1..54f0835f 100644 --- a/app/src/main/res/layout/item_message_in.xml +++ b/app/src/main/res/layout/item_message_in.xml @@ -26,7 +26,8 @@ android:id="@+id/spacer" android:layout_width="match_parent" android:layout_height="10dp" - android:visibility="gone" /> + android:visibility="gone" + tools:visibility="visible" /> - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_out.xml b/app/src/main/res/layout/item_message_out.xml index 693e1116..8ac3215d 100644 --- a/app/src/main/res/layout/item_message_out.xml +++ b/app/src/main/res/layout/item_message_out.xml @@ -38,25 +38,38 @@ android:padding="1.5dp" tools:ignore="UselessParent"> - + android:background="@drawable/ic_message_out_background" + android:orientation="vertical" + android:padding="15dp"> - - + + + + + + tools:text="12:00" + tools:visibility="visible" /> - - - \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index e8324453..c9703384 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -25,6 +25,13 @@ + + + + + + + diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml new file mode 100644 index 00000000..c278216f --- /dev/null +++ b/app/src/main/res/values/plurals.xml @@ -0,0 +1,32 @@ + + + + + @string/message_attachments_photo_one + @string/message_attachments_photo_few + @string/message_attachments_photo_many + @string/message_attachments_photo_other + + + + @string/message_attachments_video_one + @string/message_attachments_video_few + @string/message_attachments_video_many + @string/message_attachments_video_other + + + + @string/message_attachments_audio_one + @string/message_attachments_audio_few + @string/message_attachments_audio_many + @string/message_attachments_audio_other + + + + @string/message_attachments_files_one + @string/message_attachments_files_few + @string/message_attachments_files_many + @string/message_attachments_files_other + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c1dccd1..378c5604 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,7 +43,7 @@ W D Now - Start typing here... + Start typing here… Input login Input password Input code @@ -51,4 +51,62 @@ Unknown error occurred Authorization failed + + %s created «%s» + %s renamed chat to «%s» + %s updated the chat photo + %s deleted the chat photo + %s left the chat + %s kicked %s + %s returned to chat + %s invited %s + %s joined the chat via link + %s joined the call via link + %s pinned message + %s unpinned message + %s took a screenshot + %s changed chat theme + + + Photo + %d photos + %d photos + %d photos + + Video + %d videos + %d videos + %d videos + + Audio + %d audios + %d audios + %d audios + + File + %d files + %d files + %d files + + + Voice message + Link + Mini App + Sticker + Gift + Post + Graffiti + Poll + Wall comment + Call + Current call + Event + Curator + + %d bytes + + Community post + User post + Post + Story