From 56fb93d2e47d320bc5706d1f45727874568902fb Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Tue, 21 Sep 2021 13:39:34 +0300 Subject: [PATCH] code saving --- .../kotlin/com/meloda/fast/api/VKConstants.kt | 1 - .../kotlin/com/meloda/fast/api/VkUtils.kt | 8 +- .../meloda/fast/api/model/VkConversation.kt | 12 +- .../com/meloda/fast/api/model/VkMessage.kt | 14 +- .../api/model/attachments/VkAttachment.kt | 4 +- .../fast/api/model/attachments/VkAudio.kt | 7 +- .../fast/api/model/attachments/VkCall.kt | 8 +- .../fast/api/model/attachments/VkFile.kt | 7 +- .../fast/api/model/attachments/VkGift.kt | 7 +- .../fast/api/model/attachments/VkGraffiti.kt | 7 +- .../fast/api/model/attachments/VkGroupCall.kt | 7 +- .../fast/api/model/attachments/VkLink.kt | 7 +- .../fast/api/model/attachments/VkMiniApp.kt | 7 +- .../fast/api/model/attachments/VkPhoto.kt | 4 + .../fast/api/model/attachments/VkPoll.kt | 7 +- .../fast/api/model/attachments/VkSticker.kt | 4 + .../fast/api/model/attachments/VkVideo.kt | 17 ++- .../api/model/attachments/VkVoiceMessage.kt | 7 +- .../fast/api/model/attachments/VkWall.kt | 7 +- .../fast/api/model/attachments/VkWallReply.kt | 7 +- .../fast/api/model/base/BaseVkConversation.kt | 9 +- .../api/model/base/attachments/BaseVkVideo.kt | 8 ++ .../fast/api/network/ResultCallFactory.kt | 22 ++++ .../network/datasource/MessagesDataSource.kt | 5 + .../fast/base/viewmodel/BaseViewModel.kt | 50 ++++--- .../com/meloda/fast/base/viewmodel/Events.kt | 2 + .../com/meloda/fast/database/AppDatabase.kt | 6 +- .../com/meloda/fast/database/Converters.kt | 95 ++++++++++++++ .../meloda/fast/database/dao/MessagesDao.kt | 19 +++ .../conversations/ConversationsFragment.kt | 3 + .../fast/screens/login/LoginFragment.kt | 24 ++-- .../fast/screens/login/LoginViewModel.kt | 31 +---- .../messages/MessagesHistoryAdapter.kt | 122 +++++++++++++++++- .../messages/MessagesHistoryViewModel.kt | 2 + .../fast/screens/messages/MessagesManager.kt | 85 +++++++++++- .../ic_play_button_circle_background.xml | 7 + .../res/drawable/ic_round_play_arrow_24.xml | 9 ++ app/src/main/res/drawable/test.png | Bin 0 -> 44277 bytes .../res/layout/fragment_conversations.xml | 2 +- .../item_message_attachment_photos_in.xml | 25 ++-- .../item_message_attachment_videos_in.xml | 99 ++++++++++++++ app/src/main/res/layout/item_message_out.xml | 12 ++ app/src/main/res/values/colors.xml | 1 + 43 files changed, 665 insertions(+), 122 deletions(-) create mode 100644 app/src/main/kotlin/com/meloda/fast/database/Converters.kt create mode 100644 app/src/main/res/drawable/ic_play_button_circle_background.xml create mode 100644 app/src/main/res/drawable/ic_round_play_arrow_24.xml create mode 100644 app/src/main/res/drawable/test.png create mode 100644 app/src/main/res/layout/item_message_attachment_videos_in.xml diff --git a/app/src/main/kotlin/com/meloda/fast/api/VKConstants.kt b/app/src/main/kotlin/com/meloda/fast/api/VKConstants.kt index c244a0cd..cc2567ba 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/VKConstants.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/VKConstants.kt @@ -7,7 +7,6 @@ object VKConstants { const val USER_FIELDS = "photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info" - const val API_VERSION = "5.132" const val VK_APP_ID = "2274003" const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH" diff --git a/app/src/main/kotlin/com/meloda/fast/api/VkUtils.kt b/app/src/main/kotlin/com/meloda/fast/api/VkUtils.kt index a066d660..a3506fb3 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/VkUtils.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/VkUtils.kt @@ -27,7 +27,9 @@ object VkUtils { return throwable.error == VkErrors.NEED_CAPTCHA } - fun prepareMessageText(text: String): String { + fun prepareMessageText(text: String?): String? { + if (text == null) return null + return text .replace("\n", " ") .replace("&", "&") @@ -58,9 +60,7 @@ object VkUtils { } BaseVkAttachmentItem.AttachmentType.VIDEO -> { val video = baseAttachment.video ?: continue - attachments += VkVideo( - link = video.player - ) + attachments += video.asVkVideo() } BaseVkAttachmentItem.AttachmentType.AUDIO -> { val audio = baseAttachment.audio ?: continue diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/VkConversation.kt b/app/src/main/kotlin/com/meloda/fast/api/model/VkConversation.kt index d017aed8..42d221e6 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/VkConversation.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/VkConversation.kt @@ -1,8 +1,8 @@ package com.meloda.fast.api.model import android.os.Parcelable +import androidx.room.Embedded import androidx.room.Entity -import androidx.room.Ignore import androidx.room.PrimaryKey import kotlinx.parcelize.Parcelize @@ -24,10 +24,14 @@ data class VkConversation( val lastMessageId: Int, val unreadCount: Int?, val membersCount: Int?, - val isPinned: Boolean -) : Parcelable { - @Ignore + val isPinned: Boolean, + + @Embedded(prefix = "pinnedMessage_") + var pinnedMessage: VkMessage? = null, + + @Embedded(prefix = "lastMessage_") var lastMessage: VkMessage? = null +) : Parcelable { fun isChat() = type == "chat" fun isUser() = type == "user" diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/VkMessage.kt b/app/src/main/kotlin/com/meloda/fast/api/model/VkMessage.kt index 328bc370..b7e38ecb 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/VkMessage.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/VkMessage.kt @@ -2,10 +2,8 @@ package com.meloda.fast.api.model import android.os.Parcelable import androidx.room.Entity -import androidx.room.Ignore import androidx.room.PrimaryKey import com.meloda.fast.api.model.attachments.VkAttachment -import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Entity(tableName = "messages") @@ -25,16 +23,10 @@ data class VkMessage( val actionConversationMessageId: Int? = null, val actionMessage: String? = null, val geoType: String? = null, - val important: Boolean = false -) : Parcelable { - - @IgnoredOnParcel - @Ignore - var forwards: List? = null - - @IgnoredOnParcel - @Ignore + val important: Boolean = false, + var forwards: List? = null, var attachments: List? = null +) : Parcelable { fun isPeerChat() = peerId > 2_000_000_000 diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAttachment.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAttachment.kt index 61b6547d..26d3bc1a 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAttachment.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkAttachment.kt @@ -1,5 +1,7 @@ package com.meloda.fast.api.model.attachments import android.os.Parcelable +import kotlinx.parcelize.Parcelize -abstract class VkAttachment : Parcelable \ No newline at end of file +@Parcelize +open class VkAttachment : Parcelable \ 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 cd6870d4..3a23f37a 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 @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkAudio( val link: String -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCall.kt index d8ef9773..43c54a00 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkCall.kt @@ -1,8 +1,14 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkCall( val initiatorId: Int -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name + +} \ 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 b5ba69d4..a0fc7ad9 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 @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkFile( val link: String -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGift.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGift.kt index 30f1aeb4..2fea4243 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGift.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGift.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkGift( val link: String -) : VkAttachment() +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGraffiti.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGraffiti.kt index 4c71c0b6..a9b2ca9b 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGraffiti.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGraffiti.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkGraffiti( val link: String -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGroupCall.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGroupCall.kt index 32bfbd43..562cf467 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGroupCall.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkGroupCall.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkGroupCall( val initiatorId: Int -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file 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 1d9142cc..55729b31 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 @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkLink( val link: String -) : VkAttachment() +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkMiniApp.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkMiniApp.kt index 499517cb..8e6e2860 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkMiniApp.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkMiniApp.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkMiniApp( val link: String -) : VkAttachment() +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file 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 e5fdc2ea..eeab6ea5 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,7 @@ package com.meloda.fast.api.model.attachments import com.meloda.fast.api.model.base.attachments.Size +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize @@ -16,6 +17,9 @@ data class VkPhoto( val userId: Int? ) : VkAttachment() { + @IgnoredOnParcel + val className: String = this::class.java.name + fun sizeOfType(type: Char): Size? { for (size in sizes) { if (size.type == type.toString()) diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPoll.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPoll.kt index 2220f146..25c17860 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPoll.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkPoll.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkPoll( val id: Int -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file 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 c4f18664..d906b657 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 @@ -2,6 +2,7 @@ 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 @Parcelize @@ -12,6 +13,9 @@ data class VkSticker( val backgroundImages: List ) : VkAttachment() { + @IgnoredOnParcel + val className: String = this::class.java.name + fun urlForSize(@StickerSize 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/VkVideo.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVideo.kt index 874f7b3d..c3b1e40f 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 @@ -1,8 +1,21 @@ package com.meloda.fast.api.model.attachments +import com.meloda.fast.api.model.base.attachments.BaseVkVideo +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkVideo( - val link: String -) : VkAttachment() \ No newline at end of file + val id: Int, + val images: List, + val firstFrames: List +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name + + fun imageForWidth(width: Int): BaseVkVideo.Image? { + return images.find { it.width == width } + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVoiceMessage.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVoiceMessage.kt index c239755c..5ead3805 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVoiceMessage.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkVoiceMessage.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkVoiceMessage( val link: String -) : VkAttachment() +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file 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 63336f53..d96db976 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,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkWall( val id: Int -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWallReply.kt b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWallReply.kt index 24620076..dff2966b 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWallReply.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/model/attachments/VkWallReply.kt @@ -1,8 +1,13 @@ package com.meloda.fast.api.model.attachments +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class VkWallReply( val id: Int -) : VkAttachment() \ No newline at end of file +) : VkAttachment() { + + @IgnoredOnParcel + val className: String = this::class.java.name +} \ No newline at end of file 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 be2f3380..91e54dd9 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 @@ -54,7 +54,10 @@ data class BaseVkConversation( membersCount = chatSettings?.membersCount, ownerId = chatSettings?.ownerId, isPinned = sortId.majorId > 0 - ).apply { this.lastMessage = lastMessage } + ).apply { + this.lastMessage = lastMessage + this.pinnedMessage = chatSettings?.pinnedMessage?.asVkMessage() + } @Parcelize data class Peer( @@ -111,7 +114,9 @@ data class BaseVkConversation( val isDisappearing: Boolean, @SerializedName("is_service") val isService: Boolean, - val theme: String + val theme: String?, + @SerializedName("pinned_message") + val pinnedMessage: BaseVkMessage? ) : Parcelable { @Parcelize 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 707193dd..e827e67d 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 @@ -2,8 +2,10 @@ 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, @@ -53,6 +55,12 @@ data class BaseVkVideo( //ads ) : BaseVkAttachment() { + fun asVkVideo() = VkVideo( + id = id, + images = image, + firstFrames = firstFrame + ) + @Parcelize data class Image( val height: Int, 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 e6afbc0b..5f4314d4 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 @@ -1,9 +1,11 @@ package com.meloda.fast.api.network +import com.meloda.fast.api.VKException import com.meloda.fast.api.base.ApiResponse import okhttp3.Request import okio.IOException import okio.Timeout +import org.json.JSONObject import retrofit2.* import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -86,6 +88,9 @@ internal class ResultCall(proxy: Call) : CallDelegate>(proxy) } } else Answer.Error(IOException(response.errorBody()?.string() ?: "")) + if (result is Answer.Error) if (checkErrors(call, result)) return + + callback.onResponse(proxy, Response.success(result)) } @@ -95,6 +100,23 @@ internal class ResultCall(proxy: Call) : CallDelegate>(proxy) Response.success(Answer.Error(throwable = error)) ) } + + private fun checkErrors(call: Call, result: Answer.Error): Boolean { + val json = JSONObject(result.throwable.message ?: "{}") + + return if (json.has("error")) { + val error = json.optString("error", "") + val description = json.optString("error_description", "") + + val exception = VKException( + error = error, + description = description, + ).also { it.json = json } + + onFailure(call, exception) + true + } else false + } } override fun timeout(): Timeout { diff --git a/app/src/main/kotlin/com/meloda/fast/api/network/datasource/MessagesDataSource.kt b/app/src/main/kotlin/com/meloda/fast/api/network/datasource/MessagesDataSource.kt index 1a871c57..5995d515 100644 --- a/app/src/main/kotlin/com/meloda/fast/api/network/datasource/MessagesDataSource.kt +++ b/app/src/main/kotlin/com/meloda/fast/api/network/datasource/MessagesDataSource.kt @@ -1,5 +1,6 @@ package com.meloda.fast.api.network.datasource +import com.meloda.fast.api.model.VkMessage import com.meloda.fast.api.model.request.MessagesGetHistoryRequest import com.meloda.fast.api.model.request.MessagesGetLongPollServerRequest import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest @@ -25,4 +26,8 @@ class MessagesDataSource @Inject constructor( suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) = repo.getLongPollServer(params.map) + suspend fun storeMessages(messages: List) = dao.insert(messages) + + suspend fun getCachedMessages(peerId: Int) = dao.getByPeerId(peerId) + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/base/viewmodel/BaseViewModel.kt b/app/src/main/kotlin/com/meloda/fast/base/viewmodel/BaseViewModel.kt index b47feff0..4892ca01 100644 --- a/app/src/main/kotlin/com/meloda/fast/base/viewmodel/BaseViewModel.kt +++ b/app/src/main/kotlin/com/meloda/fast/base/viewmodel/BaseViewModel.kt @@ -1,6 +1,5 @@ package com.meloda.fast.base.viewmodel -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.meloda.fast.api.VKException @@ -14,6 +13,8 @@ import kotlinx.coroutines.launch abstract class BaseViewModel : ViewModel() { + var unknownErrorDefaultText: String = "" + protected val tasksEventChannel = Channel() val tasksEvent = tasksEventChannel.receiveAsFlow() @@ -30,6 +31,12 @@ abstract class BaseViewModel : ViewModel() { is Answer.Error -> { checkErrors(response.throwable) onError?.invoke(response.throwable) + ?: sendEvent( + ErrorEvent( + response.throwable.message + ?: unknownErrorDefaultText + ) + ) } } }.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } } @@ -37,31 +44,32 @@ abstract class BaseViewModel : ViewModel() { protected suspend fun sendEvent(event: T) = tasksEventChannel.send(event) private suspend fun checkErrors(throwable: Throwable) { - if (throwable is ApiError) { - when (throwable.errorCode) { - VkErrorCodes.USER_AUTHORIZATION_FAILED -> { - sendEvent(IllegalTokenEvent) - return + when (throwable) { + is ApiError -> { + when (throwable.errorCode) { + VkErrorCodes.USER_AUTHORIZATION_FAILED -> { + sendEvent(IllegalTokenEvent) + } } } - } else if (throwable is VKException) { - when (throwable.error) { - VkErrors.NEED_CAPTCHA -> { - throwable.captcha = - (throwable.json?.optString("captcha_sid") - ?: "") to (throwable.json?.optString("captcha_img") ?: "") - return + is VKException -> { + when (throwable.error) { + VkErrors.NEED_CAPTCHA -> { + val json = throwable.json ?: return + sendEvent( + CaptchaEvent( + sid = json.optString("captcha_sid"), + image = json.optString("captcha_img") + ) + ) + } + VkErrors.NEED_VALIDATION -> { + val json = throwable.json ?: return + sendEvent(ValidationEvent(sid = json.optString("validation_sid"))) + } } - VkErrors.NEED_VALIDATION -> { - throwable.validationSid = throwable.json?.optString("validation_sid") - return - } - - } } - - sendEvent(ShowDialogInfoEvent(null, Log.getStackTraceString(throwable))) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/meloda/fast/base/viewmodel/Events.kt b/app/src/main/kotlin/com/meloda/fast/base/viewmodel/Events.kt index a24c4e43..0bac43e5 100644 --- a/app/src/main/kotlin/com/meloda/fast/base/viewmodel/Events.kt +++ b/app/src/main/kotlin/com/meloda/fast/base/viewmodel/Events.kt @@ -10,6 +10,8 @@ data class ShowDialogInfoEvent( data class ErrorEvent(val errorText: String) : VKEvent() object IllegalTokenEvent : VKEvent() +data class CaptchaEvent(val sid: String, val image: String) : VKEvent() +data class ValidationEvent(val sid: String) : VKEvent() object StartProgressEvent : VKEvent() object StopProgressEvent : VKEvent() diff --git a/app/src/main/kotlin/com/meloda/fast/database/AppDatabase.kt b/app/src/main/kotlin/com/meloda/fast/database/AppDatabase.kt index 86616292..eb0a7ea4 100644 --- a/app/src/main/kotlin/com/meloda/fast/database/AppDatabase.kt +++ b/app/src/main/kotlin/com/meloda/fast/database/AppDatabase.kt @@ -2,6 +2,7 @@ package com.meloda.fast.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.TypeConverters import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.VkGroup import com.meloda.fast.api.model.VkMessage @@ -18,9 +19,10 @@ import com.meloda.fast.database.dao.UsersDao VkUser::class, VkGroup::class ], - version = 18, - exportSchema = false + version = 24, + exportSchema = false, ) +@TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun conversationsDao(): ConversationsDao diff --git a/app/src/main/kotlin/com/meloda/fast/database/Converters.kt b/app/src/main/kotlin/com/meloda/fast/database/Converters.kt new file mode 100644 index 00000000..e066617a --- /dev/null +++ b/app/src/main/kotlin/com/meloda/fast/database/Converters.kt @@ -0,0 +1,95 @@ +package com.meloda.fast.database + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.meloda.fast.api.model.VkMessage +import com.meloda.fast.api.model.attachments.VkAttachment +import org.json.JSONObject +import java.util.stream.Collectors + +class Converters { + + @TypeConverter + fun fromListVkMessageToString(messages: List?): String? { + if (messages == null) return null + + val string = + messages.map { fromVkMessageToString(it)!! }.stream() + .collect(Collectors.joining("fastkruta228355")) + + return string + } + + @TypeConverter + fun fromStringToListVkMessage(string: String?): List? { + if (string == null) return null + + if (string.contains("fastkruta228355")) { + val messages = + string.split("fastkruta228355").map { fromStringToVkMessage(it)!! } + return messages + } + + + val message = fromStringToVkMessage(string)!! + + return listOf(message) + } + + @TypeConverter + fun fromVkMessageToString(message: VkMessage?): String? { + if (message == null) return null + + return Gson().toJson(message) + } + + @TypeConverter + fun fromStringToVkMessage(string: String?): VkMessage? { + if (string == null) return null + + return Gson().fromJson(string, VkMessage::class.java) + } + + @TypeConverter + fun fromListVkAttachmentToString(attachments: List?): String? { + if (attachments == null) return null + + val string = + attachments.map { fromVkAttachmentToString(it)!! }.stream() + .collect(Collectors.joining("fastkruta228355")) + + return string + } + + @TypeConverter + fun fromStringToListVkAttachment(string: String?): List? { + if (string == null) return null + + if (string.contains("fastkruta228355")) { + val attachments = + string.split("fastkruta228355").map { fromStringToVkAttachment(it)!! } + return attachments + } + + + val attachment = fromStringToVkAttachment(string)!! + + return listOf(attachment) + } + + @TypeConverter + fun fromVkAttachmentToString(attachment: VkAttachment?): String? { + if (attachment == null) return null + + return Gson().toJson(attachment) + } + + @TypeConverter + fun fromStringToVkAttachment(string: String?): VkAttachment? { + if (string == null) return null + + val className = JSONObject(string).optString("className") + + return Gson().fromJson(string, Class.forName(className)) as VkAttachment? + } +} diff --git a/app/src/main/kotlin/com/meloda/fast/database/dao/MessagesDao.kt b/app/src/main/kotlin/com/meloda/fast/database/dao/MessagesDao.kt index 9a4ea0cc..98fc7f94 100644 --- a/app/src/main/kotlin/com/meloda/fast/database/dao/MessagesDao.kt +++ b/app/src/main/kotlin/com/meloda/fast/database/dao/MessagesDao.kt @@ -1,7 +1,26 @@ package com.meloda.fast.database.dao import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.meloda.fast.api.model.VkMessage @Dao interface MessagesDao { + + @Query("SELECT * FROM messages") + suspend fun getAll(): List + + @Query("SELECT * FROM messages WHERE id = :id") + suspend fun getById(id: Int): VkMessage? + + @Query("SELECT * FROM messages WHERE peerId = :peerId") + suspend fun getByPeerId(peerId: Int): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(values: List) + + suspend fun insert(values: Array) = insert(values.toList()) + } \ No newline at end of file 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 4c97e5b5..095cc49a 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 @@ -3,6 +3,7 @@ package com.meloda.fast.screens.conversations import android.os.Bundle import android.view.View import android.viewbinding.library.fragment.viewBinding +import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.viewModels @@ -82,6 +83,8 @@ class ConversationsFragment : return } + binding.toolbar.overflowIcon = ContextCompat.getDrawable(requireContext(), R.drawable.test) + viewModel.loadProfileUser() viewModel.loadConversations() } diff --git a/app/src/main/kotlin/com/meloda/fast/screens/login/LoginFragment.kt b/app/src/main/kotlin/com/meloda/fast/screens/login/LoginFragment.kt index cc2be0ae..7893cec7 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/login/LoginFragment.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/login/LoginFragment.kt @@ -10,7 +10,6 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener -import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -21,9 +20,7 @@ import com.google.android.material.textfield.TextInputLayout import com.meloda.fast.BuildConfig import com.meloda.fast.R 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.base.viewmodel.* import com.meloda.fast.databinding.DialogCaptchaBinding import com.meloda.fast.databinding.DialogValidationBinding import com.meloda.fast.databinding.FragmentLoginBinding @@ -60,10 +57,6 @@ class LoginFragment : BaseViewModelFragment(R.layout.fragment_lo prepareViews() binding.loginInput.clearFocus() - - setFragmentResultListener("validation") { _, bundle -> - lifecycleScope.launch { viewModel.getValidatedData(bundle) } - } } override fun onEvent(event: VKEvent) { @@ -71,15 +64,13 @@ class LoginFragment : BaseViewModelFragment(R.layout.fragment_lo when (event) { is ShowError -> showErrorSnackbar(event.errorDescription) - is CaptchaRequired -> showCaptchaDialog(event.captcha.first, event.captcha.second) - - CodeSent -> showValidationDialog() - - is ValidationRequired -> showValidationRequired() + is CaptchaEvent -> showCaptchaDialog(event.sid, event.image) + is ValidationEvent -> showValidationRequired(event.sid) is SuccessAuth -> goToMain(event.haveAuthorized) - StartProgressEvent -> onProgressStarted() - StopProgressEvent -> onProgressStopped() + is CodeSent -> showValidationDialog() + is StartProgressEvent -> onProgressStarted() + is StopProgressEvent -> onProgressStopped() } } @@ -286,8 +277,9 @@ class LoginFragment : BaseViewModelFragment(R.layout.fragment_lo validationBinding.cancel.setOnClickListener { dialog.dismiss() } } - private fun showValidationRequired() { + private fun showValidationRequired(validationSid: String) { Toast.makeText(requireContext(), R.string.validation_required, Toast.LENGTH_LONG).show() + viewModel.sendSms(validationSid) } private fun showErrorSnackbar(errorDescription: String) { diff --git a/app/src/main/kotlin/com/meloda/fast/screens/login/LoginViewModel.kt b/app/src/main/kotlin/com/meloda/fast/screens/login/LoginViewModel.kt index 73e16c16..c4a613e1 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/login/LoginViewModel.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/login/LoginViewModel.kt @@ -1,11 +1,9 @@ package com.meloda.fast.screens.login -import android.os.Bundle import androidx.lifecycle.viewModelScope import com.meloda.fast.api.UserConfig import com.meloda.fast.api.VKConstants import com.meloda.fast.api.VKException -import com.meloda.fast.api.VkUtils import com.meloda.fast.api.model.request.RequestAuthDirect import com.meloda.fast.api.network.datasource.AuthDataSource import com.meloda.fast.base.viewmodel.* @@ -18,8 +16,6 @@ class LoginViewModel @Inject constructor( private val dataSource: AuthDataSource ) : BaseViewModel() { - lateinit var unknownErrorDefaultText: String - fun login( login: String, password: String, @@ -52,24 +48,12 @@ class LoginViewModel @Inject constructor( UserConfig.userId = it.userId UserConfig.accessToken = it.accessToken - sendEvent(SuccessAuth(haveAuthorized = true)) + sendEvent(SuccessAuth()) }, onError = { if (it !is VKException) return@makeJob twoFaCode?.let { sendEvent(CodeSent) } - - if (VkUtils.isValidationRequired(it)) { - it.validationSid?.let { sid -> - sendEvent(ValidationRequired(validationSid = sid)) - - sendSms(sid) - } - } else if (VkUtils.isCaptchaRequired(it)) { - it.captcha?.let { captcha -> - sendEvent(CaptchaRequired(captcha.first to captcha.second)) - } - } }, onStart = { sendEvent(StartProgressEvent) }, onEnd = { sendEvent(StopProgressEvent) } @@ -84,23 +68,10 @@ class LoginViewModel @Inject constructor( onEnd = {}) } - suspend fun getValidatedData(bundle: Bundle) { - val accessToken = bundle.getString("token") ?: "" - val userId = bundle.getInt("userId") - - UserConfig.accessToken = accessToken - UserConfig.userId = userId - - tasksEventChannel.send(SuccessAuth()) - } - } data class ShowError(val errorDescription: String) : VKEvent() -data class ValidationRequired(val validationSid: String) : VKEvent() -data class CaptchaRequired(val captcha: Pair) : VKEvent() - object CodeSent : VKEvent() data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent() \ 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 5f93cddd..acdd3607 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 @@ -20,10 +20,13 @@ 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 class MessagesHistoryAdapter constructor( @@ -50,6 +53,8 @@ class MessagesHistoryAdapter constructor( ) 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 @@ -84,6 +89,9 @@ class MessagesHistoryAdapter constructor( 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) ) @@ -215,6 +223,12 @@ class MessagesHistoryAdapter constructor( 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) { @@ -238,6 +252,9 @@ class MessagesHistoryAdapter constructor( 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) } } @@ -303,12 +320,52 @@ class MessagesHistoryAdapter constructor( 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, - binding.photosContainer + photosContainer = binding.photosContainer ) + + MessagesManager.setMessageText( + message = message, + textView = binding.text + ) + + binding.bubble.isVisible = binding.text.text.toString().isNotEmpty() } } @@ -324,9 +381,66 @@ class MessagesHistoryAdapter constructor( 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) { @@ -444,8 +558,10 @@ class MessagesHistoryAdapter constructor( private const val ATTACHMENT_PHOTOS_IN = 101 private const val ATTACHMENT_PHOTOS_OUT = 102 - private const val ATTACHMENT_STICKER_IN = 111 - private const val ATTACHMENT_STICKER_OUT = 112 + 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( 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 9ff3e05b..ce5dad2b 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 @@ -58,6 +58,8 @@ class MessagesHistoryViewModel @Inject constructor( baseMessage.asVkMessage().let { message -> messages[message.id] = message } } + dataSource.storeMessages(messages.values.toList()) + val conversations = hashMapOf() response.conversations?.let { baseConversations -> baseConversations.forEach { baseConversation -> 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 index 3351e794..abac6b88 100644 --- a/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt +++ b/app/src/main/kotlin/com/meloda/fast/screens/messages/MessagesManager.kt @@ -1,19 +1,28 @@ 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 @@ -53,7 +62,7 @@ object MessagesManager { AndroidUtils.px(size.height).roundToInt() ) it.shapeAppearanceModel = - it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(4) } + it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) } it.scaleType = ImageView.ScaleType.CENTER_CROP } @@ -69,7 +78,77 @@ object MessagesManager { photosContainer.addView(newPhoto) - newPhoto.load(size.url) + 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) } } } } @@ -93,7 +172,7 @@ object MessagesManager { message: VkMessage, textView: TextView ) { - textView.text = message.text ?: "[no_message]" + textView.text = VkUtils.prepareMessageText(message.text) } diff --git a/app/src/main/res/drawable/ic_play_button_circle_background.xml b/app/src/main/res/drawable/ic_play_button_circle_background.xml new file mode 100644 index 00000000..96c95cab --- /dev/null +++ b/app/src/main/res/drawable/ic_play_button_circle_background.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_round_play_arrow_24.xml b/app/src/main/res/drawable/ic_round_play_arrow_24.xml new file mode 100644 index 00000000..78fbfbba --- /dev/null +++ b/app/src/main/res/drawable/ic_round_play_arrow_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/test.png b/app/src/main/res/drawable/test.png new file mode 100644 index 0000000000000000000000000000000000000000..6da07119a62140c0a6db945881691edd078ee472 GIT binary patch literal 44277 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 t(AKHz=yu4)c4ET + + + + + + + - - - - \ 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 new file mode 100644 index 00000000..1195e25b --- /dev/null +++ b/app/src/main/res/layout/item_message_attachment_videos_in.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 47042176..693e1116 100644 --- a/app/src/main/res/layout/item_message_out.xml +++ b/app/src/main/res/layout/item_message_out.xml @@ -21,6 +21,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9901966a..6ed665cd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -24,6 +24,7 @@ @color/n2_500 @color/n2_600 + @color/a1_0 @color/n2_100