chat theme change event

mark messages as important
refactoring
This commit is contained in:
2021-09-14 23:33:10 +03:00
parent 088c6c8712
commit a3a282c32c
55 changed files with 722 additions and 111 deletions
@@ -1,7 +1,10 @@
package com.meloda.fast.api package com.meloda.fast.api
import android.content.Context import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.SpannableString
import android.text.style.StyleSpan
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.api.model.VkGroup import com.meloda.fast.api.model.VkGroup
@@ -51,9 +54,7 @@ object VkUtils {
when (baseAttachment.getPreparedType()) { when (baseAttachment.getPreparedType()) {
BaseVkAttachmentItem.AttachmentType.PHOTO -> { BaseVkAttachmentItem.AttachmentType.PHOTO -> {
val photo = baseAttachment.photo ?: continue val photo = baseAttachment.photo ?: continue
attachments += VkPhoto( attachments += photo.asVkPhoto()
link = photo.sizes[0].url
)
} }
BaseVkAttachmentItem.AttachmentType.VIDEO -> { BaseVkAttachmentItem.AttachmentType.VIDEO -> {
val video = baseAttachment.video ?: continue val video = baseAttachment.video ?: continue
@@ -206,8 +207,6 @@ object VkUtils {
val actionUser = profiles?.get(memberId) val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId) val actionGroup = groups?.get(memberId)
// val actionUser = profiles?.find { it.id == memberId }
// val actionGroup = groups?.find { it.id == memberId }
if (isUser && actionUser == null) return null if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null if (isGroup && actionGroup == null) return null
@@ -233,8 +232,6 @@ object VkUtils {
val actionUser = profiles?.get(memberId) val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId) val actionGroup = groups?.get(memberId)
// val actionUser = profiles?.find { it.id == memberId }
// val actionGroup = groups?.find { it.id == memberId }
if (isUser && actionUser == null) return null if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null if (isGroup && actionGroup == null) return null
@@ -302,11 +299,257 @@ object VkUtils {
"$prefix took a screenshot" "$prefix took a screenshot"
} }
VkMessage.Action.CHAT_STYLE_UPDATE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix changed chat theme"
}
null -> null null -> null
else -> "[${message.action}]" else -> "[${message.action}]"
} }
} }
fun getActionMessageText(
message: VkMessage,
youPrefix: String,
profiles: HashMap<Int, VkUser>? = null,
groups: HashMap<Int, VkGroup>? = null,
messageUser: VkUser? = null,
messageGroup: VkGroup? = null
): SpannableString? {
return when (message.getPreparedAction()) {
VkMessage.Action.CHAT_CREATE -> {
val text = message.actionText ?: return null
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix created «$text»"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
it.setSpan(
StyleSpan(Typeface.BOLD),
spanText.indexOf(text, startIndex = prefix.length),
text.length, 0
)
}
}
VkMessage.Action.CHAT_TITLE_UPDATE -> {
val text = message.actionText ?: return null
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix renamed chat to «$text»"
val startIndex = spanText.indexOf(text)
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
it.setSpan(
StyleSpan(Typeface.BOLD), startIndex, startIndex + text.length, 0
)
}
}
VkMessage.Action.CHAT_PHOTO_UPDATE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix updated the chat photo"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_PHOTO_REMOVE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix deleted the chat photo"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_KICK_USER -> {
val memberId = message.actionMemberId ?: return null
val isUser = memberId > 0
val isGroup = memberId < 0
val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId)
if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null
if (memberId == message.fromId) {
val prefix = if (memberId == UserConfig.userId) youPrefix
else actionUser.toString()
val spanText = "$prefix left the chat"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
} else {
val prefix =
if (message.fromId == UserConfig.userId) youPrefix
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else actionUser.toString()
val spanText = "$prefix kicked $postfix"
val startIndex = spanText.indexOf(postfix)
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
it.setSpan(
StyleSpan(Typeface.BOLD), startIndex, startIndex + postfix.length, 0
)
}
}
}
VkMessage.Action.CHAT_INVITE_USER -> {
val memberId = message.actionMemberId ?: 0
val isUser = memberId > 0
val isGroup = memberId < 0
val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId)
if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null
if (memberId == message.fromId) {
val prefix = if (memberId == UserConfig.userId) youPrefix
else actionUser.toString()
val spanText = "$prefix returned the chat"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
} else {
val prefix = if (message.fromId == UserConfig.userId) youPrefix
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else actionUser.toString()
val spanText = "$prefix invited $postfix"
val startIndex = spanText.indexOf(postfix)
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
it.setSpan(
StyleSpan(Typeface.BOLD), startIndex, startIndex + postfix.length, 0
)
}
}
}
VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix joined the chat via link"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix joined the call via link"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_PIN_MESSAGE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val actionMessage = message.actionMessage ?: return null
val spanText = "$prefix pinned message «$actionMessage»"
val startIndex = spanText.indexOf(actionMessage)
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
it.setSpan(
StyleSpan(Typeface.BOLD), startIndex, startIndex + actionMessage.length, 0
)
}
}
VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix unpinned message"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_SCREENSHOT -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix took a screenshot"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
VkMessage.Action.CHAT_STYLE_UPDATE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val spanText = "$prefix changed chat theme"
SpannableString(spanText).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
}
}
null -> null
else -> SpannableString("[${message.action}]")
}
}
fun getForwardsConversationText(context: Context, message: VkMessage): String? { fun getForwardsConversationText(context: Context, message: VkMessage): String? {
if (message.forwards.isNullOrEmpty()) return null if (message.forwards.isNullOrEmpty()) return null
@@ -1,18 +0,0 @@
package com.meloda.fast.api.datasource
import com.meloda.fast.api.network.repo.MessagesRepo
import com.meloda.fast.api.network.request.MessagesGetHistoryRequest
import com.meloda.fast.api.network.request.MessagesSendRequest
import com.meloda.fast.database.dao.MessagesDao
import javax.inject.Inject
class MessagesDataSource @Inject constructor(
private val repo: MessagesRepo,
private val dao: MessagesDao
) {
suspend fun getHistory(params: MessagesGetHistoryRequest) = repo.getHistory(params.map)
suspend fun send(params: MessagesSendRequest) = repo.send(params.map)
}
@@ -24,7 +24,8 @@ data class VkMessage(
val actionText: String? = null, val actionText: String? = null,
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
) : Parcelable { ) : Parcelable {
@IgnoredOnParcel @IgnoredOnParcel
@@ -61,7 +62,8 @@ data class VkMessage(
actionText = actionText, actionText = actionText,
actionConversationMessageId = actionConversationMessageId, actionConversationMessageId = actionConversationMessageId,
actionMessage = actionMessage, actionMessage = actionMessage,
geoType = geoType geoType = geoType,
important = important
) )
enum class Action(val value: String) { enum class Action(val value: String) {
@@ -78,7 +80,8 @@ data class VkMessage(
// TODO: 9/11/2021 catch this shit // TODO: 9/11/2021 catch this shit
CHAT_INVITE_USER_BY_CALL("chat_invite_user_by_call"), CHAT_INVITE_USER_BY_CALL("chat_invite_user_by_call"),
CHAT_INVITE_USER_BY_CALL_LINK("chat_invite_user_by_call_join_link"); CHAT_INVITE_USER_BY_CALL_LINK("chat_invite_user_by_call_join_link"),
CHAT_STYLE_UPDATE("conversation_style_update");
companion object { companion object {
fun parse(value: String) = values().first { it.value == value } fun parse(value: String) = values().first { it.value == value }
@@ -1,3 +1,5 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
abstract class VkAttachment import android.os.Parcelable
abstract class VkAttachment : Parcelable
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkAudio( data class VkAudio(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkCall( data class VkCall(
val initiatorId: Int val initiatorId: Int
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkFile( data class VkFile(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkGift( data class VkGift(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkGraffiti( data class VkGraffiti(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkGroupCall( data class VkGroupCall(
val initiatorId: Int val initiatorId: Int
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkLink( data class VkLink(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkMiniApp( data class VkMiniApp(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,28 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import com.meloda.fast.api.model.base.attachments.Size
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkPhoto( data class VkPhoto(
val link: String val albumId: Int,
) : VkAttachment() val date: Int,
val id: Int,
val ownerId: Int,
val hasTags: Boolean,
val accessKey: String?,
val sizes: List<Size>,
val text: String,
val userId: Int?
) : VkAttachment() {
fun sizeOfType(type: Char): Size? {
for (size in sizes) {
if (size.type == type.toString())
return size
}
return null
}
}
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkPoll( data class VkPoll(
val id: Int val id: Int
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkSticker( data class VkSticker(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkVideo( data class VkVideo(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkVoiceMessage( data class VkVoiceMessage(
val link: String val link: String
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkWall( data class VkWall(
val id: Int val id: Int
) : VkAttachment() ) : VkAttachment()
@@ -1,5 +1,8 @@
package com.meloda.fast.api.model.attachments package com.meloda.fast.api.model.attachments
import kotlinx.parcelize.Parcelize
@Parcelize
data class VkWallReply( data class VkWallReply(
val id: Int val id: Int
) : VkAttachment() ) : VkAttachment()
@@ -46,7 +46,8 @@ data class BaseVkMessage(
actionText = action?.text, actionText = action?.text,
actionConversationMessageId = action?.conversationMessageId, actionConversationMessageId = action?.conversationMessageId,
actionMessage = action?.message, actionMessage = action?.message,
geoType = geo?.type geoType = geo?.type,
important = important
).also { ).also {
it.attachments = VkUtils.parseAttachments(attachments) it.attachments = VkUtils.parseAttachments(attachments)
it.forwards = VkUtils.parseForwards(fwdMessages) it.forwards = VkUtils.parseForwards(fwdMessages)
@@ -2,6 +2,7 @@ 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.VkPhoto
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@@ -15,12 +16,26 @@ data class BaseVkPhoto(
@SerializedName("has_tags") @SerializedName("has_tags")
val hasTags: Boolean, val hasTags: Boolean,
@SerializedName("access_key") @SerializedName("access_key")
val accessKey: String, val accessKey: String?,
val sizes: List<Size>, val sizes: List<Size>,
val text: String, val text: String,
@SerializedName("user_id") @SerializedName("user_id")
val userId: Int? val userId: Int?
) : BaseVkAttachment() ) : BaseVkAttachment() {
fun asVkPhoto() = VkPhoto(
albumId = albumId,
date = date,
id = id,
ownerId = ownerId,
hasTags = hasTags,
accessKey = accessKey,
sizes = sizes,
text = text,
userId = userId
)
}
@Parcelize @Parcelize
data class Size( data class Size(
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.request package com.meloda.fast.api.model.request
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.request package com.meloda.fast.api.model.request
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -1,6 +1,7 @@
package com.meloda.fast.api.network.request package com.meloda.fast.api.model.request
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@@ -54,5 +55,19 @@ data class MessagesSendRequest(
disableMentions?.let { this["disable_mentions"] = (if (it) 1 else 0).toString() } disableMentions?.let { this["disable_mentions"] = (if (it) 1 else 0).toString() }
dontParseLinks?.let { this["dont_parse_links"] = (if (it) 1 else 0).toString() } dontParseLinks?.let { this["dont_parse_links"] = (if (it) 1 else 0).toString() }
} }
}
@Parcelize
data class MessagesMarkAsImportantRequest(
@SerializedName("message_ids")
val messagesIds: List<Int>,
val important: Boolean
) : Parcelable {
val map
get() = mutableMapOf(
"message_ids" to messagesIds.joinToString { it.toString() },
"important" to (if (important) 1 else 0).toString()
)
} }
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.request package com.meloda.fast.api.model.request
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.response package com.meloda.fast.api.model.response
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.response package com.meloda.fast.api.model.response
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -1,4 +1,4 @@
package com.meloda.fast.api.network.response package com.meloda.fast.api.model.response
import android.os.Parcelable import android.os.Parcelable
import com.meloda.fast.api.model.base.BaseVkConversation import com.meloda.fast.api.model.base.BaseVkConversation
@@ -0,0 +1,2 @@
package com.meloda.fast.api.model.response
@@ -21,6 +21,7 @@ object VKUrls {
object Messages { object Messages {
const val GetHistory = "$API/messages.getHistory" const val GetHistory = "$API/messages.getHistory"
const val Send = "$API/messages.send" const val Send = "$API/messages.send"
const val MarkAsImportant = "$API/messages.markAsImportant"
} }
@@ -1,7 +1,7 @@
package com.meloda.fast.api.datasource package com.meloda.fast.api.network.datasource
import com.meloda.fast.api.network.repo.AuthRepo import com.meloda.fast.api.network.repo.AuthRepo
import com.meloda.fast.api.network.request.RequestAuthDirect import com.meloda.fast.api.model.request.RequestAuthDirect
import javax.inject.Inject import javax.inject.Inject
class AuthDataSource @Inject constructor( class AuthDataSource @Inject constructor(
@@ -1,8 +1,8 @@
package com.meloda.fast.api.datasource package com.meloda.fast.api.network.datasource
import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.VkConversation
import com.meloda.fast.api.network.repo.ConversationsRepo import com.meloda.fast.api.network.repo.ConversationsRepo
import com.meloda.fast.api.network.request.ConversationsGetRequest import com.meloda.fast.api.model.request.ConversationsGetRequest
import com.meloda.fast.database.dao.ConversationsDao import com.meloda.fast.database.dao.ConversationsDao
import javax.inject.Inject import javax.inject.Inject
@@ -0,0 +1,24 @@
package com.meloda.fast.api.network.datasource
import com.meloda.fast.api.model.request.MessagesGetHistoryRequest
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
import com.meloda.fast.api.model.request.MessagesSendRequest
import com.meloda.fast.api.network.repo.MessagesRepo
import com.meloda.fast.database.dao.MessagesDao
import javax.inject.Inject
class MessagesDataSource @Inject constructor(
private val repo: MessagesRepo,
private val dao: MessagesDao
) {
suspend fun getHistory(params: MessagesGetHistoryRequest) =
repo.getHistory(params.map)
suspend fun send(params: MessagesSendRequest) =
repo.send(params.map)
suspend fun markAsImportant(params: MessagesMarkAsImportantRequest) =
repo.markAsImportant(params.map)
}
@@ -1,8 +1,8 @@
package com.meloda.fast.api.datasource package com.meloda.fast.api.network.datasource
import com.meloda.fast.api.model.VkUser import com.meloda.fast.api.model.VkUser
import com.meloda.fast.api.network.repo.UsersRepo import com.meloda.fast.api.network.repo.UsersRepo
import com.meloda.fast.api.network.request.UsersGetRequest import com.meloda.fast.api.model.request.UsersGetRequest
import com.meloda.fast.database.dao.UsersDao import com.meloda.fast.database.dao.UsersDao
import javax.inject.Inject import javax.inject.Inject
@@ -1,9 +1,9 @@
package com.meloda.fast.api.network.repo package com.meloda.fast.api.network.repo
import com.meloda.fast.api.network.VKUrls import com.meloda.fast.api.network.VKUrls
import com.meloda.fast.api.network.response.ResponseAuthDirect import com.meloda.fast.api.model.response.ResponseAuthDirect
import com.meloda.fast.api.network.Answer import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.response.ResponseSendSms import com.meloda.fast.api.model.response.ResponseSendSms
import retrofit2.http.* import retrofit2.http.*
interface AuthRepo { interface AuthRepo {
@@ -3,7 +3,7 @@ package com.meloda.fast.api.network.repo
import com.meloda.fast.api.base.ApiResponse import com.meloda.fast.api.base.ApiResponse
import com.meloda.fast.api.network.Answer import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.VKUrls import com.meloda.fast.api.network.VKUrls
import com.meloda.fast.api.network.response.ConversationsGetResponse import com.meloda.fast.api.model.response.ConversationsGetResponse
import retrofit2.http.FieldMap import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST import retrofit2.http.POST
@@ -1,9 +1,9 @@
package com.meloda.fast.api.network.repo package com.meloda.fast.api.network.repo
import com.meloda.fast.api.base.ApiResponse import com.meloda.fast.api.base.ApiResponse
import com.meloda.fast.api.model.response.MessagesGetHistoryResponse
import com.meloda.fast.api.network.Answer import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.VKUrls import com.meloda.fast.api.network.VKUrls
import com.meloda.fast.api.network.response.MessagesGetHistoryResponse
import retrofit2.http.FieldMap import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST import retrofit2.http.POST
@@ -18,4 +18,8 @@ interface MessagesRepo {
@POST(VKUrls.Messages.Send) @POST(VKUrls.Messages.Send)
suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>> suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>>
@FormUrlEncoded
@POST(VKUrls.Messages.MarkAsImportant)
suspend fun markAsImportant(@FieldMap params: Map<String, String>): Answer<ApiResponse<List<Int>>>
} }
@@ -1,5 +0,0 @@
package com.meloda.fast.api.network.response
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@@ -18,7 +18,7 @@ import com.meloda.fast.database.dao.UsersDao
VkUser::class, VkUser::class,
VkGroup::class VkGroup::class
], ],
version = 15, version = 16,
exportSchema = false exportSchema = false
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@@ -2,10 +2,10 @@ package com.meloda.fast.di
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.meloda.fast.api.datasource.AuthDataSource import com.meloda.fast.api.network.datasource.AuthDataSource
import com.meloda.fast.api.datasource.ConversationsDataSource import com.meloda.fast.api.network.datasource.ConversationsDataSource
import com.meloda.fast.api.datasource.MessagesDataSource import com.meloda.fast.api.network.datasource.MessagesDataSource
import com.meloda.fast.api.datasource.UsersDataSource import com.meloda.fast.api.network.datasource.UsersDataSource
import com.meloda.fast.api.network.AuthInterceptor import com.meloda.fast.api.network.AuthInterceptor
import com.meloda.fast.api.network.ResultCallFactory import com.meloda.fast.api.network.ResultCallFactory
import com.meloda.fast.api.network.repo.AuthRepo import com.meloda.fast.api.network.repo.AuthRepo
@@ -9,7 +9,6 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import coil.load import coil.load
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.api.UserConfig import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.VkConversation
@@ -27,34 +26,38 @@ class ConversationsFragment :
BaseViewModelFragment<ConversationsViewModel>(R.layout.fragment_conversations) { BaseViewModelFragment<ConversationsViewModel>(R.layout.fragment_conversations) {
companion object { companion object {
val TAG: String = ConversationsFragment::class.java.name const val TAG = "ConversationsFragment"
} }
override val viewModel: ConversationsViewModel by viewModels() override val viewModel: ConversationsViewModel by viewModels()
private val binding: FragmentConversationsBinding by viewBinding() private val binding: FragmentConversationsBinding by viewBinding()
private lateinit var adapter: ConversationsAdapter private val adapter: ConversationsAdapter by lazy {
ConversationsAdapter(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireContext(),
super.onViewCreated(view, savedInstanceState) mutableListOf(),
hashMapOf(),
viewModel.loadProfileUser() hashMapOf()
).also {
prepareViews()
adapter = ConversationsAdapter(requireContext(), mutableListOf()).also {
it.itemClickListener = this::onItemClick it.itemClickListener = this::onItemClick
it.itemLongClickListener = this::onItemLongClick it.itemLongClickListener = this::onItemLongClick
} }
}
private var isPaused = false
override fun onPause() {
super.onPause()
isPaused = true
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
prepareViews()
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
viewModel.loadConversations() binding.createChat.setOnClickListener {}
binding.createChat.setOnClickListener {
Snackbar.make(it, "Test Snackbar with action", Snackbar.LENGTH_LONG)
.setAction("Action") {}.show()
}
UserConfig.vkUser.observe(viewLifecycleOwner) { UserConfig.vkUser.observe(viewLifecycleOwner) {
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } } it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
@@ -68,10 +71,16 @@ class ConversationsFragment :
val alpha = 1 - abs(verticalOffset * 0.01).toFloat() val alpha = 1 - abs(verticalOffset * 0.01).toFloat()
// println("offset: $verticalOffset; alpha: $alpha")
binding.avatarContainer.alpha = alpha binding.avatarContainer.alpha = alpha
}) })
if (isPaused) {
isPaused = false
return
}
viewModel.loadProfileUser()
viewModel.loadConversations()
} }
override fun onEvent(event: VKEvent) { override fun onEvent(event: VKEvent) {
@@ -3,13 +3,13 @@ package com.meloda.fast.screens.conversations
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.datasource.ConversationsDataSource import com.meloda.fast.api.network.datasource.ConversationsDataSource
import com.meloda.fast.api.datasource.UsersDataSource import com.meloda.fast.api.network.datasource.UsersDataSource
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.VkUser import com.meloda.fast.api.model.VkUser
import com.meloda.fast.api.network.request.ConversationsGetRequest import com.meloda.fast.api.model.request.ConversationsGetRequest
import com.meloda.fast.api.network.request.UsersGetRequest import com.meloda.fast.api.model.request.UsersGetRequest
import com.meloda.fast.base.viewmodel.BaseViewModel import com.meloda.fast.base.viewmodel.BaseViewModel
import com.meloda.fast.base.viewmodel.StartProgressEvent import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent import com.meloda.fast.base.viewmodel.StopProgressEvent
@@ -6,8 +6,8 @@ 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.VkUtils
import com.meloda.fast.api.datasource.AuthDataSource import com.meloda.fast.api.network.datasource.AuthDataSource
import com.meloda.fast.api.network.request.RequestAuthDirect import com.meloda.fast.api.model.request.RequestAuthDirect
import com.meloda.fast.base.viewmodel.BaseViewModel import com.meloda.fast.base.viewmodel.BaseViewModel
import com.meloda.fast.base.viewmodel.StartProgressEvent import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent import com.meloda.fast.base.viewmodel.StopProgressEvent
@@ -3,19 +3,22 @@ package com.meloda.fast.screens.messages
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import coil.load import coil.load
import com.meloda.fast.R
import com.meloda.fast.api.VkUtils
import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.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
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.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.common.AppGlobal import com.meloda.fast.common.AppGlobal
import com.meloda.fast.databinding.ItemMessageInBinding import com.meloda.fast.databinding.*
import com.meloda.fast.databinding.ItemMessageOutBinding
import com.meloda.fast.databinding.ItemMessageServiceBinding
import com.meloda.fast.util.AndroidUtils import com.meloda.fast.util.AndroidUtils
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -37,6 +40,15 @@ class MessagesHistoryAdapter constructor(
if (viewType == -1) { if (viewType == -1) {
getItem(position).let { getItem(position).let {
if (it.action != null) viewType = SERVICE if (it.action != null) viewType = SERVICE
val attachments = it.attachments ?: return@let
if (attachments.isEmpty()) return@let
if (VkUtils.isAttachmentsHaveOneType(attachments) &&
attachments[0] is VkPhoto
) {
return if (it.isOut) ATTACHMENT_PHOTOS_OUT else ATTACHMENT_PHOTOS_IN
}
if (it.isOut) viewType = OUTGOING if (it.isOut) viewType = OUTGOING
if (!it.isOut) viewType = INCOMING if (!it.isOut) viewType = INCOMING
} }
@@ -52,9 +64,21 @@ class MessagesHistoryAdapter constructor(
return when (viewType) { return when (viewType) {
HEADER -> Header(createEmptyView(60)) HEADER -> Header(createEmptyView(60))
FOOTER -> Footer(createEmptyView(36)) FOOTER -> Footer(createEmptyView(36))
SERVICE -> ServiceMessage(ItemMessageServiceBinding.inflate(inflater, parent, false)) SERVICE -> ServiceMessage(
OUTGOING -> OutgoingMessage(ItemMessageOutBinding.inflate(inflater, parent, false)) ItemMessageServiceBinding.inflate(inflater, parent, false)
INCOMING -> IncomingMessage(ItemMessageInBinding.inflate(inflater, parent, false)) )
ATTACHMENT_PHOTOS_IN -> AttachmentPhotosIncoming(
ItemMessageAttachmentPhotoInBinding.inflate(inflater, parent, false)
)
ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing(
ItemMessageAttachmentPhotoOutBinding.inflate(inflater, parent, false)
)
OUTGOING -> OutgoingMessage(
ItemMessageOutBinding.inflate(inflater, parent, false)
)
INCOMING -> IncomingMessage(
ItemMessageInBinding.inflate(inflater, parent, false)
)
else -> Holder() else -> Holder()
} }
} }
@@ -83,12 +107,87 @@ class MessagesHistoryAdapter constructor(
inner class Footer(v: View) : Holder(v) inner class Footer(v: View) : Holder(v)
inner class AttachmentPhotosIncoming(
private val binding: ItemMessageAttachmentPhotoInBinding
) : Holder(binding.root) {
init {
binding.photo.shapeAppearanceModel = binding.photo.shapeAppearanceModel.withCornerSize {
AndroidUtils.px(12)
}
}
override fun bind(position: Int) {
val message = getItem(position)
val photo = message.attachments?.get(0) as? VkPhoto ?: return
val size = photo.sizeOfType('m') ?: return
binding.photo.layoutParams = FrameLayout.LayoutParams(
AndroidUtils.px(size.width).roundToInt(),
AndroidUtils.px(size.height).roundToInt()
)
binding.photo.load(size.url)
}
}
inner class AttachmentPhotosOutgoing(
private val binding: ItemMessageAttachmentPhotoOutBinding
) : Holder(binding.root) {
init {
binding.photo.shapeAppearanceModel = binding.photo.shapeAppearanceModel.withCornerSize {
AndroidUtils.px(12)
}
}
override fun bind(position: Int) {
val message = getItem(position)
val photo = message.attachments?.get(0) as? VkPhoto ?: return
val size = photo.sizeOfType('m') ?: return
binding.photo.layoutParams = LinearLayoutCompat.LayoutParams(
AndroidUtils.px(size.width).roundToInt(),
AndroidUtils.px(size.height).roundToInt()
)
binding.photo.load(size.url)
}
}
inner class ServiceMessage( inner class ServiceMessage(
private val binding: ItemMessageServiceBinding private val binding: ItemMessageServiceBinding
) : Holder(binding.root) { ) : Holder(binding.root) {
override fun bind(position: Int) { private val youPrefix = context.getString(R.string.you_message_prefix)
override fun bind(position: Int) {
val message = getItem(position)
val messageUser =
if (message.isUser()) profiles[message.fromId]
else null
val messageGroup =
if (message.isGroup()) groups[message.fromId]
else null
message.action ?: return
binding.message.text = VkUtils.getActionMessageText(
message = message,
youPrefix = youPrefix,
profiles = profiles,
groups = groups,
messageUser = messageUser,
messageGroup = messageGroup
)
} }
} }
@@ -176,11 +275,14 @@ class MessagesHistoryAdapter constructor(
} }
companion object { companion object {
private const val INCOMING = 1001 private const val SERVICE = 1
private const val OUTGOING = 1002
private const val SERVICE = 1003
private const val HEADER = 0 private const val HEADER = 0
private const val FOOTER = 2 private const val FOOTER = 2
private const val INCOMING = 3
private const val OUTGOING = 4
private const val ATTACHMENT_PHOTOS_IN = 101
private const val ATTACHMENT_PHOTOS_OUT = 1011
private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() { private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() {
override fun areItemsTheSame( override fun areItemsTheSame(
@@ -12,6 +12,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.api.UserConfig import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.model.VkConversation import com.meloda.fast.api.model.VkConversation
@@ -57,7 +58,10 @@ class MessagesHistoryFragment :
} }
private val adapter: MessagesHistoryAdapter by lazy { private val adapter: MessagesHistoryAdapter by lazy {
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation) MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
it.itemClickListener = this::onItemClick
it.itemLongClickListener = this::onItemLongClick
}
} }
private var timestampTimer: Timer? = null private var timestampTimer: Timer? = null
@@ -183,6 +187,7 @@ class MessagesHistoryFragment :
} }
} }
private fun performAction() { private fun performAction() {
if (action.value == Action.RECORD) { if (action.value == Action.RECORD) {
@@ -203,7 +208,7 @@ class MessagesHistoryFragment :
) )
adapter.add(message) adapter.add(message)
adapter.notifyItemRangeInserted(adapter.lastPosition - 1, 1) adapter.notifyDataSetChanged()
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition) binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
binding.message.clear() binding.message.clear()
@@ -217,6 +222,7 @@ class MessagesHistoryFragment :
override fun onEvent(event: VKEvent) { override fun onEvent(event: VKEvent) {
when (event) { when (event) {
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
is MessagesLoaded -> refreshMessages(event) is MessagesLoaded -> refreshMessages(event)
is StartProgressEvent -> onProgressStarted() is StartProgressEvent -> onProgressStarted()
is StopProgressEvent -> onProgressStopped() is StopProgressEvent -> onProgressStopped()
@@ -264,6 +270,19 @@ class MessagesHistoryFragment :
} }
} }
private fun markMessagesAsImportant(event: MessagesMarkAsImportant) {
var changed = false
for (i in adapter.values.indices) {
val message = adapter.values[i]
if (event.messagesIds.contains(message.id)) {
if (!changed) changed = true
adapter.values[i] = message.copy(important = event.important)
}
}
if (changed) adapter.notifyDataSetChanged()
}
private fun refreshMessages(event: MessagesLoaded) { private fun refreshMessages(event: MessagesLoaded) {
adapter.profiles += event.profiles adapter.profiles += event.profiles
adapter.groups += event.groups adapter.groups += event.groups
@@ -282,4 +301,30 @@ class MessagesHistoryFragment :
else binding.recyclerView.scrollToPosition(adapter.lastPosition) else binding.recyclerView.scrollToPosition(adapter.lastPosition)
} }
private fun onItemClick(position: Int) {
val message = adapter.values[position]
val important = if (message.important) "Unmark as important" else "Mark as important"
val params = arrayOf(important)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setItems(params) { _, which ->
if (which == 0) {
viewModel.markAsImportant(
messagesIds = listOf(message.id),
important = !message.important
)
}
}
dialog.show()
}
private fun onItemLongClick(position: Int): Boolean {
return true
}
} }
@@ -1,13 +1,14 @@
package com.meloda.fast.screens.messages package com.meloda.fast.screens.messages
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.meloda.fast.api.datasource.MessagesDataSource
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
import com.meloda.fast.api.model.VkUser import com.meloda.fast.api.model.VkUser
import com.meloda.fast.api.network.request.MessagesGetHistoryRequest import com.meloda.fast.api.model.request.MessagesGetHistoryRequest
import com.meloda.fast.api.network.request.MessagesSendRequest import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
import com.meloda.fast.api.model.request.MessagesSendRequest
import com.meloda.fast.api.network.datasource.MessagesDataSource
import com.meloda.fast.base.viewmodel.BaseViewModel import com.meloda.fast.base.viewmodel.BaseViewModel
import com.meloda.fast.base.viewmodel.StartProgressEvent import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent import com.meloda.fast.base.viewmodel.StopProgressEvent
@@ -109,6 +110,33 @@ class MessagesHistoryViewModel @Inject constructor(
}) })
} }
fun markAsImportant(
messagesIds: List<Int>,
important: Boolean
) = viewModelScope.launch {
makeJob({
dataSource.markAsImportant(
MessagesMarkAsImportantRequest(
messagesIds = messagesIds,
important = important
)
)
},
onAnswer = {
val response = it.response ?: return@makeJob
sendEvent(
MessagesMarkAsImportant(
messagesIds = response,
important = important
)
)
},
onError = {
val throwable = it
val i = 0
})
}
} }
data class MessagesLoaded( data class MessagesLoaded(
@@ -118,3 +146,8 @@ data class MessagesLoaded(
val profiles: HashMap<Int, VkUser>, val profiles: HashMap<Int, VkUser>,
val groups: HashMap<Int, VkGroup> val groups: HashMap<Int, VkGroup>
) : VKEvent() ) : VKEvent()
data class MessagesMarkAsImportant(
val messagesIds: List<Int>,
val important: Boolean
) : VKEvent()
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />
</FrameLayout>
</layout>
@@ -0,0 +1,20 @@
<?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:gravity="end"
android:padding="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
@@ -3,15 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="12dp"
android:paddingVertical="2.5dp">
<TextView <TextView
android:id="@+id/message" android:id="@+id/message"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Service" /> android:textColor="?textColorService"
tools:text="Service" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/conversationsFragment">
<include app:graph="@navigation/messages" />
</navigation>
+15 -2
View File
@@ -10,6 +10,10 @@
@android:color/transparent @android:color/transparent
</item> </item>
<item name="android:windowLightStatusBar">true</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</item> <item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorOnPrimary">@color/colorOnPrimary</item> <item name="colorOnPrimary">@color/colorOnPrimary</item>
@@ -18,9 +22,18 @@
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item> <item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item> <item name="colorOnSecondary">@color/colorOnSecondary</item>
<item name="android:windowLightStatusBar">true</item> <item name="colorSecondary2">@color/colorSecondary2</item>
<item name="colorSecondary2Variant">@color/colorSecondary2Variant</item>
<item name="colorOnSecondary2">@color/colorOnSecondary2</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item> <item name="colorSecondary3">@color/colorSecondary3</item>
<item name="colorSecondary3Variant">@color/colorSecondary3Variant</item>
<item name="colorOnSecondary3">@color/colorOnSecondary3</item>
<item name="textColorPrimary">@color/textColorPrimary</item>
<item name="textColorSecondary">@color/textColorSecondary</item>
<item name="textColorSecondaryVariant">@color/textColorSecondaryVariant</item>
<item name="textColorService">@color/textColorService</item>
<item name="dialogCornerRadius">12dp</item> <item name="dialogCornerRadius">12dp</item>
</style> </style>
+1
View File
@@ -25,5 +25,6 @@
<color name="n2_10">@android:color/system_neutral2_10</color> <color name="n2_10">@android:color/system_neutral2_10</color>
<color name="n2_100">@android:color/system_neutral2_100</color> <color name="n2_100">@android:color/system_neutral2_100</color>
<color name="n2_500">@android:color/system_neutral2_500</color> <color name="n2_500">@android:color/system_neutral2_500</color>
<color name="n2_600">@android:color/system_neutral2_600</color>
</resources> </resources>
+4 -3
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<attr name="textColorPrimary" format="color"/> <attr name="textColorPrimary" format="color" />
<attr name="textColorSecondary" format="color"/> <attr name="textColorSecondary" format="color" />
<attr name="textColorSecondaryVariant" format="color"/> <attr name="textColorSecondaryVariant" format="color" />
<attr name="textColorService" format="color" />
<attr name="colorSecondary2" format="color" /> <attr name="colorSecondary2" format="color" />
<attr name="colorSecondary2Variant" format="color" /> <attr name="colorSecondary2Variant" format="color" />
+2
View File
@@ -22,6 +22,7 @@
<color name="textColorPrimary">@color/n1_900</color> <color name="textColorPrimary">@color/n1_900</color>
<color name="textColorSecondary">@color/n1_900</color> <color name="textColorSecondary">@color/n1_900</color>
<color name="textColorSecondaryVariant">@color/n2_500</color> <color name="textColorSecondaryVariant">@color/n2_500</color>
<color name="textColorService">@color/n2_600</color>
<color name="colorSurface">@color/a1_0</color> <color name="colorSurface">@color/a1_0</color>
@@ -50,5 +51,6 @@
<color name="n2_10">#FDFBFE</color> <color name="n2_10">#FDFBFE</color>
<color name="n2_100">#E0E2EB</color> <color name="n2_100">#E0E2EB</color>
<color name="n2_500">#74767D</color> <color name="n2_500">#74767D</color>
<color name="n2_600">#5C5E65</color>
</resources> </resources>
+1
View File
@@ -30,6 +30,7 @@
<item name="textColorPrimary">@color/textColorPrimary</item> <item name="textColorPrimary">@color/textColorPrimary</item>
<item name="textColorSecondary">@color/textColorSecondary</item> <item name="textColorSecondary">@color/textColorSecondary</item>
<item name="textColorSecondaryVariant">@color/textColorSecondaryVariant</item> <item name="textColorSecondaryVariant">@color/textColorSecondaryVariant</item>
<item name="textColorService">@color/textColorService</item>
<item name="colorSurface">@color/colorSurface</item> <item name="colorSurface">@color/colorSurface</item>