forked from melod1n/fast-messenger
code saving
This commit is contained in:
@@ -7,7 +7,6 @@ object VKConstants {
|
|||||||
const val USER_FIELDS =
|
const val USER_FIELDS =
|
||||||
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info"
|
"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 API_VERSION = "5.132"
|
||||||
const val VK_APP_ID = "2274003"
|
const val VK_APP_ID = "2274003"
|
||||||
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ object VkUtils {
|
|||||||
return throwable.error == VkErrors.NEED_CAPTCHA
|
return throwable.error == VkErrors.NEED_CAPTCHA
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepareMessageText(text: String): String {
|
fun prepareMessageText(text: String?): String? {
|
||||||
|
if (text == null) return null
|
||||||
|
|
||||||
return text
|
return text
|
||||||
.replace("\n", " ")
|
.replace("\n", " ")
|
||||||
.replace("&", "&")
|
.replace("&", "&")
|
||||||
@@ -58,9 +60,7 @@ object VkUtils {
|
|||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.VIDEO -> {
|
BaseVkAttachmentItem.AttachmentType.VIDEO -> {
|
||||||
val video = baseAttachment.video ?: continue
|
val video = baseAttachment.video ?: continue
|
||||||
attachments += VkVideo(
|
attachments += video.asVkVideo()
|
||||||
link = video.player
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
|
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
|
||||||
val audio = baseAttachment.audio ?: continue
|
val audio = baseAttachment.audio ?: continue
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.meloda.fast.api.model
|
package com.meloda.fast.api.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.room.Embedded
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@@ -24,10 +24,14 @@ data class VkConversation(
|
|||||||
val lastMessageId: Int,
|
val lastMessageId: Int,
|
||||||
val unreadCount: Int?,
|
val unreadCount: Int?,
|
||||||
val membersCount: Int?,
|
val membersCount: Int?,
|
||||||
val isPinned: Boolean
|
val isPinned: Boolean,
|
||||||
) : Parcelable {
|
|
||||||
@Ignore
|
@Embedded(prefix = "pinnedMessage_")
|
||||||
|
var pinnedMessage: VkMessage? = null,
|
||||||
|
|
||||||
|
@Embedded(prefix = "lastMessage_")
|
||||||
var lastMessage: VkMessage? = null
|
var lastMessage: VkMessage? = null
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
fun isChat() = type == "chat"
|
fun isChat() = type == "chat"
|
||||||
fun isUser() = type == "user"
|
fun isUser() = type == "user"
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package com.meloda.fast.api.model
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Entity(tableName = "messages")
|
@Entity(tableName = "messages")
|
||||||
@@ -25,16 +23,10 @@ data class VkMessage(
|
|||||||
val actionConversationMessageId: Int? = null,
|
val actionConversationMessageId: Int? = null,
|
||||||
val actionMessage: String? = null,
|
val actionMessage: String? = null,
|
||||||
val geoType: String? = null,
|
val geoType: String? = null,
|
||||||
val important: Boolean = false
|
val important: Boolean = false,
|
||||||
) : Parcelable {
|
var forwards: List<VkMessage>? = null,
|
||||||
|
|
||||||
@IgnoredOnParcel
|
|
||||||
@Ignore
|
|
||||||
var forwards: List<VkMessage>? = null
|
|
||||||
|
|
||||||
@IgnoredOnParcel
|
|
||||||
@Ignore
|
|
||||||
var attachments: List<VkAttachment>? = null
|
var attachments: List<VkAttachment>? = null
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
fun isPeerChat() = peerId > 2_000_000_000
|
fun isPeerChat() = peerId > 2_000_000_000
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
abstract class VkAttachment : Parcelable
|
@Parcelize
|
||||||
|
open class VkAttachment : Parcelable
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkAudio(
|
data class VkAudio(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkCall(
|
data class VkCall(
|
||||||
val initiatorId: Int
|
val initiatorId: Int
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkFile(
|
data class VkFile(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkGift(
|
data class VkGift(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkGraffiti(
|
data class VkGraffiti(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkGroupCall(
|
data class VkGroupCall(
|
||||||
val initiatorId: Int
|
val initiatorId: Int
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkLink(
|
data class VkLink(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkMiniApp(
|
data class VkMiniApp(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
import com.meloda.fast.api.model.base.attachments.Size
|
import com.meloda.fast.api.model.base.attachments.Size
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -16,6 +17,9 @@ data class VkPhoto(
|
|||||||
val userId: Int?
|
val userId: Int?
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
fun sizeOfType(type: Char): Size? {
|
fun sizeOfType(type: Char): Size? {
|
||||||
for (size in sizes) {
|
for (size in sizes) {
|
||||||
if (size.type == type.toString())
|
if (size.type == type.toString())
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkPoll(
|
data class VkPoll(
|
||||||
val id: Int
|
val id: Int
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -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.BaseVkSticker
|
||||||
import com.meloda.fast.api.model.base.attachments.StickerSize
|
import com.meloda.fast.api.model.base.attachments.StickerSize
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -12,6 +13,9 @@ data class VkSticker(
|
|||||||
val backgroundImages: List<BaseVkSticker.Image>
|
val backgroundImages: List<BaseVkSticker.Image>
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
fun urlForSize(@StickerSize size: Int): String? {
|
fun urlForSize(@StickerSize size: Int): String? {
|
||||||
for (image in images) {
|
for (image in images) {
|
||||||
if (image.width == size) return image.url
|
if (image.width == size) return image.url
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import com.meloda.fast.api.model.base.attachments.BaseVkVideo
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkVideo(
|
data class VkVideo(
|
||||||
val link: String
|
val id: Int,
|
||||||
) : VkAttachment()
|
val images: List<BaseVkVideo.Image>,
|
||||||
|
val firstFrames: List<BaseVkVideo.FirstFrame>
|
||||||
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
|
fun imageForWidth(width: Int): BaseVkVideo.Image? {
|
||||||
|
return images.find { it.width == width }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkVoiceMessage(
|
data class VkVoiceMessage(
|
||||||
val link: String
|
val link: String
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkWall(
|
data class VkWall(
|
||||||
val id: Int
|
val id: Int
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkWallReply(
|
data class VkWallReply(
|
||||||
val id: Int
|
val id: Int
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val className: String = this::class.java.name
|
||||||
|
}
|
||||||
@@ -54,7 +54,10 @@ data class BaseVkConversation(
|
|||||||
membersCount = chatSettings?.membersCount,
|
membersCount = chatSettings?.membersCount,
|
||||||
ownerId = chatSettings?.ownerId,
|
ownerId = chatSettings?.ownerId,
|
||||||
isPinned = sortId.majorId > 0
|
isPinned = sortId.majorId > 0
|
||||||
).apply { this.lastMessage = lastMessage }
|
).apply {
|
||||||
|
this.lastMessage = lastMessage
|
||||||
|
this.pinnedMessage = chatSettings?.pinnedMessage?.asVkMessage()
|
||||||
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Peer(
|
data class Peer(
|
||||||
@@ -111,7 +114,9 @@ data class BaseVkConversation(
|
|||||||
val isDisappearing: Boolean,
|
val isDisappearing: Boolean,
|
||||||
@SerializedName("is_service")
|
@SerializedName("is_service")
|
||||||
val isService: Boolean,
|
val isService: Boolean,
|
||||||
val theme: String
|
val theme: String?,
|
||||||
|
@SerializedName("pinned_message")
|
||||||
|
val pinnedMessage: BaseVkMessage?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package com.meloda.fast.api.model.base.attachments
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.meloda.fast.api.model.attachments.VkVideo
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
//not all fields
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class BaseVkVideo(
|
data class BaseVkVideo(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
@@ -53,6 +55,12 @@ data class BaseVkVideo(
|
|||||||
//ads
|
//ads
|
||||||
) : BaseVkAttachment() {
|
) : BaseVkAttachment() {
|
||||||
|
|
||||||
|
fun asVkVideo() = VkVideo(
|
||||||
|
id = id,
|
||||||
|
images = image,
|
||||||
|
firstFrames = firstFrame
|
||||||
|
)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Image(
|
data class Image(
|
||||||
val height: Int,
|
val height: Int,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.meloda.fast.api.network
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
|
import com.meloda.fast.api.VKException
|
||||||
import com.meloda.fast.api.base.ApiResponse
|
import com.meloda.fast.api.base.ApiResponse
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
import okio.Timeout
|
import okio.Timeout
|
||||||
|
import org.json.JSONObject
|
||||||
import retrofit2.*
|
import retrofit2.*
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
@@ -86,6 +88,9 @@ internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy)
|
|||||||
}
|
}
|
||||||
} else Answer.Error(IOException(response.errorBody()?.string() ?: ""))
|
} else Answer.Error(IOException(response.errorBody()?.string() ?: ""))
|
||||||
|
|
||||||
|
if (result is Answer.Error) if (checkErrors(call, result)) return
|
||||||
|
|
||||||
|
|
||||||
callback.onResponse(proxy, Response.success(result))
|
callback.onResponse(proxy, Response.success(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +100,23 @@ internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy)
|
|||||||
Response.success(Answer.Error(throwable = error))
|
Response.success(Answer.Error(throwable = error))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkErrors(call: Call<T>, 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 {
|
override fun timeout(): Timeout {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.meloda.fast.api.network.datasource
|
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.MessagesGetHistoryRequest
|
||||||
import com.meloda.fast.api.model.request.MessagesGetLongPollServerRequest
|
import com.meloda.fast.api.model.request.MessagesGetLongPollServerRequest
|
||||||
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
|
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
|
||||||
@@ -25,4 +26,8 @@ class MessagesDataSource @Inject constructor(
|
|||||||
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
||||||
repo.getLongPollServer(params.map)
|
repo.getLongPollServer(params.map)
|
||||||
|
|
||||||
|
suspend fun storeMessages(messages: List<VkMessage>) = dao.insert(messages)
|
||||||
|
|
||||||
|
suspend fun getCachedMessages(peerId: Int) = dao.getByPeerId(peerId)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.meloda.fast.base.viewmodel
|
package com.meloda.fast.base.viewmodel
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.meloda.fast.api.VKException
|
import com.meloda.fast.api.VKException
|
||||||
@@ -14,6 +13,8 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
abstract class BaseViewModel : ViewModel() {
|
abstract class BaseViewModel : ViewModel() {
|
||||||
|
|
||||||
|
var unknownErrorDefaultText: String = ""
|
||||||
|
|
||||||
protected val tasksEventChannel = Channel<VKEvent>()
|
protected val tasksEventChannel = Channel<VKEvent>()
|
||||||
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||||
|
|
||||||
@@ -30,6 +31,12 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
is Answer.Error -> {
|
is Answer.Error -> {
|
||||||
checkErrors(response.throwable)
|
checkErrors(response.throwable)
|
||||||
onError?.invoke(response.throwable)
|
onError?.invoke(response.throwable)
|
||||||
|
?: sendEvent(
|
||||||
|
ErrorEvent(
|
||||||
|
response.throwable.message
|
||||||
|
?: unknownErrorDefaultText
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
||||||
@@ -37,31 +44,32 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
||||||
|
|
||||||
private suspend fun checkErrors(throwable: Throwable) {
|
private suspend fun checkErrors(throwable: Throwable) {
|
||||||
if (throwable is ApiError) {
|
when (throwable) {
|
||||||
|
is ApiError -> {
|
||||||
when (throwable.errorCode) {
|
when (throwable.errorCode) {
|
||||||
VkErrorCodes.USER_AUTHORIZATION_FAILED -> {
|
VkErrorCodes.USER_AUTHORIZATION_FAILED -> {
|
||||||
sendEvent(IllegalTokenEvent)
|
sendEvent(IllegalTokenEvent)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (throwable is VKException) {
|
}
|
||||||
|
is VKException -> {
|
||||||
when (throwable.error) {
|
when (throwable.error) {
|
||||||
VkErrors.NEED_CAPTCHA -> {
|
VkErrors.NEED_CAPTCHA -> {
|
||||||
throwable.captcha =
|
val json = throwable.json ?: return
|
||||||
(throwable.json?.optString("captcha_sid")
|
sendEvent(
|
||||||
?: "") to (throwable.json?.optString("captcha_img") ?: "")
|
CaptchaEvent(
|
||||||
return
|
sid = json.optString("captcha_sid"),
|
||||||
|
image = json.optString("captcha_img")
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
VkErrors.NEED_VALIDATION -> {
|
VkErrors.NEED_VALIDATION -> {
|
||||||
throwable.validationSid = throwable.json?.optString("validation_sid")
|
val json = throwable.json ?: return
|
||||||
return
|
sendEvent(ValidationEvent(sid = json.optString("validation_sid")))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(ShowDialogInfoEvent(null, Log.getStackTraceString(throwable)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@ data class ShowDialogInfoEvent(
|
|||||||
data class ErrorEvent(val errorText: String) : VKEvent()
|
data class ErrorEvent(val errorText: String) : VKEvent()
|
||||||
|
|
||||||
object IllegalTokenEvent : 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 StartProgressEvent : VKEvent()
|
||||||
object StopProgressEvent : VKEvent()
|
object StopProgressEvent : VKEvent()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.meloda.fast.database
|
|||||||
|
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.TypeConverters
|
||||||
import com.meloda.fast.api.model.VkConversation
|
import com.meloda.fast.api.model.VkConversation
|
||||||
import com.meloda.fast.api.model.VkGroup
|
import com.meloda.fast.api.model.VkGroup
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
@@ -18,9 +19,10 @@ import com.meloda.fast.database.dao.UsersDao
|
|||||||
VkUser::class,
|
VkUser::class,
|
||||||
VkGroup::class
|
VkGroup::class
|
||||||
],
|
],
|
||||||
version = 18,
|
version = 24,
|
||||||
exportSchema = false
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
|
@TypeConverters(Converters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun conversationsDao(): ConversationsDao
|
abstract fun conversationsDao(): ConversationsDao
|
||||||
|
|||||||
@@ -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<VkMessage>?): 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<VkMessage>? {
|
||||||
|
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<VkAttachment>?): 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<VkAttachment>? {
|
||||||
|
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?
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,26 @@
|
|||||||
package com.meloda.fast.database.dao
|
package com.meloda.fast.database.dao
|
||||||
|
|
||||||
import androidx.room.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
|
@Dao
|
||||||
interface MessagesDao {
|
interface MessagesDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM messages")
|
||||||
|
suspend fun getAll(): List<VkMessage>
|
||||||
|
|
||||||
|
@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<VkMessage>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insert(values: List<VkMessage>)
|
||||||
|
|
||||||
|
suspend fun insert(values: Array<out VkMessage>) = insert(values.toList())
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.meloda.fast.screens.conversations
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@@ -82,6 +83,8 @@ class ConversationsFragment :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.toolbar.overflowIcon = ContextCompat.getDrawable(requireContext(), R.drawable.test)
|
||||||
|
|
||||||
viewModel.loadProfileUser()
|
viewModel.loadProfileUser()
|
||||||
viewModel.loadConversations()
|
viewModel.loadConversations()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.setFragmentResultListener
|
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
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.BuildConfig
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseViewModelFragment
|
import com.meloda.fast.base.BaseViewModelFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.*
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
|
||||||
import com.meloda.fast.databinding.DialogCaptchaBinding
|
import com.meloda.fast.databinding.DialogCaptchaBinding
|
||||||
import com.meloda.fast.databinding.DialogValidationBinding
|
import com.meloda.fast.databinding.DialogValidationBinding
|
||||||
import com.meloda.fast.databinding.FragmentLoginBinding
|
import com.meloda.fast.databinding.FragmentLoginBinding
|
||||||
@@ -60,10 +57,6 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
prepareViews()
|
prepareViews()
|
||||||
|
|
||||||
binding.loginInput.clearFocus()
|
binding.loginInput.clearFocus()
|
||||||
|
|
||||||
setFragmentResultListener("validation") { _, bundle ->
|
|
||||||
lifecycleScope.launch { viewModel.getValidatedData(bundle) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VKEvent) {
|
||||||
@@ -71,15 +64,13 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is ShowError -> showErrorSnackbar(event.errorDescription)
|
is ShowError -> showErrorSnackbar(event.errorDescription)
|
||||||
is CaptchaRequired -> showCaptchaDialog(event.captcha.first, event.captcha.second)
|
is CaptchaEvent -> showCaptchaDialog(event.sid, event.image)
|
||||||
|
is ValidationEvent -> showValidationRequired(event.sid)
|
||||||
CodeSent -> showValidationDialog()
|
|
||||||
|
|
||||||
is ValidationRequired -> showValidationRequired()
|
|
||||||
is SuccessAuth -> goToMain(event.haveAuthorized)
|
is SuccessAuth -> goToMain(event.haveAuthorized)
|
||||||
|
|
||||||
StartProgressEvent -> onProgressStarted()
|
is CodeSent -> showValidationDialog()
|
||||||
StopProgressEvent -> onProgressStopped()
|
is StartProgressEvent -> onProgressStarted()
|
||||||
|
is StopProgressEvent -> onProgressStopped()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,8 +277,9 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
validationBinding.cancel.setOnClickListener { dialog.dismiss() }
|
validationBinding.cancel.setOnClickListener { dialog.dismiss() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showValidationRequired() {
|
private fun showValidationRequired(validationSid: String) {
|
||||||
Toast.makeText(requireContext(), R.string.validation_required, Toast.LENGTH_LONG).show()
|
Toast.makeText(requireContext(), R.string.validation_required, Toast.LENGTH_LONG).show()
|
||||||
|
viewModel.sendSms(validationSid)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showErrorSnackbar(errorDescription: String) {
|
private fun showErrorSnackbar(errorDescription: String) {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package com.meloda.fast.screens.login
|
package com.meloda.fast.screens.login
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.meloda.fast.api.UserConfig
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.api.VKConstants
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.api.VKException
|
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.model.request.RequestAuthDirect
|
||||||
import com.meloda.fast.api.network.datasource.AuthDataSource
|
import com.meloda.fast.api.network.datasource.AuthDataSource
|
||||||
import com.meloda.fast.base.viewmodel.*
|
import com.meloda.fast.base.viewmodel.*
|
||||||
@@ -18,8 +16,6 @@ class LoginViewModel @Inject constructor(
|
|||||||
private val dataSource: AuthDataSource
|
private val dataSource: AuthDataSource
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
lateinit var unknownErrorDefaultText: String
|
|
||||||
|
|
||||||
fun login(
|
fun login(
|
||||||
login: String,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -52,24 +48,12 @@ class LoginViewModel @Inject constructor(
|
|||||||
UserConfig.userId = it.userId
|
UserConfig.userId = it.userId
|
||||||
UserConfig.accessToken = it.accessToken
|
UserConfig.accessToken = it.accessToken
|
||||||
|
|
||||||
sendEvent(SuccessAuth(haveAuthorized = true))
|
sendEvent(SuccessAuth())
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
if (it !is VKException) return@makeJob
|
if (it !is VKException) return@makeJob
|
||||||
|
|
||||||
twoFaCode?.let { sendEvent(CodeSent) }
|
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) },
|
onStart = { sendEvent(StartProgressEvent) },
|
||||||
onEnd = { sendEvent(StopProgressEvent) }
|
onEnd = { sendEvent(StopProgressEvent) }
|
||||||
@@ -84,23 +68,10 @@ class LoginViewModel @Inject constructor(
|
|||||||
onEnd = {})
|
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 ShowError(val errorDescription: String) : VKEvent()
|
||||||
|
|
||||||
data class ValidationRequired(val validationSid: String) : VKEvent()
|
|
||||||
data class CaptchaRequired(val captcha: Pair<String, String>) : VKEvent()
|
|
||||||
|
|
||||||
object CodeSent : VKEvent()
|
object CodeSent : VKEvent()
|
||||||
|
|
||||||
data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent()
|
data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent()
|
||||||
@@ -20,10 +20,13 @@ import com.meloda.fast.api.model.VkMessage
|
|||||||
import com.meloda.fast.api.model.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||||
import com.meloda.fast.api.model.attachments.VkSticker
|
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.BaseAdapter
|
||||||
import com.meloda.fast.base.adapter.BaseHolder
|
import com.meloda.fast.base.adapter.BaseHolder
|
||||||
import com.meloda.fast.databinding.*
|
import com.meloda.fast.databinding.*
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MessagesHistoryAdapter constructor(
|
class MessagesHistoryAdapter constructor(
|
||||||
@@ -50,6 +53,8 @@ class MessagesHistoryAdapter constructor(
|
|||||||
) return if (message.isOut) ATTACHMENT_PHOTOS_OUT
|
) return if (message.isOut) ATTACHMENT_PHOTOS_OUT
|
||||||
else ATTACHMENT_PHOTOS_IN
|
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
|
if (attachments[0] is VkSticker) return if (message.isOut) ATTACHMENT_STICKER_OUT
|
||||||
else ATTACHMENT_STICKER_IN
|
else ATTACHMENT_STICKER_IN
|
||||||
@@ -84,6 +89,9 @@ class MessagesHistoryAdapter constructor(
|
|||||||
ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing(
|
ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing(
|
||||||
ItemMessageAttachmentPhotosOutBinding.inflate(inflater, parent, false)
|
ItemMessageAttachmentPhotosOutBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
ATTACHMENT_VIDEOS_IN, ATTACHMENT_VIDEOS_OUT -> AttachmentVideosIncoming(
|
||||||
|
ItemMessageAttachmentVideosInBinding.inflate(inflater, parent, false)
|
||||||
|
)
|
||||||
OUTGOING -> OutgoingMessage(
|
OUTGOING -> OutgoingMessage(
|
||||||
ItemMessageOutBinding.inflate(inflater, parent, false)
|
ItemMessageOutBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
@@ -215,6 +223,12 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
MessagesManager.setRootMaxWidth(binding.bubble)
|
MessagesManager.setRootMaxWidth(binding.bubble)
|
||||||
|
|
||||||
|
binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() }
|
||||||
|
|
||||||
|
binding.bubble.setOnClickListener {
|
||||||
|
binding.time.isVisible = !binding.time.isVisible
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
@@ -238,6 +252,9 @@ class MessagesHistoryAdapter constructor(
|
|||||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStroke
|
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStroke
|
||||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStroke
|
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStroke
|
||||||
else backgroundStroke
|
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) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
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(
|
MessagesManager.loadPhotos(
|
||||||
context = context,
|
context = context,
|
||||||
message = message,
|
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,
|
message = message,
|
||||||
photosContainer = binding.photosContainer
|
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(
|
inner class AttachmentStickerOutgoing(
|
||||||
private val binding: ItemMessageAttachmentStickerOutBinding
|
private val binding: ItemMessageAttachmentStickerOutBinding
|
||||||
) : Holder(binding.root) {
|
) : Holder(binding.root) {
|
||||||
@@ -444,8 +558,10 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
private const val ATTACHMENT_PHOTOS_IN = 101
|
private const val ATTACHMENT_PHOTOS_IN = 101
|
||||||
private const val ATTACHMENT_PHOTOS_OUT = 102
|
private const val ATTACHMENT_PHOTOS_OUT = 102
|
||||||
private const val ATTACHMENT_STICKER_IN = 111
|
private const val ATTACHMENT_VIDEOS_IN = 111
|
||||||
private const val ATTACHMENT_STICKER_OUT = 112
|
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<VkMessage>() {
|
private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() {
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
baseMessage.asVkMessage().let { message -> messages[message.id] = message }
|
baseMessage.asVkMessage().let { message -> messages[message.id] = message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataSource.storeMessages(messages.values.toList())
|
||||||
|
|
||||||
val conversations = hashMapOf<Int, VkConversation>()
|
val conversations = hashMapOf<Int, VkConversation>()
|
||||||
response.conversations?.let { baseConversations ->
|
response.conversations?.let { baseConversations ->
|
||||||
baseConversations.forEach { baseConversation ->
|
baseConversations.forEach { baseConversation ->
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Space
|
import android.widget.Space
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
import androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isNotEmpty
|
import androidx.core.view.isNotEmpty
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
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.VkGroup
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
import com.meloda.fast.api.model.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
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.common.AppGlobal
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import com.meloda.fast.widget.BoundedFrameLayout
|
import com.meloda.fast.widget.BoundedFrameLayout
|
||||||
@@ -53,7 +62,7 @@ object MessagesManager {
|
|||||||
AndroidUtils.px(size.height).roundToInt()
|
AndroidUtils.px(size.height).roundToInt()
|
||||||
)
|
)
|
||||||
it.shapeAppearanceModel =
|
it.shapeAppearanceModel =
|
||||||
it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(4) }
|
it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||||
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +78,77 @@ object MessagesManager {
|
|||||||
|
|
||||||
photosContainer.addView(newPhoto)
|
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,
|
message: VkMessage,
|
||||||
textView: TextView
|
textView: TextView
|
||||||
) {
|
) {
|
||||||
textView.text = message.text ?: "[no_message]"
|
textView.text = VkUtils.prepareMessageText(message.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z"/>
|
||||||
|
</vector>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -20,7 +20,7 @@
|
|||||||
android:elevation="0dp"
|
android:elevation="0dp"
|
||||||
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
||||||
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
||||||
app:layout_scrollFlags="scroll|enterAlwaysCollapsed|snap"
|
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||||
app:title="Messages">
|
app:title="Messages">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|||||||
@@ -68,16 +68,14 @@
|
|||||||
|
|
||||||
</com.meloda.fast.widget.BoundedFrameLayout>
|
</com.meloda.fast.widget.BoundedFrameLayout>
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:id="@+id/unread"
|
|
||||||
android:layout_width="13dp"
|
|
||||||
android:layout_height="13dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginBottom="20dp"
|
|
||||||
android:src="@color/a3_200" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/photosContainer"
|
android:id="@+id/photosContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -85,8 +83,17 @@
|
|||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:orientation="vertical" />
|
android:orientation="vertical" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
<com.meloda.fast.widget.CircleImageView
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
android:id="@+id/unread"
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
android:layout_width="13dp"
|
||||||
|
android:layout_height="13dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:src="@color/a3_200" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="12dp"
|
||||||
|
android:paddingVertical="2.5dp">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/spacer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/google_sans_regular"
|
||||||
|
android:textColor="@color/a3_700"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.BoundedFrameLayout
|
||||||
|
android:id="@+id/bubble"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/ic_message_in_background"
|
||||||
|
android:backgroundTint="@color/n2_100"
|
||||||
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:textColor="@color/n1_800"
|
||||||
|
tools:text="This" />
|
||||||
|
|
||||||
|
</com.meloda.fast.widget.BoundedFrameLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/videosContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/unread"
|
||||||
|
android:layout_width="13dp"
|
||||||
|
android:layout_height="13dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:src="@color/a3_200" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</layout>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
@@ -57,8 +58,19 @@
|
|||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:textColor="?textColorSecondaryVariant"
|
||||||
|
tools:layout_height="18dp"
|
||||||
|
tools:text="12:00" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
<color name="textColorSecondaryVariant">@color/n2_500</color>
|
<color name="textColorSecondaryVariant">@color/n2_500</color>
|
||||||
<color name="textColorService">@color/n2_600</color>
|
<color name="textColorService">@color/n2_600</color>
|
||||||
|
|
||||||
|
|
||||||
<color name="colorSurface">@color/a1_0</color>
|
<color name="colorSurface">@color/a1_0</color>
|
||||||
|
|
||||||
<color name="messageOutStrokeColor">@color/n2_100</color>
|
<color name="messageOutStrokeColor">@color/n2_100</color>
|
||||||
|
|||||||
Reference in New Issue
Block a user