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 00000000..6da07119 Binary files /dev/null and b/app/src/main/res/drawable/test.png differ diff --git a/app/src/main/res/layout/fragment_conversations.xml b/app/src/main/res/layout/fragment_conversations.xml index 419ce0f6..cf7d291f 100644 --- a/app/src/main/res/layout/fragment_conversations.xml +++ b/app/src/main/res/layout/fragment_conversations.xml @@ -20,7 +20,7 @@ android:elevation="0dp" app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle" app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle" - app:layout_scrollFlags="scroll|enterAlwaysCollapsed|snap" + app:layout_scrollFlags="scroll|enterAlways|snap" app:title="Messages"> + + + + + + + - - - - \ 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