forked from melod1n/fast-messenger
simplifying base models
new attachments
This commit is contained in:
@@ -19,7 +19,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "com.meloda.fast"
|
||||
minSdk = 23
|
||||
targetSdk = 31
|
||||
targetSdk = 30
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
@@ -75,15 +75,17 @@ kapt {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31")
|
||||
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
|
||||
implementation("androidx.work:work-runtime-ktx:2.6.0")
|
||||
|
||||
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
||||
|
||||
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
|
||||
implementation("com.google.android.material:material:1.5.0-alpha03")
|
||||
implementation("androidx.core:core-ktx:1.7.0-alpha02")
|
||||
implementation("androidx.core:core-ktx:1.7.0-beta01")
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
|
||||
@@ -28,15 +28,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.MinuteReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.ACTION_TIME_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.meloda.fast.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import android.viewbinding.library.activity.viewBinding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.base.BaseActivity
|
||||
import com.meloda.fast.databinding.ActivityMainBinding
|
||||
@@ -14,6 +15,10 @@ class MainActivity : BaseActivity(R.layout.activity_main) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
lifecycleScope.launchWhenStarted {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,27 +13,22 @@ import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.*
|
||||
import com.meloda.fast.api.model.base.BaseVkMessage
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
|
||||
import com.meloda.fast.api.network.VkErrors
|
||||
|
||||
object VkUtils {
|
||||
|
||||
fun isValidationRequired(throwable: Throwable): Boolean {
|
||||
if (throwable !is VKException) return false
|
||||
return throwable.error == VkErrors.NEED_VALIDATION
|
||||
fun prepareMessageText(text: String, forConversations: Boolean? = null): String {
|
||||
return text.apply {
|
||||
if (forConversations == true) replace("\n", "")
|
||||
|
||||
replace("&", "&")
|
||||
}
|
||||
}
|
||||
|
||||
fun isCaptchaRequired(throwable: Throwable): Boolean {
|
||||
if (throwable !is VKException) return false
|
||||
return throwable.error == VkErrors.NEED_CAPTCHA
|
||||
}
|
||||
fun isPreviousMessageSentFiveMinutesAgo(prevMessage: VkMessage?, message: VkMessage?) =
|
||||
prevMessage != null && message != null && (message.date - prevMessage.date >= 300)
|
||||
|
||||
fun prepareMessageText(text: String?): String? {
|
||||
if (text == null) return null
|
||||
|
||||
return text
|
||||
.replace("\n", " ")
|
||||
.replace("&", "&")
|
||||
}
|
||||
fun isPreviousMessageFromDifferentSender(prevMessage: VkMessage?, message: VkMessage?) =
|
||||
prevMessage != null && message != null && prevMessage.fromId != message.fromId
|
||||
|
||||
fun parseForwards(baseForwards: List<BaseVkMessage>?): List<VkMessage>? {
|
||||
if (baseForwards.isNullOrEmpty()) return null
|
||||
@@ -64,21 +59,15 @@ object VkUtils {
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
|
||||
val audio = baseAttachment.audio ?: continue
|
||||
attachments += VkAudio(
|
||||
link = audio.url
|
||||
)
|
||||
attachments += audio.asVkAudio()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.FILE -> {
|
||||
val file = baseAttachment.file ?: continue
|
||||
attachments += VkFile(
|
||||
link = file.url
|
||||
)
|
||||
attachments += file.asVkFile()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.LINK -> {
|
||||
val link = baseAttachment.link ?: continue
|
||||
attachments += VkLink(
|
||||
link = link.url
|
||||
)
|
||||
attachments += link.asVkLink()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.MINI_APP -> {
|
||||
val miniApp = baseAttachment.miniApp ?: continue
|
||||
@@ -89,7 +78,7 @@ object VkUtils {
|
||||
BaseVkAttachmentItem.AttachmentType.VOICE -> {
|
||||
val voiceMessage = baseAttachment.voiceMessage ?: continue
|
||||
attachments += VkVoiceMessage(
|
||||
link = voiceMessage.linkMp3
|
||||
link = voiceMessage.link_mp3
|
||||
)
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.STICKER -> {
|
||||
@@ -99,14 +88,12 @@ object VkUtils {
|
||||
BaseVkAttachmentItem.AttachmentType.GIFT -> {
|
||||
val gift = baseAttachment.gift ?: continue
|
||||
attachments += VkGift(
|
||||
link = gift.thumb48
|
||||
link = gift.thumb_48
|
||||
)
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.WALL -> {
|
||||
val wall = baseAttachment.wall ?: continue
|
||||
attachments += VkWall(
|
||||
id = wall.id
|
||||
)
|
||||
attachments += wall.asVkWall()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.GRAFFITI -> {
|
||||
val graffiti = baseAttachment.graffiti ?: continue
|
||||
@@ -129,15 +116,27 @@ object VkUtils {
|
||||
BaseVkAttachmentItem.AttachmentType.CALL -> {
|
||||
val call = baseAttachment.call ?: continue
|
||||
attachments += VkCall(
|
||||
initiatorId = call.initiatorId
|
||||
initiatorId = call.initiator_id
|
||||
)
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> {
|
||||
val groupCall = baseAttachment.groupCall ?: continue
|
||||
attachments += VkGroupCall(
|
||||
initiatorId = groupCall.initiatorId
|
||||
initiatorId = groupCall.initiator_id
|
||||
)
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.CURATOR -> {
|
||||
val curator = baseAttachment.curator ?: continue
|
||||
attachments += curator.asVkCurator()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.EVENT -> {
|
||||
val event = baseAttachment.event ?: continue
|
||||
attachments += event.asVkEvent()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.STORY -> {
|
||||
val story = baseAttachment.story ?: continue
|
||||
attachments += story.asVkStory()
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
}
|
||||
@@ -145,177 +144,12 @@ object VkUtils {
|
||||
return attachments
|
||||
}
|
||||
|
||||
fun getActionConversationText(
|
||||
message: VkMessage,
|
||||
youPrefix: String,
|
||||
profiles: HashMap<Int, VkUser>? = null,
|
||||
groups: HashMap<Int, VkGroup>? = null,
|
||||
messageUser: VkUser? = null,
|
||||
messageGroup: VkGroup? = null
|
||||
): String? {
|
||||
return when (message.getPreparedAction()) {
|
||||
VkMessage.Action.CHAT_CREATE -> {
|
||||
val text = message.actionText ?: return null
|
||||
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix created «$text»"
|
||||
}
|
||||
VkMessage.Action.CHAT_TITLE_UPDATE -> {
|
||||
val text = message.actionText ?: return null
|
||||
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix renamed chat to «$text»"
|
||||
}
|
||||
VkMessage.Action.CHAT_PHOTO_UPDATE -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix updated the chat photo"
|
||||
}
|
||||
VkMessage.Action.CHAT_PHOTO_REMOVE -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix deleted the chat photo"
|
||||
}
|
||||
VkMessage.Action.CHAT_KICK_USER -> {
|
||||
val memberId = message.actionMemberId ?: return null
|
||||
val isUser = memberId > 0
|
||||
val isGroup = memberId < 0
|
||||
|
||||
val actionUser = profiles?.get(memberId)
|
||||
val actionGroup = groups?.get(memberId)
|
||||
|
||||
if (isUser && actionUser == null) return null
|
||||
if (isGroup && actionGroup == null) return null
|
||||
|
||||
if (memberId == message.fromId) {
|
||||
val prefix = if (memberId == UserConfig.userId) youPrefix
|
||||
else actionUser.toString()
|
||||
"$prefix left the chat"
|
||||
} else {
|
||||
val prefix =
|
||||
if (message.fromId == UserConfig.userId) youPrefix
|
||||
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
|
||||
val postfix =
|
||||
if (memberId == UserConfig.userId) youPrefix.lowercase()
|
||||
else actionUser.toString()
|
||||
"$prefix kicked $postfix"
|
||||
}
|
||||
}
|
||||
VkMessage.Action.CHAT_INVITE_USER -> {
|
||||
val memberId = message.actionMemberId ?: 0
|
||||
val isUser = memberId > 0
|
||||
val isGroup = memberId < 0
|
||||
|
||||
val actionUser = profiles?.get(memberId)
|
||||
val actionGroup = groups?.get(memberId)
|
||||
|
||||
if (isUser && actionUser == null) return null
|
||||
if (isGroup && actionGroup == null) return null
|
||||
|
||||
if (memberId == message.fromId) {
|
||||
val prefix = if (memberId == UserConfig.userId) youPrefix
|
||||
else actionUser.toString()
|
||||
"$prefix returned the chat"
|
||||
} else {
|
||||
val prefix = if (message.fromId == UserConfig.userId) youPrefix
|
||||
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
|
||||
val postfix =
|
||||
if (memberId == UserConfig.userId) youPrefix.lowercase()
|
||||
else actionUser.toString()
|
||||
"$prefix invited $postfix"
|
||||
}
|
||||
}
|
||||
VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix joined the chat via link"
|
||||
}
|
||||
VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix joined the call via link"
|
||||
}
|
||||
VkMessage.Action.CHAT_PIN_MESSAGE -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val actionMessage = message.actionMessage
|
||||
|
||||
"$prefix pinned message ${if (actionMessage == null) "" else "«$actionMessage»"}".trim()
|
||||
}
|
||||
VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix unpinned message"
|
||||
}
|
||||
VkMessage.Action.CHAT_SCREENSHOT -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isGroup() -> messageGroup?.name
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix took a screenshot"
|
||||
}
|
||||
VkMessage.Action.CHAT_STYLE_UPDATE -> {
|
||||
val prefix = when {
|
||||
message.fromId == UserConfig.userId -> youPrefix
|
||||
message.isUser() -> messageUser?.toString()
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
"$prefix changed chat theme"
|
||||
}
|
||||
null -> null
|
||||
else -> "[${message.action}]"
|
||||
}
|
||||
}
|
||||
|
||||
fun getActionMessageText(
|
||||
context: Context,
|
||||
message: VkMessage,
|
||||
youPrefix: String,
|
||||
profiles: HashMap<Int, VkUser>? = null,
|
||||
groups: HashMap<Int, VkGroup>? = null,
|
||||
profiles: Map<Int, VkUser>? = null,
|
||||
groups: Map<Int, VkGroup>? = null,
|
||||
messageUser: VkUser? = null,
|
||||
messageGroup: VkGroup? = null
|
||||
): SpannableString? {
|
||||
@@ -330,7 +164,8 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix created «$text»"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_created, prefix, text)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
@@ -351,8 +186,8 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix renamed chat to «$text»"
|
||||
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_renamed, prefix, text)
|
||||
val startIndex = spanText.indexOf(text)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
@@ -370,7 +205,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix updated the chat photo"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_photo_update, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -383,7 +220,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix deleted the chat photo"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_photo_remove, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -402,7 +241,10 @@ object VkUtils {
|
||||
if (memberId == message.fromId) {
|
||||
val prefix = if (memberId == UserConfig.userId) youPrefix
|
||||
else actionUser.toString()
|
||||
val spanText = "$prefix left the chat"
|
||||
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_user_left, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -410,11 +252,17 @@ object VkUtils {
|
||||
val prefix =
|
||||
if (message.fromId == UserConfig.userId) youPrefix
|
||||
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
|
||||
|
||||
val postfix =
|
||||
if (memberId == UserConfig.userId) youPrefix.lowercase()
|
||||
else actionUser.toString()
|
||||
|
||||
val spanText = "$prefix kicked $postfix"
|
||||
val spanText =
|
||||
context.getString(
|
||||
R.string.message_action_chat_user_kicked,
|
||||
prefix,
|
||||
postfix
|
||||
)
|
||||
val startIndex = spanText.indexOf(postfix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
@@ -439,18 +287,27 @@ object VkUtils {
|
||||
if (memberId == message.fromId) {
|
||||
val prefix = if (memberId == UserConfig.userId) youPrefix
|
||||
else actionUser.toString()
|
||||
val spanText = "$prefix returned the chat"
|
||||
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_user_returned, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
} else {
|
||||
val prefix = if (message.fromId == UserConfig.userId) youPrefix
|
||||
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
|
||||
|
||||
val postfix =
|
||||
if (memberId == UserConfig.userId) youPrefix.lowercase()
|
||||
else actionUser.toString()
|
||||
|
||||
val spanText = "$prefix invited $postfix"
|
||||
val spanText =
|
||||
context.getString(
|
||||
R.string.message_action_chat_user_invited,
|
||||
prefix,
|
||||
postfix
|
||||
)
|
||||
val startIndex = spanText.indexOf(postfix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
@@ -468,7 +325,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix joined the chat via link"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_user_joined_by_link, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -480,7 +339,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix joined the call via link"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_user_joined_by_call_link, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -493,16 +354,11 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val actionMessage = message.actionMessage ?: return null
|
||||
|
||||
val spanText = "$prefix pinned message «$actionMessage»"
|
||||
val startIndex = spanText.indexOf(actionMessage)
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_pin_message, prefix).trim()
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
it.setSpan(
|
||||
StyleSpan(Typeface.BOLD), startIndex, startIndex + actionMessage.length, 0
|
||||
)
|
||||
}
|
||||
}
|
||||
VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
|
||||
@@ -513,7 +369,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix unpinned message"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_unpin_message, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -526,7 +384,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix took a screenshot"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_screenshot, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -538,7 +398,9 @@ object VkUtils {
|
||||
else -> return null
|
||||
} ?: return null
|
||||
|
||||
val spanText = "$prefix changed chat theme"
|
||||
val spanText =
|
||||
context.getString(R.string.message_action_chat_style_update, prefix)
|
||||
|
||||
SpannableString(spanText).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, prefix.length, 0)
|
||||
}
|
||||
@@ -548,6 +410,26 @@ object VkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
fun getActionConversationText(
|
||||
context: Context,
|
||||
message: VkMessage,
|
||||
youPrefix: String,
|
||||
profiles: HashMap<Int, VkUser>? = null,
|
||||
groups: HashMap<Int, VkGroup>? = null,
|
||||
messageUser: VkUser? = null,
|
||||
messageGroup: VkGroup? = null
|
||||
): String? {
|
||||
return getActionMessageText(
|
||||
context = context,
|
||||
message = message,
|
||||
youPrefix = youPrefix,
|
||||
profiles = profiles,
|
||||
groups = groups,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup
|
||||
)?.toString()
|
||||
}
|
||||
|
||||
fun getForwardsConversationText(context: Context, message: VkMessage): String? {
|
||||
if (message.forwards.isNullOrEmpty()) return null
|
||||
|
||||
@@ -570,10 +452,19 @@ object VkUtils {
|
||||
|
||||
return message.attachments?.let { attachments ->
|
||||
if (attachments.size == 1) {
|
||||
getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) }
|
||||
getAttachmentTypeByClass(attachments[0])?.let {
|
||||
getAttachmentTextByType(
|
||||
context,
|
||||
it
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (isAttachmentsHaveOneType(attachments)) {
|
||||
getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) }
|
||||
getAttachmentTypeByClass(attachments[0])?.let {
|
||||
getAttachmentTextByType(
|
||||
context, it, attachments.size
|
||||
)
|
||||
}
|
||||
} else {
|
||||
context.getString(R.string.message_attachments_many)
|
||||
}
|
||||
@@ -622,6 +513,8 @@ object VkUtils {
|
||||
BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> R.drawable.ic_attachment_wall_reply
|
||||
BaseVkAttachmentItem.AttachmentType.CALL -> R.drawable.ic_attachment_call
|
||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_attachment_group_call
|
||||
BaseVkAttachmentItem.AttachmentType.STORY -> R.drawable.ic_attachment_story
|
||||
else -> return null
|
||||
}
|
||||
|
||||
return ContextCompat.getDrawable(context, resId)
|
||||
@@ -657,14 +550,56 @@ object VkUtils {
|
||||
is VkWallReply -> BaseVkAttachmentItem.AttachmentType.WALL_REPLY
|
||||
is VkCall -> BaseVkAttachmentItem.AttachmentType.CALL
|
||||
is VkGroupCall -> BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS
|
||||
is VkEvent -> BaseVkAttachmentItem.AttachmentType.EVENT
|
||||
is VkCurator -> BaseVkAttachmentItem.AttachmentType.CURATOR
|
||||
is VkStory -> BaseVkAttachmentItem.AttachmentType.STORY
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getAttachmentTextByType(attachmentType: BaseVkAttachmentItem.AttachmentType): String? {
|
||||
fun getAttachmentTextByType(
|
||||
context: Context,
|
||||
attachmentType: BaseVkAttachmentItem.AttachmentType,
|
||||
size: Int = 1
|
||||
): String {
|
||||
return when (attachmentType) {
|
||||
BaseVkAttachmentItem.AttachmentType.PHOTO ->
|
||||
context.resources.getQuantityString(R.plurals.attachment_photos, size, size)
|
||||
BaseVkAttachmentItem.AttachmentType.VIDEO ->
|
||||
context.resources.getQuantityString(R.plurals.attachment_videos, size, size)
|
||||
BaseVkAttachmentItem.AttachmentType.AUDIO ->
|
||||
context.resources.getQuantityString(R.plurals.attachment_audios, size, size)
|
||||
BaseVkAttachmentItem.AttachmentType.FILE ->
|
||||
context.resources.getQuantityString(R.plurals.attachment_files, size, size)
|
||||
BaseVkAttachmentItem.AttachmentType.LINK ->
|
||||
context.resources.getString(R.string.message_attachments_link)
|
||||
BaseVkAttachmentItem.AttachmentType.VOICE ->
|
||||
context.resources.getString(R.string.message_attachments_voice)
|
||||
BaseVkAttachmentItem.AttachmentType.MINI_APP ->
|
||||
context.resources.getString(R.string.message_attachments_mini_app)
|
||||
BaseVkAttachmentItem.AttachmentType.STICKER ->
|
||||
context.resources.getString(R.string.message_attachments_sticker)
|
||||
BaseVkAttachmentItem.AttachmentType.GIFT ->
|
||||
context.resources.getString(R.string.message_attachments_gift)
|
||||
BaseVkAttachmentItem.AttachmentType.WALL ->
|
||||
context.resources.getString(R.string.message_attachments_wall)
|
||||
BaseVkAttachmentItem.AttachmentType.GRAFFITI ->
|
||||
context.resources.getString(R.string.message_attachments_graffiti)
|
||||
BaseVkAttachmentItem.AttachmentType.POLL ->
|
||||
context.resources.getString(R.string.message_attachments_poll)
|
||||
BaseVkAttachmentItem.AttachmentType.WALL_REPLY ->
|
||||
context.resources.getString(R.string.message_attachments_wall_reply)
|
||||
BaseVkAttachmentItem.AttachmentType.CALL ->
|
||||
context.resources.getString(R.string.message_attachments_call)
|
||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS ->
|
||||
context.resources.getString(R.string.message_attachments_call_in_progress)
|
||||
BaseVkAttachmentItem.AttachmentType.EVENT ->
|
||||
context.resources.getString(R.string.message_attachments_event)
|
||||
BaseVkAttachmentItem.AttachmentType.CURATOR ->
|
||||
context.resources.getString(R.string.message_attachments_curator)
|
||||
BaseVkAttachmentItem.AttachmentType.STORY ->
|
||||
context.resources.getString(R.string.message_attachments_story)
|
||||
else -> attachmentType.value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,11 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkAudio(
|
||||
val link: String
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val artist: String,
|
||||
val url: String,
|
||||
val duration: Int
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkCurator(
|
||||
val id: Int
|
||||
) : VkAttachment()
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkEvent(
|
||||
val id: Int
|
||||
) : VkAttachment()
|
||||
@@ -5,7 +5,11 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkFile(
|
||||
val link: String
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val ext: String,
|
||||
val size: Int,
|
||||
val url: String
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -5,7 +5,12 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkLink(
|
||||
val link: String
|
||||
val url: String,
|
||||
val title: String?,
|
||||
val caption: String?,
|
||||
val photo: VkPhoto?,
|
||||
val target: String,
|
||||
val isFavorite: Boolean
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.Size
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkPhoto
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -12,7 +12,7 @@ data class VkPhoto(
|
||||
val ownerId: Int,
|
||||
val hasTags: Boolean,
|
||||
val accessKey: String?,
|
||||
val sizes: List<Size>,
|
||||
val sizes: List<BaseVkPhoto.Size>,
|
||||
val text: String,
|
||||
val userId: Int?
|
||||
) : VkAttachment() {
|
||||
@@ -20,7 +20,7 @@ data class VkPhoto(
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
fun sizeOfType(type: Char): Size? {
|
||||
fun sizeOfType(type: Char): BaseVkPhoto.Size? {
|
||||
for (size in sizes) {
|
||||
if (size.type == type.toString())
|
||||
return size
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkSticker
|
||||
import com.meloda.fast.api.model.base.attachments.StickerSize
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -16,7 +15,7 @@ data class VkSticker(
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
fun urlForSize(@StickerSize size: Int): String? {
|
||||
fun urlForSize(size: Int): String? {
|
||||
for (image in images) {
|
||||
if (image.width == size) return image.url
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkStory(
|
||||
val id: Int,
|
||||
val ownerId: Int,
|
||||
val date: Int,
|
||||
val photo: VkPhoto
|
||||
) : VkAttachment()
|
||||
@@ -8,7 +8,7 @@ import kotlinx.parcelize.Parcelize
|
||||
data class VkVideo(
|
||||
val id: Int,
|
||||
val images: List<BaseVkVideo.Image>,
|
||||
val firstFrames: List<BaseVkVideo.FirstFrame>
|
||||
val firstFrames: List<BaseVkVideo.FirstFrame>?
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkWall(
|
||||
val id: Int
|
||||
val id: Int,
|
||||
val fromId: Int,
|
||||
val toId: Int,
|
||||
val date: Int,
|
||||
val text: String,
|
||||
val attachments: List<BaseVkAttachmentItem>?,
|
||||
val comments: Int,
|
||||
val likes: Int,
|
||||
val reposts: Int,
|
||||
val views: Int,
|
||||
val isFavorite: Boolean,
|
||||
val accessKey: String
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -1,90 +1,70 @@
|
||||
package com.meloda.fast.api.model.base
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkGroupCall
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkConversation(
|
||||
val peer: Peer,
|
||||
@SerializedName("last_message_id")
|
||||
val lastMessageId: Int,
|
||||
@SerializedName("in_read")
|
||||
val inRead: Int,
|
||||
@SerializedName("out_read")
|
||||
val outRead: Int,
|
||||
@SerializedName("sort_id")
|
||||
val sortId: SortId,
|
||||
@SerializedName("last_conversation_message_id")
|
||||
val lastConversationMessageId: Int,
|
||||
@SerializedName("is_marked_unread")
|
||||
val isMarkedUnread: Boolean,
|
||||
val last_message_id: Int,
|
||||
val in_read: Int,
|
||||
val out_read: Int,
|
||||
val sort_id: SortId,
|
||||
val last_conversation_message_id: Int,
|
||||
val is_marked_unread: Boolean,
|
||||
val important: Boolean,
|
||||
@SerializedName("push_settings")
|
||||
val pushSettings: PushSettings,
|
||||
@SerializedName("can_write")
|
||||
val canWrite: CanWrite,
|
||||
@SerializedName("can_send_money")
|
||||
val canSendMoney: Boolean,
|
||||
@SerializedName("can_receive_money")
|
||||
val canReceiveMoney: Boolean,
|
||||
@SerializedName("chat_settings")
|
||||
val chatSettings: ChatSettings?,
|
||||
@SerializedName("call_in_progress")
|
||||
val callInProgress: CallInProgress?,
|
||||
@SerializedName("unread_count")
|
||||
val unreadCount: Int?
|
||||
val push_settings: PushSettings,
|
||||
val can_write: CanWrite,
|
||||
val can_send_money: Boolean,
|
||||
val can_receive_money: Boolean,
|
||||
val chat_settings: ChatSettings?,
|
||||
val call_in_progress: CallInProgress?,
|
||||
val unread_count: Int?
|
||||
) : Parcelable {
|
||||
|
||||
fun asVkConversation(lastMessage: VkMessage? = null) = VkConversation(
|
||||
id = peer.id,
|
||||
title = chatSettings?.title,
|
||||
photo200 = chatSettings?.photo?.photo200,
|
||||
title = chat_settings?.title,
|
||||
photo200 = chat_settings?.photo?.photo_200,
|
||||
type = peer.type,
|
||||
callInProgress = callInProgress != null,
|
||||
isPhantom = chatSettings?.isDisappearing == true,
|
||||
lastConversationMessageId = lastConversationMessageId,
|
||||
inRead = inRead,
|
||||
outRead = outRead,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
lastMessageId = lastMessageId,
|
||||
unreadCount = unreadCount,
|
||||
membersCount = chatSettings?.membersCount,
|
||||
ownerId = chatSettings?.ownerId,
|
||||
isPinned = sortId.majorId > 0
|
||||
callInProgress = call_in_progress != null,
|
||||
isPhantom = chat_settings?.is_disappearing == true,
|
||||
lastConversationMessageId = last_conversation_message_id,
|
||||
inRead = in_read,
|
||||
outRead = out_read,
|
||||
isMarkedUnread = is_marked_unread,
|
||||
lastMessageId = last_message_id,
|
||||
unreadCount = unread_count,
|
||||
membersCount = chat_settings?.members_count,
|
||||
ownerId = chat_settings?.owner_id,
|
||||
isPinned = sort_id.major_id > 0
|
||||
).apply {
|
||||
this.lastMessage = lastMessage
|
||||
this.pinnedMessage = chatSettings?.pinnedMessage?.asVkMessage()
|
||||
this.pinnedMessage = chat_settings?.pinned_message?.asVkMessage()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Peer(
|
||||
val id: Int,
|
||||
val type: String,
|
||||
@SerializedName("local_id")
|
||||
val localId: Int
|
||||
val local_id: Int
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class SortId(
|
||||
@SerializedName("major_id")
|
||||
val majorId: Int,
|
||||
@SerializedName("minor_id")
|
||||
val minorId: Int
|
||||
val major_id: Int,
|
||||
val minor_id: Int
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class PushSettings(
|
||||
@SerializedName("disabled_forever")
|
||||
val disabledForever: Boolean,
|
||||
@SerializedName("no_sound")
|
||||
val noSound: Boolean,
|
||||
@SerializedName("disabled_mentions")
|
||||
val disabledMentions: Boolean,
|
||||
@SerializedName("disabled_mass_mentions")
|
||||
val disabledMassMentions: Boolean
|
||||
val disabled_forever: Boolean,
|
||||
val no_sound: Boolean,
|
||||
val disabled_mentions: Boolean,
|
||||
val disabled_mass_mentions: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
@@ -94,75 +74,50 @@ data class BaseVkConversation(
|
||||
|
||||
@Parcelize
|
||||
data class ChatSettings(
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
val owner_id: Int,
|
||||
val title: String,
|
||||
val state: String,
|
||||
val acl: Acl,
|
||||
@SerializedName("members_count")
|
||||
val membersCount: Int,
|
||||
@SerializedName("friends_count")
|
||||
val friendsCount: Int,
|
||||
val members_count: Int,
|
||||
val friends_count: Int,
|
||||
val photo: Photo?,
|
||||
@SerializedName("admin_ids")
|
||||
val adminsIds: List<Int>,
|
||||
@SerializedName("active_ids")
|
||||
val activeIds: List<Int>,
|
||||
@SerializedName("is_group_channel")
|
||||
val isGroupChannel: Boolean,
|
||||
@SerializedName("is_disappearing")
|
||||
val isDisappearing: Boolean,
|
||||
@SerializedName("is_service")
|
||||
val isService: Boolean,
|
||||
val admin_ids: List<Int>,
|
||||
val active_ids: List<Int>,
|
||||
val is_group_channel: Boolean,
|
||||
val is_disappearing: Boolean,
|
||||
val is_service: Boolean,
|
||||
val theme: String?,
|
||||
@SerializedName("pinned_message")
|
||||
val pinnedMessage: BaseVkMessage?
|
||||
val pinned_message: BaseVkMessage?
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Acl(
|
||||
@SerializedName("can_change_info")
|
||||
val canChangeInfo: Boolean,
|
||||
@SerializedName("can_change_invite_link")
|
||||
val canChangeInviteLink: Boolean,
|
||||
@SerializedName("can_change_pin")
|
||||
val canChangePin: Boolean,
|
||||
@SerializedName("can_invite")
|
||||
val canInvite: Boolean,
|
||||
@SerializedName("can_promote_users")
|
||||
val canPromoteUsers: Boolean,
|
||||
@SerializedName("can_see_invite_link")
|
||||
val canSeeInviteLink: Boolean,
|
||||
@SerializedName("can_moderate")
|
||||
val canModerate: Boolean,
|
||||
@SerializedName("can_copy_chat")
|
||||
val canCopyChat: Boolean,
|
||||
@SerializedName("can_call")
|
||||
val canCall: Boolean,
|
||||
@SerializedName("can_use_mass_mentions")
|
||||
val canUseMassMentions: Boolean,
|
||||
@SerializedName("can_change_style")
|
||||
val canChangeStyle: Boolean
|
||||
val can_change_info: Boolean,
|
||||
val can_change_invite_link: Boolean,
|
||||
val can_change_pin: Boolean,
|
||||
val can_invite: Boolean,
|
||||
val can_promote_users: Boolean,
|
||||
val can_see_invite_link: Boolean,
|
||||
val can_moderate: Boolean,
|
||||
val can_copy_chat: Boolean,
|
||||
val can_call: Boolean,
|
||||
val can_use_mass_mentions: Boolean,
|
||||
val can_change_style: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Photo(
|
||||
@SerializedName("photo_50")
|
||||
val photo50: String?,
|
||||
@SerializedName("photo_100")
|
||||
val photo100: String?,
|
||||
@SerializedName("photo_200")
|
||||
val photo200: String?,
|
||||
@SerializedName("is_default_photo")
|
||||
val isDefaultPhoto: Boolean
|
||||
val photo_50: String?,
|
||||
val photo_100: String?,
|
||||
val photo_200: String?,
|
||||
val is_default_photo: Boolean
|
||||
) : Parcelable
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class CallInProgress(
|
||||
val participants: Participants,
|
||||
@SerializedName("join_link")
|
||||
val joinLink: String
|
||||
val participants: BaseVkGroupCall.Participants,
|
||||
val join_link: String
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.meloda.fast.api.model.base
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -9,33 +8,24 @@ import kotlinx.parcelize.Parcelize
|
||||
data class BaseVkGroup(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@SerializedName("screen_name")
|
||||
val screenName: String,
|
||||
@SerializedName("is_closed")
|
||||
val isClosed: Int,
|
||||
val screen_name: String,
|
||||
val is_closed: Int,
|
||||
val type: String,
|
||||
@SerializedName("is_admin")
|
||||
val isAdmin: Int,
|
||||
@SerializedName("is_member")
|
||||
val isMember: Int,
|
||||
@SerializedName("is_advertiser")
|
||||
val isAdvertiser: Int,
|
||||
@SerializedName("photo_50")
|
||||
val photo50: String?,
|
||||
@SerializedName("photo_100")
|
||||
val photo100: String?,
|
||||
@SerializedName("photo_200")
|
||||
val photo200: String?,
|
||||
@SerializedName("members_count")
|
||||
val membersCount: Int?
|
||||
val is_admin: Int,
|
||||
val is_member: Int,
|
||||
val is_advertiser: Int,
|
||||
val photo_50: String?,
|
||||
val photo_100: String?,
|
||||
val photo_200: String?,
|
||||
val members_count: Int?
|
||||
) : Parcelable {
|
||||
|
||||
fun asVkGroup() = VkGroup(
|
||||
id = -id,
|
||||
name = name,
|
||||
screenName = screenName,
|
||||
photo200 = photo200,
|
||||
membersCount = membersCount
|
||||
screenName = screen_name,
|
||||
photo200 = photo_200,
|
||||
membersCount = members_count
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.meloda.fast.api.model.base
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
|
||||
@@ -10,23 +9,17 @@ import kotlinx.parcelize.Parcelize
|
||||
@Parcelize
|
||||
data class BaseVkMessage(
|
||||
val date: Int,
|
||||
@SerializedName("from_id")
|
||||
val fromId: Int,
|
||||
val from_id: Int,
|
||||
val id: Int,
|
||||
val out: Int,
|
||||
@SerializedName("peer_id")
|
||||
val peerId: Int,
|
||||
val peer_id: Int,
|
||||
val text: String,
|
||||
@SerializedName("conversation_message_id")
|
||||
val conversationMessageId: Int,
|
||||
@SerializedName("fwd_messages")
|
||||
val fwdMessages: List<BaseVkMessage>? = listOf(),
|
||||
val conversation_message_id: Int,
|
||||
val fwd_messages: List<BaseVkMessage>? = listOf(),
|
||||
val important: Boolean,
|
||||
@SerializedName("random_id")
|
||||
val randomId: Int,
|
||||
val random_id: Int,
|
||||
val attachments: List<BaseVkAttachmentItem> = listOf(),
|
||||
@SerializedName("is_hidden")
|
||||
val isHidden: Boolean,
|
||||
val is_hidden: Boolean,
|
||||
val payload: String,
|
||||
val geo: Geo?,
|
||||
val action: Action?,
|
||||
@@ -37,20 +30,20 @@ data class BaseVkMessage(
|
||||
id = id,
|
||||
text = if (text.isBlank()) null else text,
|
||||
isOut = out == 1,
|
||||
peerId = peerId,
|
||||
fromId = fromId,
|
||||
peerId = peer_id,
|
||||
fromId = from_id,
|
||||
date = date,
|
||||
randomId = randomId,
|
||||
randomId = random_id,
|
||||
action = action?.type,
|
||||
actionMemberId = action?.memberId,
|
||||
actionMemberId = action?.member_id,
|
||||
actionText = action?.text,
|
||||
actionConversationMessageId = action?.conversationMessageId,
|
||||
actionConversationMessageId = action?.conversation_message_id,
|
||||
actionMessage = action?.message,
|
||||
geoType = geo?.type,
|
||||
important = important
|
||||
).also {
|
||||
it.attachments = VkUtils.parseAttachments(attachments)
|
||||
it.forwards = VkUtils.parseForwards(fwdMessages)
|
||||
it.forwards = VkUtils.parseForwards(fwd_messages)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
@@ -71,11 +64,9 @@ data class BaseVkMessage(
|
||||
@Parcelize
|
||||
data class Action(
|
||||
val type: String,
|
||||
@SerializedName("member_id")
|
||||
val memberId: Int?,
|
||||
val member_id: Int?,
|
||||
val text: String?,
|
||||
@SerializedName("conversation_message_id")
|
||||
val conversationMessageId: Int?,
|
||||
val conversation_message_id: Int?,
|
||||
val message: String?
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
package com.meloda.fast.api.model.base
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkUser(
|
||||
val id: Int,
|
||||
@SerializedName("first_name")
|
||||
val firstName: String,
|
||||
@SerializedName("last_name")
|
||||
val lastName: String,
|
||||
@SerializedName("can_access_closed")
|
||||
val canAccessClosed: Boolean,
|
||||
@SerializedName("is_closed")
|
||||
val isClosed: Boolean,
|
||||
@SerializedName("can_invite_to_chats")
|
||||
val canInviteToChats: Boolean,
|
||||
val first_name: String,
|
||||
val last_name: String,
|
||||
val can_access_closed: Boolean,
|
||||
val is_closed: Boolean,
|
||||
val can_invite_to_chats: Boolean,
|
||||
val sex: Int?,
|
||||
@SerializedName("photo_50")
|
||||
val photo50: String?,
|
||||
@SerializedName("photo_100")
|
||||
val photo100: String?,
|
||||
@SerializedName("photo_200")
|
||||
val photo200: String?,
|
||||
val photo_50: String?,
|
||||
val photo_100: String?,
|
||||
val photo_200: String?,
|
||||
val online: Int?,
|
||||
@SerializedName("online_info")
|
||||
val onlineInfo: OnlineInfo?,
|
||||
@SerializedName("screen_name")
|
||||
val screenName: String
|
||||
val online_info: OnlineInfo?,
|
||||
val screen_name: String
|
||||
//...other fields
|
||||
) : Parcelable {
|
||||
|
||||
@@ -37,24 +26,20 @@ data class BaseVkUser(
|
||||
data class OnlineInfo(
|
||||
val visible: Boolean,
|
||||
val status: String,
|
||||
@SerializedName("last_seen")
|
||||
val lastSeen: Int?,
|
||||
@SerializedName("is_online")
|
||||
val isOnline: Boolean?,
|
||||
@SerializedName("online_mobile")
|
||||
val isOnlineMobile: Boolean?,
|
||||
@SerializedName("app_id")
|
||||
val appId: Int?
|
||||
val last_seen: Int?,
|
||||
val is_online: Boolean?,
|
||||
val online_mobile: Boolean?,
|
||||
val app_id: Int?
|
||||
) : Parcelable
|
||||
|
||||
fun asVkUser() = VkUser(
|
||||
id = id,
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
firstName = first_name,
|
||||
lastName = last_name,
|
||||
online = online == 1,
|
||||
photo200 = photo200,
|
||||
lastSeen = onlineInfo?.lastSeen,
|
||||
lastSeenStatus = onlineInfo?.status
|
||||
photo200 = photo_200,
|
||||
lastSeen = online_info?.last_seen,
|
||||
lastSeenStatus = online_info?.status
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
+20
-4
@@ -1,6 +1,7 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -26,12 +27,16 @@ data class BaseVkAttachmentItem(
|
||||
val wallReply: BaseVkWallReply?,
|
||||
val call: BaseVkCall?,
|
||||
@SerializedName("group_call_in_progress")
|
||||
val groupCall: BaseVkGroupCall?
|
||||
val groupCall: BaseVkGroupCall?,
|
||||
val curator: BaseVkCurator?,
|
||||
val event: BaseVkEvent?,
|
||||
val story: BaseVkStory?
|
||||
) : Parcelable {
|
||||
|
||||
fun getPreparedType() = AttachmentType.parse(type)
|
||||
|
||||
enum class AttachmentType(val value: String) {
|
||||
enum class AttachmentType(var value: String) {
|
||||
UNKNOWN("unknown"),
|
||||
PHOTO("photo"),
|
||||
VIDEO("video"),
|
||||
AUDIO("audio"),
|
||||
@@ -46,11 +51,22 @@ data class BaseVkAttachmentItem(
|
||||
POLL("poll"),
|
||||
WALL_REPLY("wall_reply"),
|
||||
CALL("call"),
|
||||
GROUP_CALL_IN_PROGRESS("group_call_in_progress")
|
||||
GROUP_CALL_IN_PROGRESS("group_call_in_progress"),
|
||||
CURATOR("curator"),
|
||||
EVENT("event"),
|
||||
STORY("story")
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun parse(value: String) = values().firstOrNull { it.value == value }
|
||||
fun parse(value: String): AttachmentType? {
|
||||
val parsedValue = values().firstOrNull { it.value == value } ?: UNKNOWN
|
||||
|
||||
if (parsedValue == UNKNOWN) {
|
||||
Log.e("AttachmentType", "Unknown attachment type: $value")
|
||||
}
|
||||
|
||||
return parsedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkAudio
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -12,37 +12,33 @@ data class BaseVkAudio(
|
||||
val duration: Int,
|
||||
val url: String,
|
||||
val date: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
@SerializedName("is_explicit")
|
||||
val isExplicit: Boolean,
|
||||
@SerializedName("is_focus_track")
|
||||
val isFocusTrack: Boolean,
|
||||
@SerializedName("is_licensed")
|
||||
val isLicensed: Boolean,
|
||||
@SerializedName("track_code")
|
||||
val trackCode: String,
|
||||
@SerializedName("genre_id")
|
||||
val genreId: Int,
|
||||
val owner_id: Int,
|
||||
val access_key: String,
|
||||
val is_explicit: Boolean,
|
||||
val is_focus_track: Boolean,
|
||||
val is_licensed: Boolean,
|
||||
val track_code: String,
|
||||
val genre_id: Int,
|
||||
val album: Album,
|
||||
@SerializedName("short_videos_allowed")
|
||||
val shortVideosAllowed: Boolean,
|
||||
@SerializedName("stories_allowed")
|
||||
val storiesAllowed: Boolean,
|
||||
@SerializedName("stories_cover_allowed")
|
||||
val storiesCoverAllowed: Boolean
|
||||
val short_videos_allowed: Boolean,
|
||||
val stories_allowed: Boolean,
|
||||
val stories_cover_allowed: Boolean
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkAudio() = VkAudio(
|
||||
id = id,
|
||||
title = title,
|
||||
artist = artist,
|
||||
url = url,
|
||||
duration = duration
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Album(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
val owner_id: Int,
|
||||
val access_key: String,
|
||||
val thumb: Thumb
|
||||
) : Parcelable {
|
||||
|
||||
@@ -50,22 +46,13 @@ data class BaseVkAudio(
|
||||
data class Thumb(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
@SerializedName("photo_34")
|
||||
val photo34: String,
|
||||
@SerializedName("photo_68")
|
||||
val photo68: String,
|
||||
@SerializedName("photo_135")
|
||||
val photo135: String,
|
||||
@SerializedName("photo_270")
|
||||
val photo270: String,
|
||||
@SerializedName("photo_300")
|
||||
val photo300: String,
|
||||
@SerializedName("photo_600")
|
||||
val photo600: String,
|
||||
@SerializedName("photo_1200")
|
||||
val photo1200: String
|
||||
val photo_34: String,
|
||||
val photo_68: String,
|
||||
val photo_135: String,
|
||||
val photo_270: String,
|
||||
val photo_300: String,
|
||||
val photo_600: String,
|
||||
val photo_1200: String
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkCall(
|
||||
@SerializedName("initiator_id")
|
||||
val initiatorId: Int,
|
||||
@SerializedName("receiver_id")
|
||||
val receiverId: Int,
|
||||
val initiator_id: Int,
|
||||
val receiver_id: Int,
|
||||
val state: String,
|
||||
val time: Int,
|
||||
val duration: Int,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.meloda.fast.api.model.attachments.VkCurator
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkCurator(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val url: String,
|
||||
val photo: List<Photo>
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkCurator() = VkCurator(
|
||||
id = id
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Photo(
|
||||
val height: Int,
|
||||
val url: String,
|
||||
val width: String
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import com.meloda.fast.api.model.attachments.VkEvent
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkEvent(
|
||||
val button_text: String,
|
||||
val id: Int,
|
||||
val is_favorite: Boolean,
|
||||
val text: String,
|
||||
val address: String,
|
||||
val friends: List<Int> = listOf(),
|
||||
val member_status: Int,
|
||||
val time: Int
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkEvent() = VkEvent(
|
||||
id = id
|
||||
)
|
||||
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkFile
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkFile(
|
||||
val id: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
val owner_id: Int,
|
||||
val title: String,
|
||||
val size: Int,
|
||||
val ext: String,
|
||||
@@ -16,14 +15,19 @@ data class BaseVkFile(
|
||||
val type: Int,
|
||||
val url: String,
|
||||
val preview: Preview?,
|
||||
@SerializedName("is_licensed")
|
||||
val isLicensed: Int,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
@SerializedName("web_preview_url")
|
||||
val webPreviewUrl: String?
|
||||
val ic_licensed: Int,
|
||||
val access_key: String,
|
||||
val web_preview_url: String?
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkFile() = VkFile(
|
||||
id = id,
|
||||
title = title,
|
||||
ext = ext,
|
||||
url = url,
|
||||
size = size
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Preview(
|
||||
val photo: Photo?,
|
||||
@@ -31,15 +35,24 @@ data class BaseVkFile(
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Photo(val sizes: List<Size>) : Parcelable
|
||||
data class Photo(val sizes: List<Size>) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Size(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val type: String,
|
||||
val src: String
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Video(
|
||||
val src: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
@SerializedName("file_size")
|
||||
val fileSize: Int
|
||||
val file_size: Int
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkGift(
|
||||
val id: Int,
|
||||
@SerializedName("thumb_256")
|
||||
val thumb256: String?,
|
||||
@SerializedName("thumb_96")
|
||||
val thumb96: String?,
|
||||
@SerializedName("thumb_48")
|
||||
val thumb48: String
|
||||
val thumb_256: String?,
|
||||
val thumb_96: String?,
|
||||
val thumb_48: String
|
||||
) : Parcelable
|
||||
@@ -1,17 +1,14 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkGraffiti(
|
||||
val id: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
val owner_id: Int,
|
||||
val url: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String
|
||||
val access_key: String
|
||||
) : Parcelable
|
||||
@@ -1,15 +1,12 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkGroupCall(
|
||||
@SerializedName("initiator_id")
|
||||
val initiatorId: Int,
|
||||
@SerializedName("join_link")
|
||||
val joinLink: String,
|
||||
val initiator_id: Int,
|
||||
val join_link: String,
|
||||
val participants: Participants
|
||||
) : Parcelable {
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkLink
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkLink(
|
||||
val url: String,
|
||||
val title: String,
|
||||
val caption: String,
|
||||
val photo: BaseVkPhoto,
|
||||
val title: String?,
|
||||
val caption: String?,
|
||||
val photo: BaseVkPhoto?,
|
||||
val target: String,
|
||||
@SerializedName("is_favorite")
|
||||
val isFavorite: Boolean
|
||||
) : BaseVkAttachment()
|
||||
val is_favorite: Boolean
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkLink() = VkLink(
|
||||
url = url,
|
||||
title = title,
|
||||
caption = caption,
|
||||
photo = photo?.asVkPhoto(),
|
||||
target = target,
|
||||
isFavorite = is_favorite
|
||||
)
|
||||
|
||||
}
|
||||
@@ -10,8 +10,7 @@ data class BaseVkMiniApp(
|
||||
val description: String,
|
||||
val app: App,
|
||||
val images: List<Image>?,
|
||||
@SerializedName("button_text")
|
||||
val buttonText: String
|
||||
val button_text: String
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -1,47 +1,43 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkPhoto(
|
||||
@SerializedName("album_id")
|
||||
val albumId: Int,
|
||||
val album_id: Int,
|
||||
val date: Int,
|
||||
val id: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
@SerializedName("has_tags")
|
||||
val hasTags: Boolean,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String?,
|
||||
val owner_id: Int,
|
||||
val has_tags: Boolean,
|
||||
val access_key: String?,
|
||||
val sizes: List<Size>,
|
||||
val text: String,
|
||||
@SerializedName("user_id")
|
||||
val userId: Int?
|
||||
val user_id: Int?,
|
||||
val lat: Double?,
|
||||
val long: Double?,
|
||||
val post_id: Int?
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkPhoto() = VkPhoto(
|
||||
albumId = albumId,
|
||||
albumId = album_id,
|
||||
date = date,
|
||||
id = id,
|
||||
ownerId = ownerId,
|
||||
hasTags = hasTags,
|
||||
accessKey = accessKey,
|
||||
ownerId = owner_id,
|
||||
hasTags = has_tags,
|
||||
accessKey = access_key,
|
||||
sizes = sizes,
|
||||
text = text,
|
||||
userId = userId
|
||||
userId = user_id
|
||||
)
|
||||
|
||||
}
|
||||
@Parcelize
|
||||
data class Size(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val type: String,
|
||||
val url: String
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Size(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val type: String,
|
||||
@SerializedName("url", alternate = ["src"])
|
||||
val url: String,
|
||||
) : Parcelable
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -11,30 +10,20 @@ data class BaseVkPoll(
|
||||
val votes: Int,
|
||||
val anonymous: Boolean,
|
||||
val closed: Boolean,
|
||||
@SerializedName("end_date")
|
||||
val endDate: Int,
|
||||
@SerializedName("is_board")
|
||||
val isBoard: Boolean,
|
||||
@SerializedName("can_vote")
|
||||
val canVote: Boolean,
|
||||
@SerializedName("can_edit")
|
||||
val canEdit: Boolean,
|
||||
@SerializedName("can_report")
|
||||
val canReport: Boolean,
|
||||
@SerializedName("can_share")
|
||||
val canShare: Boolean,
|
||||
val end_date: Int,
|
||||
val is_board: Boolean,
|
||||
val can_vote: Boolean,
|
||||
val can_edit: Boolean,
|
||||
val can_report: Boolean,
|
||||
val can_share: Boolean,
|
||||
val created: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
val owner_id: Int,
|
||||
val question: String,
|
||||
@SerializedName("disable_unvote")
|
||||
val disableUnVote: Boolean,
|
||||
val disable_unvote: Boolean,
|
||||
val friends: List<Friend>?,
|
||||
@SerializedName("embed_hash")
|
||||
val embedHash: String,
|
||||
val embed_hash: String,
|
||||
val answers: List<Answer>,
|
||||
@SerializedName("author_id")
|
||||
val authorId: Int,
|
||||
val author_id: Int,
|
||||
val background: Background?
|
||||
) : Parcelable {
|
||||
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.IntDef
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkSticker
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkSticker(
|
||||
@SerializedName("product_id")
|
||||
val productId: Int,
|
||||
@SerializedName("sticker_id")
|
||||
val stickerId: Int,
|
||||
val product_id: Int,
|
||||
val sticker_id: Int,
|
||||
val images: List<Image>,
|
||||
@SerializedName("images_with_background")
|
||||
val imagesWithBackground: List<Image>,
|
||||
@SerializedName("animation_url")
|
||||
val animationUrl: String?,
|
||||
val images_with_background: List<Image>,
|
||||
val animation_url: String?,
|
||||
val animations: List<Animation>?
|
||||
) : Parcelable {
|
||||
|
||||
fun asVkSticker() = VkSticker(
|
||||
id = stickerId,
|
||||
productId = productId,
|
||||
id = sticker_id,
|
||||
productId = product_id,
|
||||
images = images,
|
||||
backgroundImages = imagesWithBackground
|
||||
backgroundImages = images_with_background
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@@ -41,6 +35,3 @@ data class BaseVkSticker(
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
|
||||
@IntDef(64, 128, 256, 352)
|
||||
annotation class StickerSize
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.meloda.fast.api.model.attachments.VkStory
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkStory(
|
||||
val id: Int,
|
||||
val owner_id: Int,
|
||||
val access_key: String,
|
||||
val can_comment: Int,
|
||||
val can_reply: Int,
|
||||
val can_like: Boolean,
|
||||
val can_share: Int,
|
||||
val can_hide: Int,
|
||||
val date: Int,
|
||||
val expires_at: Int,
|
||||
val is_ads: Boolean,
|
||||
val photo: BaseVkPhoto,
|
||||
val replies: Replies,
|
||||
val is_one_time: Boolean,
|
||||
val track_code: String,
|
||||
val type: String,
|
||||
val views: Int,
|
||||
val likes_count: Int,
|
||||
val reaction_set_id: String,
|
||||
val is_restricted: Boolean,
|
||||
val no_sound: Boolean,
|
||||
val need_mute: Boolean,
|
||||
val mute_reply: Boolean,
|
||||
val can_ask: Int,
|
||||
val can_ask_anonymous: Int,
|
||||
val preloading_enabled: Boolean,
|
||||
val narratives_count: Int,
|
||||
val can_use_in_narrative: Boolean
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkStory() = VkStory(
|
||||
id = id,
|
||||
ownerId = owner_id,
|
||||
date = date,
|
||||
photo = photo.asVkPhoto()
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Replies(
|
||||
val count: Int,
|
||||
val new: Int
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
//not all fields
|
||||
@Parcelize
|
||||
data class BaseVkVideo(
|
||||
val id: Int,
|
||||
@@ -20,45 +18,30 @@ data class BaseVkVideo(
|
||||
val added: Int,
|
||||
val type: String,
|
||||
val views: Int,
|
||||
@SerializedName("can_comment")
|
||||
val canComment: Int,
|
||||
@SerializedName("can_edit")
|
||||
val canEdit: Int,
|
||||
@SerializedName("can_like")
|
||||
val canLike: Int,
|
||||
@SerializedName("can_repost")
|
||||
val canRepost: Int,
|
||||
@SerializedName("can_subscribe")
|
||||
val canSubscribe: Int,
|
||||
@SerializedName("can_add_to_faves")
|
||||
val canAddToFaves: Int,
|
||||
@SerializedName("can_add")
|
||||
val canAdd: Int,
|
||||
@SerializedName("can_attach_link")
|
||||
val canAttachLink: Int,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
@SerializedName("ov_id")
|
||||
val ovId: String,
|
||||
@SerializedName("is_favorite")
|
||||
val isFavorite: Boolean,
|
||||
@SerializedName("track_code")
|
||||
val trackCode: String,
|
||||
val can_comment: Int,
|
||||
val can_edit: Int,
|
||||
val can_like: Int,
|
||||
val can_repost: Int,
|
||||
val can_subscribe: Int,
|
||||
val can_add_to_faves: Int,
|
||||
val can_add: Int,
|
||||
val can_attach_link: Int,
|
||||
val access_key: String,
|
||||
val owner_id: Int,
|
||||
val ov_id: String,
|
||||
val is_favorite: Boolean,
|
||||
val track_code: String,
|
||||
val image: List<Image>,
|
||||
@SerializedName("first_frame")
|
||||
val firstFrame: List<FirstFrame>,
|
||||
val first_frame: List<FirstFrame>,
|
||||
val files: File,
|
||||
@SerializedName("timeline_thumbs")
|
||||
val timelineThumbs: TimelineThumbs
|
||||
//ads
|
||||
val timeline_thumbs: TimelineThumbs,
|
||||
val ads: Ads
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkVideo() = VkVideo(
|
||||
id = id,
|
||||
images = image,
|
||||
firstFrames = firstFrame
|
||||
firstFrames = first_frame
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@@ -66,8 +49,7 @@ data class BaseVkVideo(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val url: String,
|
||||
@SerializedName("with_padding")
|
||||
val withPadding: Int
|
||||
val with_padding: Int
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
@@ -86,34 +68,62 @@ data class BaseVkVideo(
|
||||
val mp4_1080: String?,
|
||||
val mp4_1440: String?,
|
||||
val hls: String,
|
||||
@SerializedName("dash_uni")
|
||||
val dashUni: String,
|
||||
@SerializedName("dash_sep")
|
||||
val dashSep: String,
|
||||
@SerializedName("hls_ondemand")
|
||||
val hlsOnDemand: String,
|
||||
@SerializedName("dash_ondemand")
|
||||
val dashOnDemand: String,
|
||||
@SerializedName("failover_host")
|
||||
val failOverHost: String
|
||||
val dash_uni: String,
|
||||
val dash_sep: String,
|
||||
val hls_ondemand: String,
|
||||
val dash_ondemand: String,
|
||||
val failover_host: String
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class TimelineThumbs(
|
||||
@SerializedName("count_per_image")
|
||||
val countPerImage: Int,
|
||||
@SerializedName("count_per_row")
|
||||
val countPerRow: Int,
|
||||
@SerializedName("count_total")
|
||||
val countTotal: Int,
|
||||
@SerializedName("frame_height")
|
||||
val frameHeight: Int,
|
||||
@SerializedName("frame_width")
|
||||
val frameWidth: Float,
|
||||
val count_per_image: Int,
|
||||
val count_per_row: Int,
|
||||
val count_total: Int,
|
||||
val frame_height: Int,
|
||||
val frame_width: Float,
|
||||
val links: List<String>,
|
||||
@SerializedName("is_uv")
|
||||
val isUv: Boolean,
|
||||
val is_uv: Boolean,
|
||||
val frequency: Int
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Ads(
|
||||
val slot_id: Int,
|
||||
val timeout: Int,
|
||||
val can_play: Int,
|
||||
val params: Params,
|
||||
val sections: List<String>,
|
||||
val midroll_percents: List<Float>
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Params(
|
||||
val vk_id: Int,
|
||||
val duration: Int,
|
||||
val video_id: String,
|
||||
val pl: Int,
|
||||
val content_id: String,
|
||||
val lang: Int,
|
||||
val puid1: String,
|
||||
val puid2: Int,
|
||||
val puid3: Int,
|
||||
val puid5: Int,
|
||||
val puid6: Int,
|
||||
val puid7: Int,
|
||||
val puid9: Int,
|
||||
val puid10: Int,
|
||||
val puid12: Int,
|
||||
val puid13: Int,
|
||||
val puid14: Int,
|
||||
val puid15: Int,
|
||||
val puid18: Int,
|
||||
val puid21: Int,
|
||||
val sign: String,
|
||||
val groupId: Int,
|
||||
val vk_catid: Int,
|
||||
val is_xz_video: Int
|
||||
) : Parcelable
|
||||
}
|
||||
|
||||
}
|
||||
+5
-11
@@ -1,23 +1,17 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkVoiceMessage(
|
||||
val id: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
val owner_id: Int,
|
||||
val duration: Int,
|
||||
val waveform: List<Int>,
|
||||
@SerializedName("link_ogg")
|
||||
val linkOgg: String,
|
||||
@SerializedName("link_mp3")
|
||||
val linkMp3: String,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
@SerializedName("transcript_state")
|
||||
val transcriptState: String,
|
||||
val link_ogg: String,
|
||||
val link_mp3: String,
|
||||
val access_key: String,
|
||||
val transcript_state: String,
|
||||
val transcript: String
|
||||
) : Parcelable
|
||||
@@ -1,34 +1,43 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkWall
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkWall(
|
||||
val id: Int,
|
||||
@SerializedName("from_id")
|
||||
val fromId: Int,
|
||||
@SerializedName("to_id")
|
||||
val toId: Int,
|
||||
val from_id: Int,
|
||||
val to_id: Int,
|
||||
val date: Int,
|
||||
val text: String,
|
||||
val attachments: List<BaseVkAttachmentItem>?,
|
||||
@SerializedName("post_source")
|
||||
val postSource: PostSource,
|
||||
val post_source: PostSource,
|
||||
val comments: Comments,
|
||||
val likes: Likes,
|
||||
val reposts: Reposts,
|
||||
val views: Views,
|
||||
@SerializedName("is_favorite")
|
||||
val isFavorite: Boolean,
|
||||
val is_favorite: Boolean,
|
||||
val donut: Donut,
|
||||
@SerializedName("access_key")
|
||||
val accessKey: String,
|
||||
@SerializedName("short_text_rate")
|
||||
val shortTextRate: Double
|
||||
val access_key: String,
|
||||
val short_text_rate: Double
|
||||
) : Parcelable {
|
||||
|
||||
fun asVkWall() = VkWall(
|
||||
id = id,
|
||||
fromId = from_id,
|
||||
toId = to_id,
|
||||
date = date,
|
||||
text = text,
|
||||
attachments = attachments,
|
||||
comments = comments.count,
|
||||
likes = likes.count,
|
||||
reposts = reposts.count,
|
||||
views = views.count,
|
||||
isFavorite = is_favorite,
|
||||
accessKey = access_key
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class PostSource(
|
||||
val type: String,
|
||||
@@ -38,28 +47,22 @@ data class BaseVkWall(
|
||||
@Parcelize
|
||||
data class Comments(
|
||||
val count: Int,
|
||||
@SerializedName("can_post")
|
||||
val canPost: Int,
|
||||
@SerializedName("groups_can_post")
|
||||
val groupsCanPost: Boolean
|
||||
val can_post: Int,
|
||||
val groups_can_post: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Likes(
|
||||
val count: Int,
|
||||
@SerializedName("user_likes")
|
||||
val userLikes: Int,
|
||||
@SerializedName("can_like")
|
||||
val canLike: Int,
|
||||
@SerializedName("can_publish")
|
||||
val canPublish: Int,
|
||||
val user_likes: Int,
|
||||
val can_like: Int,
|
||||
val can_publish: Int,
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Reposts(
|
||||
val count: Int,
|
||||
@SerializedName("user_reposted")
|
||||
val userReposted: Int
|
||||
val user_reposted: Int
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
@@ -69,8 +72,7 @@ data class BaseVkWall(
|
||||
|
||||
@Parcelize
|
||||
data class Donut(
|
||||
@SerializedName("is_donut")
|
||||
val isDonut: Boolean
|
||||
val is_donut: Boolean
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
@@ -1,39 +1,29 @@
|
||||
package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BaseVkWallReply(
|
||||
val id: Int,
|
||||
@SerializedName("from_id")
|
||||
val fromId: Int,
|
||||
val from_id: Int,
|
||||
val date: Int,
|
||||
val text: String,
|
||||
@SerializedName("post_id")
|
||||
val postId: Int,
|
||||
@SerializedName("owner_id")
|
||||
val ownerId: Int,
|
||||
@SerializedName("parents_stack")
|
||||
val parentsStack: List<Int>,
|
||||
val post_id: Int,
|
||||
val owner_id: Int,
|
||||
val parents_stack: List<Int>,
|
||||
val likes: Likes,
|
||||
@SerializedName("reply_to_user")
|
||||
val replyToUser: Int?,
|
||||
@SerializedName("reply_to_comment")
|
||||
val replyToComment: Int?
|
||||
val reply_to_user: Int?,
|
||||
val reply_to_comment: Int?
|
||||
) : Parcelable {
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class Likes(
|
||||
val count: Int,
|
||||
@SerializedName("can_like")
|
||||
val canLike: Int,
|
||||
@SerializedName("user_likes")
|
||||
val userLikes: Int,
|
||||
@SerializedName("can_publish")
|
||||
val canPublish: Int
|
||||
val can_like: Int,
|
||||
val user_likes: Int,
|
||||
val can_publish: Int
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
@@ -77,18 +77,21 @@ internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy)
|
||||
) : Callback<T> {
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
var isVkException = true
|
||||
|
||||
val result: Answer<T> =
|
||||
if (response.isSuccessful) {
|
||||
val baseBody = response.body()
|
||||
if (baseBody !is ApiResponse<*>) Answer.Success(baseBody as T)
|
||||
else {
|
||||
val body = baseBody as ApiResponse<*>
|
||||
if (body.error != null) Answer.Error(body.error)
|
||||
else Answer.Success(body as T)
|
||||
if (body.error != null) {
|
||||
Answer.Error(body.error)
|
||||
} else Answer.Success(body as T)
|
||||
}
|
||||
} else Answer.Error(IOException(response.errorBody()?.string() ?: ""))
|
||||
|
||||
if (result is Answer.Error) if (checkErrors(call, result)) return
|
||||
if (result is Answer.Error && isVkException) if (checkErrors(call, result)) return
|
||||
|
||||
|
||||
callback.onResponse(proxy, Response.success(result))
|
||||
|
||||
@@ -7,15 +7,16 @@ import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.util.Log
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.room.Room
|
||||
import com.meloda.fast.BuildConfig
|
||||
import com.meloda.fast.database.AppDatabase
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import org.acra.ACRA
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@HiltAndroidApp
|
||||
class AppGlobal : Application() {
|
||||
@@ -66,8 +67,24 @@ class AppGlobal : Application() {
|
||||
Companion.packageName = packageName
|
||||
Companion.packageManager = packageManager
|
||||
|
||||
screenWidth = AndroidUtils.getDisplayWidth()
|
||||
screenHeight = AndroidUtils.getDisplayHeight()
|
||||
screenWidth = resources.displayMetrics.widthPixels
|
||||
screenHeight = resources.displayMetrics.heightPixels
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val densityDpi = resources.displayMetrics.densityDpi
|
||||
val densityScaled = resources.displayMetrics.scaledDensity
|
||||
val xDpi = resources.displayMetrics.xdpi
|
||||
val yDpi = resources.displayMetrics.ydpi
|
||||
|
||||
val diagonal = sqrt(
|
||||
(screenWidth * screenWidth - screenHeight * screenHeight).toFloat()
|
||||
)
|
||||
|
||||
Log.i(
|
||||
"Fast::DeviceInfo",
|
||||
"width: $screenWidth; height: $screenHeight; density: $density; diagonal: $diagonal; dpiDensity: $densityDpi; scaledDensity: $densityScaled; xDpi: $xDpi; yDpi: $yDpi"
|
||||
)
|
||||
|
||||
|
||||
inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.meloda.fast.common
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
|
||||
object AppSettings {
|
||||
|
||||
val keyIsMultilineEnabled = booleanPreferencesKey("isMultilineEnabled")
|
||||
}
|
||||
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = "settings",
|
||||
corruptionHandler = null,
|
||||
scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.text.SpannableString
|
||||
import android.text.TextUtils
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -27,7 +28,8 @@ class ConversationsAdapter constructor(
|
||||
context: Context,
|
||||
values: MutableList<VkConversation>,
|
||||
val profiles: HashMap<Int, VkUser> = hashMapOf(),
|
||||
val groups: HashMap<Int, VkGroup> = hashMapOf()
|
||||
val groups: HashMap<Int, VkGroup> = hashMapOf(),
|
||||
var isMultilineEnabled: Boolean = true
|
||||
) : BaseAdapter<VkConversation, ConversationsAdapter.ItemHolder>(
|
||||
context, values, COMPARATOR
|
||||
) {
|
||||
@@ -41,6 +43,11 @@ class ConversationsAdapter constructor(
|
||||
private val dateColor = ContextCompat.getColor(context, R.color.n2_500)
|
||||
private val youPrefix = context.getString(R.string.you_message_prefix)
|
||||
|
||||
init {
|
||||
binding.title.ellipsize = TextUtils.TruncateAt.END
|
||||
binding.message.ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val conversation = getItem(position)
|
||||
|
||||
@@ -48,6 +55,11 @@ class ConversationsAdapter constructor(
|
||||
binding.callIcon.isVisible = conversation.callInProgress
|
||||
binding.phantomIcon.isVisible = conversation.isPhantom
|
||||
|
||||
val maxLines = if (isMultilineEnabled) 2 else 1
|
||||
|
||||
binding.title.maxLines = maxLines
|
||||
binding.message.maxLines = maxLines
|
||||
|
||||
val message = if (conversation.lastMessage != null) conversation.lastMessage!!
|
||||
else {
|
||||
binding.title.text = conversation.title
|
||||
@@ -129,6 +141,7 @@ class ConversationsAdapter constructor(
|
||||
binding.pin.isVisible = conversation.isPinned
|
||||
|
||||
val actionMessage = VkUtils.getActionConversationText(
|
||||
context = context,
|
||||
message = message,
|
||||
youPrefix = youPrefix,
|
||||
profiles = profiles,
|
||||
|
||||
@@ -6,10 +6,13 @@ import android.viewbinding.library.fragment.viewBinding
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import coil.load
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
@@ -17,9 +20,14 @@ import com.meloda.fast.base.BaseViewModelFragment
|
||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.VKEvent
|
||||
import com.meloda.fast.common.AppSettings
|
||||
import com.meloda.fast.common.dataStore
|
||||
import com.meloda.fast.databinding.FragmentConversationsBinding
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -59,7 +67,18 @@ class ConversationsFragment :
|
||||
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
binding.createChat.setOnClickListener {}
|
||||
lifecycleScope.launch {
|
||||
requireContext().dataStore.data.map {
|
||||
adapter.isMultilineEnabled = it[AppSettings.keyIsMultilineEnabled] ?: true
|
||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||
}.collect { }
|
||||
}
|
||||
|
||||
binding.createChat.setOnClickListener {
|
||||
Snackbar.make(it, "Test snackbar", Snackbar.LENGTH_SHORT)
|
||||
.setAction("Action") {}
|
||||
.show()
|
||||
}
|
||||
|
||||
UserConfig.vkUser.observe(viewLifecycleOwner) {
|
||||
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
|
||||
@@ -87,6 +106,18 @@ class ConversationsFragment :
|
||||
|
||||
viewModel.loadProfileUser()
|
||||
viewModel.loadConversations()
|
||||
|
||||
binding.avatar.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
requireContext().dataStore.edit { settings ->
|
||||
val isMultilineEnabled = settings[AppSettings.keyIsMultilineEnabled] ?: true
|
||||
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
||||
|
||||
adapter.isMultilineEnabled = !isMultilineEnabled
|
||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEvent(event: VKEvent) {
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Space
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isNotEmpty
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.*
|
||||
import com.meloda.fast.databinding.*
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.widget.RoundedFrameLayout
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AttachmentInflater constructor(
|
||||
private val context: Context,
|
||||
private val container: LinearLayoutCompat,
|
||||
private val message: VkMessage,
|
||||
private val profiles: Map<Int, VkUser>,
|
||||
private val groups: Map<Int, VkGroup>
|
||||
) {
|
||||
private lateinit var attachments: List<VkAttachment>
|
||||
|
||||
private val inflater = LayoutInflater.from(context)
|
||||
|
||||
private val playColor = ContextCompat.getColor(context, R.color.a3_700)
|
||||
private val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
||||
|
||||
fun inflate() {
|
||||
if (message.attachments.isNullOrEmpty()) return
|
||||
attachments = message.attachments!!
|
||||
|
||||
container.removeAllViews()
|
||||
|
||||
if (attachments.size == 1) {
|
||||
when (val attachment = attachments[0]) {
|
||||
is VkSticker -> return sticker(attachment)
|
||||
is VkWall -> return wall(attachment)
|
||||
}
|
||||
}
|
||||
|
||||
if (attachments.size > 1) {
|
||||
if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkPhoto) {
|
||||
return attachments.forEach { photo(it as VkPhoto) }
|
||||
}
|
||||
|
||||
if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkVideo) {
|
||||
return attachments.forEach { video(it as VkVideo) }
|
||||
}
|
||||
|
||||
if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkAudio) {
|
||||
return attachments.forEach { audio(it as VkAudio) }
|
||||
}
|
||||
|
||||
if (VkUtils.isAttachmentsHaveOneType(attachments) && attachments[0] is VkFile) {
|
||||
return attachments.forEach { file(it as VkFile) }
|
||||
}
|
||||
}
|
||||
|
||||
attachments.forEach { attachment ->
|
||||
when (attachment) {
|
||||
is VkPhoto -> photo(attachment)
|
||||
is VkVideo -> video(attachment)
|
||||
is VkAudio -> audio(attachment)
|
||||
is VkFile -> file(attachment)
|
||||
is VkLink -> link(attachment)
|
||||
is VkStory -> story(attachment)
|
||||
|
||||
else -> Log.e(
|
||||
"Attachment inflater",
|
||||
"Unknown attachment type: ${attachment.javaClass.name}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun photo(photo: VkPhoto) {
|
||||
val size = photo.sizeOfType('m') ?: return
|
||||
|
||||
val newPhoto = ShapeableImageView(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
|
||||
shapeAppearanceModel =
|
||||
shapeAppearanceModel.withCornerSize {
|
||||
AndroidUtils.px(5)
|
||||
}
|
||||
|
||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
val spacer = Space(context).also {
|
||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
AndroidUtils.px(5).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (container.isNotEmpty())
|
||||
container.addView(spacer)
|
||||
|
||||
if (attachments.size == 1) {
|
||||
val roundedLayout = RoundedFrameLayout(context).apply {
|
||||
setTopRightCornerRadius((if (message.isOut) 30 else 40).toFloat())
|
||||
setTopLeftCornerRadius((if (message.isOut) 40 else 30).toFloat())
|
||||
setBottomRightCornerRadius((if (message.isOut) 5 else 40).toFloat())
|
||||
setBottomLeftCornerRadius((if (message.isOut) 40 else 5).toFloat())
|
||||
}
|
||||
|
||||
roundedLayout.addView(newPhoto)
|
||||
container.addView(roundedLayout)
|
||||
} else {
|
||||
container.addView(newPhoto)
|
||||
}
|
||||
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
|
||||
private fun video(video: VkVideo) {
|
||||
val size = video.images[1]
|
||||
|
||||
val layout = FrameLayout(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
val newPhoto = ShapeableImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
|
||||
shapeAppearanceModel =
|
||||
shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
val play = AppCompatImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(50).roundToInt(),
|
||||
AndroidUtils.px(50).roundToInt()
|
||||
).also {
|
||||
it.gravity = Gravity.CENTER
|
||||
}
|
||||
|
||||
backgroundTintList = ColorStateList.valueOf(playBackgroundColor)
|
||||
imageTintList = ColorStateList.valueOf(playColor)
|
||||
|
||||
setBackgroundResource(R.drawable.ic_play_button_circle_background)
|
||||
setImageResource(R.drawable.ic_round_play_arrow_24)
|
||||
|
||||
setPadding(12)
|
||||
}
|
||||
|
||||
layout.addView(newPhoto)
|
||||
layout.addView(play)
|
||||
|
||||
val spacer = Space(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
AndroidUtils.px(5).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (container.isNotEmpty())
|
||||
container.addView(spacer)
|
||||
|
||||
container.addView(layout)
|
||||
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
|
||||
private fun audio(audio: VkAudio) {
|
||||
val binding = ItemMessageAttachmentAudioBinding.inflate(inflater, container, true)
|
||||
|
||||
binding.title.text = audio.title
|
||||
binding.artist.text = "%s | %s".format(
|
||||
audio.artist,
|
||||
SimpleDateFormat("mm:ss", Locale.getDefault()).format(audio.duration * 1000L)
|
||||
)
|
||||
}
|
||||
|
||||
private fun file(file: VkFile) {
|
||||
val binding = ItemMessageAttachmentFileBinding.inflate(inflater, container, true)
|
||||
|
||||
binding.title.text = file.title
|
||||
binding.size.text = "%s | %s".format(
|
||||
AndroidUtils.bytesToHumanReadableSize(file.size.toDouble()),
|
||||
file.ext.uppercase()
|
||||
)
|
||||
}
|
||||
|
||||
private fun link(link: VkLink) {
|
||||
val binding = ItemMessageAttachmentLinkBinding.inflate(inflater, container, true)
|
||||
|
||||
binding.title.text = link.title
|
||||
binding.title.isVisible = !link.title.isNullOrBlank()
|
||||
|
||||
binding.caption.text = link.caption
|
||||
binding.caption.isVisible = !link.caption.isNullOrBlank()
|
||||
|
||||
binding.preview.shapeAppearanceModel.toBuilder()
|
||||
.setAllCornerSizes(40f)
|
||||
.build()
|
||||
.let {
|
||||
binding.preview.shapeAppearanceModel = it
|
||||
}
|
||||
|
||||
link.photo?.sizeOfType('m')?.let {
|
||||
binding.preview.load(it.url) { crossfade(150) }
|
||||
binding.preview.isVisible = true
|
||||
return
|
||||
}
|
||||
|
||||
binding.preview.isVisible = false
|
||||
}
|
||||
|
||||
private fun sticker(sticker: VkSticker) {
|
||||
val binding = ItemMessageAttachmentStickerBinding.inflate(inflater, container, true)
|
||||
|
||||
val url = sticker.urlForSize(352)
|
||||
|
||||
with(binding.image) {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
AndroidUtils.px(180).roundToInt(),
|
||||
AndroidUtils.px(180).roundToInt()
|
||||
)
|
||||
|
||||
load(url) { crossfade(150) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun wall(wall: VkWall) {
|
||||
val binding = ItemMessageAttachmentWallPostBinding.inflate(inflater, container, true)
|
||||
|
||||
val group = if (wall.fromId > 0) null else groups[wall.fromId]
|
||||
val user = if (wall.fromId < 0) null else profiles[wall.fromId]
|
||||
|
||||
val postTitleRes = when {
|
||||
group != null && user == null -> R.string.post_type_community
|
||||
user != null && group == null -> R.string.post_type_user
|
||||
else -> R.string.post_type_unknown
|
||||
}
|
||||
|
||||
val avatar = when {
|
||||
group == null && user != null -> user.photo200
|
||||
user == null && group != null -> group.photo200
|
||||
else -> null
|
||||
}
|
||||
|
||||
val title = when {
|
||||
group == null && user != null -> user.fullName
|
||||
user == null && group != null -> group.name
|
||||
else -> "..."
|
||||
}
|
||||
|
||||
binding.postTitle.text = context.getString(postTitleRes)
|
||||
binding.postTitle.isVisible = false
|
||||
|
||||
binding.avatar.isVisible = group != null || user != null
|
||||
binding.avatar.shapeAppearanceModel.toBuilder()
|
||||
.setAllCornerSizes(40f)
|
||||
.build()
|
||||
.let {
|
||||
binding.avatar.shapeAppearanceModel = it
|
||||
}
|
||||
|
||||
if (binding.avatar.isVisible) {
|
||||
binding.avatar.load(avatar) { crossfade(150) }
|
||||
} else {
|
||||
binding.avatar.setImageDrawable(null)
|
||||
}
|
||||
|
||||
binding.title.text = title
|
||||
|
||||
binding.date.text = SimpleDateFormat(
|
||||
"dd.MM.yyyy HH:mm",
|
||||
Locale.getDefault()
|
||||
).format(wall.date * 1000L)
|
||||
}
|
||||
|
||||
private fun story(story: VkStory) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,12 +3,9 @@ package com.meloda.fast.screens.messages
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.load
|
||||
@@ -19,13 +16,10 @@ import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||
import com.meloda.fast.api.model.attachments.VkSticker
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import com.meloda.fast.base.adapter.BaseAdapter
|
||||
import com.meloda.fast.base.adapter.BaseHolder
|
||||
import com.meloda.fast.databinding.*
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -45,21 +39,6 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
getItem(position).let { message ->
|
||||
if (message.action != null) return SERVICE
|
||||
|
||||
if (!message.attachments.isNullOrEmpty()) {
|
||||
val attachments = message.attachments ?: return@let
|
||||
if (VkUtils.isAttachmentsHaveOneType(attachments) &&
|
||||
attachments[0] is VkPhoto
|
||||
) return if (message.isOut) ATTACHMENT_PHOTOS_OUT
|
||||
else ATTACHMENT_PHOTOS_IN
|
||||
|
||||
if (attachments[0] is VkVideo) return if (message.isOut) ATTACHMENT_VIDEOS_OUT
|
||||
else ATTACHMENT_VIDEOS_IN
|
||||
|
||||
if (attachments[0] is VkSticker) return if (message.isOut) ATTACHMENT_STICKER_OUT
|
||||
else ATTACHMENT_STICKER_IN
|
||||
}
|
||||
|
||||
if (message.isOut) return OUTGOING
|
||||
if (!message.isOut) return INCOMING
|
||||
}
|
||||
@@ -72,26 +51,12 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||
return when (viewType) {
|
||||
// magick numbers is great!
|
||||
HEADER -> Header(createEmptyView(60))
|
||||
FOOTER -> Footer(createEmptyView(36))
|
||||
SERVICE -> ServiceMessage(
|
||||
ItemMessageServiceBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_STICKER_IN -> AttachmentStickerIncoming(
|
||||
ItemMessageAttachmentStickerInBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_STICKER_OUT -> AttachmentStickerOutgoing(
|
||||
ItemMessageAttachmentStickerOutBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_PHOTOS_IN -> AttachmentPhotosIncoming(
|
||||
ItemMessageAttachmentPhotosInBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing(
|
||||
ItemMessageAttachmentPhotosOutBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_VIDEOS_IN, ATTACHMENT_VIDEOS_OUT -> AttachmentVideosIncoming(
|
||||
ItemMessageAttachmentVideosInBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
OUTGOING -> OutgoingMessage(
|
||||
ItemMessageOutBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
@@ -130,81 +95,31 @@ class MessagesHistoryAdapter constructor(
|
||||
private val binding: ItemMessageInBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
private val backgroundNormal =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_in_background)
|
||||
private val backgroundMiddle =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_in_background_middle)
|
||||
|
||||
init {
|
||||
MessagesManager.setRootMaxWidth(binding.bubble)
|
||||
}
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
binding.unread.isVisible = message.isRead(conversation)
|
||||
|
||||
binding.bubble.background =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormal
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddle
|
||||
else backgroundNormal
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.title.isVisible = false
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.title.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.spacer.isVisible = binding.title.isVisible
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
val messageUser: VkUser? = if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
} else null
|
||||
|
||||
val messageGroup: VkGroup? = if (message.isGroup()) {
|
||||
groups[message.fromId]
|
||||
} else null
|
||||
|
||||
MessagesManager.loadMessageAvatar(
|
||||
MessagesPreparator(
|
||||
context = context,
|
||||
conversation = conversation,
|
||||
message = message,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup,
|
||||
imageView = binding.avatar
|
||||
)
|
||||
prevMessage = prevMessage,
|
||||
nextMessage = nextMessage,
|
||||
|
||||
val title = when {
|
||||
message.isUser() && messageUser != null -> messageUser.firstName
|
||||
message.isGroup() && messageGroup != null -> messageGroup.name
|
||||
else -> null
|
||||
}
|
||||
avatar = binding.avatar,
|
||||
bubble = binding.bubble,
|
||||
text = binding.text,
|
||||
spacer = binding.spacer,
|
||||
time = binding.time,
|
||||
unread = binding.unread,
|
||||
attachmentContainer = binding.attachmentContainer,
|
||||
attachmentSpacer = binding.attachmentSpacer,
|
||||
|
||||
binding.title.text = title
|
||||
binding.title.measure(0, 0)
|
||||
|
||||
if (binding.title.isVisible) {
|
||||
binding.bubble.minimumWidth = binding.title.measuredWidth + 60
|
||||
} else {
|
||||
binding.bubble.minimumWidth = 0
|
||||
}
|
||||
|
||||
MessagesManager.setMessageText(
|
||||
message = message,
|
||||
textView = binding.text
|
||||
)
|
||||
profiles = profiles,
|
||||
groups = groups
|
||||
).prepare()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,23 +127,8 @@ class MessagesHistoryAdapter constructor(
|
||||
private val binding: ItemMessageOutBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
private val backgroundNormal =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||
private val backgroundMiddle =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
||||
private val backgroundStroke =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||
private val backgroundMiddleStroke =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||
|
||||
init {
|
||||
MessagesManager.setRootMaxWidth(binding.bubble)
|
||||
|
||||
binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() }
|
||||
|
||||
binding.bubble.setOnClickListener {
|
||||
binding.time.isVisible = !binding.time.isVisible
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(position: Int) {
|
||||
@@ -236,27 +136,25 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
|
||||
binding.text.text = message.text ?: "[no_message]"
|
||||
MessagesPreparator(
|
||||
context = context,
|
||||
conversation = conversation,
|
||||
message = message,
|
||||
prevMessage = prevMessage,
|
||||
|
||||
binding.unread.isVisible = message.isRead(conversation)
|
||||
bubble = binding.bubble,
|
||||
bubbleStroke = binding.bubbleStroke,
|
||||
text = binding.text,
|
||||
spacer = binding.spacer,
|
||||
time = binding.time,
|
||||
unread = binding.unread,
|
||||
attachmentContainer = binding.attachmentContainer,
|
||||
attachmentSpacer = binding.attachmentSpacer,
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
|
||||
binding.bubble.background =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormal
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddle
|
||||
else backgroundNormal
|
||||
|
||||
binding.bubbleStroke.background =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStroke
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStroke
|
||||
else backgroundStroke
|
||||
|
||||
binding.time.text =
|
||||
SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
|
||||
profiles = profiles,
|
||||
groups = groups
|
||||
).prepare()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class ServiceMessage(
|
||||
@@ -285,6 +183,7 @@ class MessagesHistoryAdapter constructor(
|
||||
message.action ?: return
|
||||
|
||||
binding.message.text = VkUtils.getActionMessageText(
|
||||
context = context,
|
||||
message = message,
|
||||
youPrefix = youPrefix,
|
||||
profiles = profiles,
|
||||
@@ -314,233 +213,6 @@ class MessagesHistoryAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentPhotosIncoming(
|
||||
private val binding: ItemMessageAttachmentPhotosInBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
val messageUser =
|
||||
if (message.isUser()) profiles[message.fromId]
|
||||
else null
|
||||
|
||||
val messageGroup =
|
||||
if (message.isGroup()) groups[message.fromId]
|
||||
else null
|
||||
|
||||
MessagesManager.loadMessageAvatar(
|
||||
message = message,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup,
|
||||
imageView = binding.avatar
|
||||
)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
MessagesManager.loadPhotos(
|
||||
context = context,
|
||||
message = message,
|
||||
photosContainer = binding.photosContainer
|
||||
)
|
||||
|
||||
MessagesManager.setMessageText(
|
||||
message = message,
|
||||
textView = binding.text
|
||||
)
|
||||
|
||||
binding.bubble.isVisible = binding.text.text.toString().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentPhotosOutgoing(
|
||||
private val binding: ItemMessageAttachmentPhotosOutBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
|
||||
MessagesManager.loadPhotos(
|
||||
context = context,
|
||||
message = message,
|
||||
photosContainer = binding.photosContainer
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentVideosIncoming(
|
||||
private val binding: ItemMessageAttachmentVideosInBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
val messageUser =
|
||||
if (message.isUser()) profiles[message.fromId]
|
||||
else null
|
||||
|
||||
val messageGroup =
|
||||
if (message.isGroup()) groups[message.fromId]
|
||||
else null
|
||||
|
||||
MessagesManager.loadMessageAvatar(
|
||||
message = message,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup,
|
||||
imageView = binding.avatar
|
||||
)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
MessagesManager.loadVideos(
|
||||
context = context,
|
||||
message = message,
|
||||
videosContainer = binding.videosContainer
|
||||
)
|
||||
|
||||
MessagesManager.setMessageText(
|
||||
message = message,
|
||||
textView = binding.text
|
||||
)
|
||||
|
||||
binding.bubble.isVisible = binding.text.text.toString().isNotEmpty()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class AttachmentStickerOutgoing(
|
||||
private val binding: ItemMessageAttachmentStickerOutBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
}
|
||||
|
||||
val sticker = message.attachments?.get(0) as? VkSticker ?: return
|
||||
val url = sticker.urlForSize(352)!!
|
||||
|
||||
binding.photo.layoutParams.also {
|
||||
it.width = 352
|
||||
it.height = 352
|
||||
}
|
||||
|
||||
binding.photo.load(url) { crossfade(150) }
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentStickerIncoming(
|
||||
private val binding: ItemMessageAttachmentStickerInBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
val messageUser: VkUser? = if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
} else null
|
||||
|
||||
val messageGroup: VkGroup? = if (message.isGroup()) {
|
||||
groups[message.fromId]
|
||||
} else null
|
||||
|
||||
val avatar = when {
|
||||
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
||||
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
||||
else -> null
|
||||
}
|
||||
|
||||
binding.avatar.load(avatar) { crossfade(100) }
|
||||
|
||||
val title = when {
|
||||
message.isUser() && messageUser != null -> messageUser.fullName
|
||||
message.isGroup() && messageGroup != null -> messageGroup.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
binding.avatar.setOnLongClickListener {
|
||||
Toast.makeText(context, title, Toast.LENGTH_SHORT).apply {
|
||||
setGravity(
|
||||
Gravity.START or Gravity.BOTTOM,
|
||||
0,
|
||||
-50
|
||||
)
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
|
||||
val sticker = message.attachments?.get(0) as? VkSticker ?: return
|
||||
val url = sticker.urlForSize(352)!!
|
||||
|
||||
binding.photo.layoutParams.also {
|
||||
it.width = 352
|
||||
it.height = 352
|
||||
}
|
||||
|
||||
binding.photo.load(url) { crossfade(150) }
|
||||
}
|
||||
}
|
||||
|
||||
private val actualSize get() = values.size
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@@ -556,13 +228,6 @@ class MessagesHistoryAdapter constructor(
|
||||
private const val OUTGOING = 4
|
||||
|
||||
|
||||
private const val ATTACHMENT_PHOTOS_IN = 101
|
||||
private const val ATTACHMENT_PHOTOS_OUT = 102
|
||||
private const val ATTACHMENT_VIDEOS_IN = 111
|
||||
private const val ATTACHMENT_VIDEOS_OUT = 112
|
||||
private const val ATTACHMENT_STICKER_IN = 121
|
||||
private const val ATTACHMENT_STICKER_OUT = 122
|
||||
|
||||
private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: VkMessage,
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.meloda.fast.screens.messages
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.viewbinding.library.fragment.viewBinding
|
||||
import androidx.core.view.isVisible
|
||||
@@ -76,6 +77,9 @@ class MessagesHistoryFragment :
|
||||
else -> null
|
||||
}
|
||||
|
||||
binding.title.ellipsize = TextUtils.TruncateAt.END
|
||||
binding.status.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
binding.title.text = title ?: "..."
|
||||
|
||||
val status = when {
|
||||
|
||||
@@ -64,7 +64,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
response.conversations?.let { baseConversations ->
|
||||
baseConversations.forEach { baseConversation ->
|
||||
baseConversation.asVkConversation(
|
||||
messages[baseConversation.lastMessageId]
|
||||
messages[baseConversation.last_message_id]
|
||||
).let { conversation -> conversations[conversation.id] = conversation }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isNotEmpty
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.widget.BoundedFrameLayout
|
||||
import com.meloda.fast.widget.BoundedLinearLayout
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object MessagesManager {
|
||||
|
||||
fun setRootMaxWidth(
|
||||
layout: View
|
||||
) {
|
||||
val maxWidth = (AppGlobal.screenWidth * 0.7).roundToInt()
|
||||
|
||||
if (layout is BoundedFrameLayout) {
|
||||
layout.maxWidth = maxWidth
|
||||
} else if (layout is BoundedLinearLayout) {
|
||||
layout.maxWidth = maxWidth
|
||||
}
|
||||
}
|
||||
|
||||
fun loadPhotos(
|
||||
context: Context,
|
||||
message: VkMessage,
|
||||
photosContainer: LinearLayoutCompat
|
||||
) {
|
||||
photosContainer.removeAllViews()
|
||||
|
||||
message.attachments?.let { attachments ->
|
||||
val photos = attachments.map { it as VkPhoto }
|
||||
|
||||
photos.forEach { photo ->
|
||||
val size = photo.sizeOfType('m') ?: return
|
||||
|
||||
val newPhoto = ShapeableImageView(context).also {
|
||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
it.shapeAppearanceModel =
|
||||
it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
val spacer = Space(context).also {
|
||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
AndroidUtils.px(5).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (photosContainer.isNotEmpty())
|
||||
photosContainer.addView(spacer)
|
||||
|
||||
photosContainer.addView(newPhoto)
|
||||
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadVideos(
|
||||
context: Context,
|
||||
message: VkMessage,
|
||||
videosContainer: LinearLayoutCompat
|
||||
) {
|
||||
videosContainer.removeAllViews()
|
||||
val playColor = ContextCompat.getColor(context, R.color.a3_700)
|
||||
val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
||||
|
||||
message.attachments?.let { attachments ->
|
||||
val photos = attachments.map { it as VkVideo }
|
||||
|
||||
photos.forEach { video ->
|
||||
val size = video.images[1] ?: return
|
||||
|
||||
val layout = FrameLayout(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
val newPhoto = ShapeableImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
|
||||
shapeAppearanceModel =
|
||||
shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
val play = AppCompatImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(50).roundToInt(),
|
||||
AndroidUtils.px(50).roundToInt()
|
||||
).also {
|
||||
it.gravity = Gravity.CENTER
|
||||
}
|
||||
|
||||
backgroundTintList = ColorStateList.valueOf(playBackgroundColor)
|
||||
imageTintList = ColorStateList.valueOf(playColor)
|
||||
|
||||
setBackgroundResource(R.drawable.ic_play_button_circle_background)
|
||||
setImageResource(R.drawable.ic_round_play_arrow_24)
|
||||
|
||||
setPadding(12)
|
||||
}
|
||||
|
||||
layout.addView(newPhoto)
|
||||
layout.addView(play)
|
||||
|
||||
val spacer = Space(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
AndroidUtils.px(5).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (videosContainer.isNotEmpty())
|
||||
videosContainer.addView(spacer)
|
||||
|
||||
videosContainer.addView(layout)
|
||||
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMessageAvatar(
|
||||
message: VkMessage,
|
||||
messageUser: VkUser?,
|
||||
messageGroup: VkGroup?,
|
||||
imageView: ImageView
|
||||
) {
|
||||
val avatar = when {
|
||||
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
||||
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
||||
else -> null
|
||||
}
|
||||
|
||||
imageView.load(avatar) { crossfade(100) }
|
||||
}
|
||||
|
||||
fun setMessageText(
|
||||
message: VkMessage,
|
||||
textView: TextView
|
||||
) {
|
||||
textView.text = VkUtils.prepareMessageText(message.text)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.VkSticker
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.widget.BoundedLinearLayout
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
class MessagesPreparator constructor(
|
||||
private val context: Context,
|
||||
|
||||
private val conversation: VkConversation,
|
||||
private val message: VkMessage,
|
||||
private val prevMessage: VkMessage? = null,
|
||||
private val nextMessage: VkMessage? = null,
|
||||
|
||||
private val bubble: BoundedLinearLayout? = null,
|
||||
private val bubbleStroke: View? = null,
|
||||
private val text: TextView? = null,
|
||||
private val avatar: ImageView? = null,
|
||||
private val title: TextView? = null,
|
||||
private val spacer: Space? = null,
|
||||
private val unread: ImageView? = null,
|
||||
private val time: TextView? = null,
|
||||
private val attachmentContainer: LinearLayoutCompat? = null,
|
||||
private val attachmentSpacer: Space? = null,
|
||||
|
||||
private val profiles: Map<Int, VkUser>,
|
||||
private val groups: Map<Int, VkGroup>
|
||||
) {
|
||||
|
||||
init {
|
||||
val maxWidth = (AppGlobal.screenWidth * 0.7).roundToInt()
|
||||
|
||||
if (bubble != null) bubble.maxWidth = maxWidth
|
||||
}
|
||||
|
||||
private val backgroundNormalIn =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_in_background)
|
||||
private val backgroundMiddleIn =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_in_background_middle)
|
||||
|
||||
private val backgroundNormalOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||
private val backgroundMiddleOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
||||
private val backgroundStrokeOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||
private val backgroundMiddleStrokeOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||
|
||||
fun prepare() {
|
||||
val messageUser: VkUser? = if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
} else null
|
||||
|
||||
val messageGroup: VkGroup? = if (message.isGroup()) {
|
||||
groups[message.fromId]
|
||||
} else null
|
||||
|
||||
if (unread != null) {
|
||||
unread.isVisible = message.isRead(conversation)
|
||||
}
|
||||
|
||||
if (bubble != null && time != null) {
|
||||
bubble.setOnClickListener { time.isVisible = !time.isVisible }
|
||||
}
|
||||
|
||||
if (attachmentContainer != null) {
|
||||
if (message.attachments.isNullOrEmpty()) {
|
||||
attachmentContainer.isVisible = false
|
||||
attachmentContainer.removeAllViews()
|
||||
} else {
|
||||
attachmentContainer.isVisible = true
|
||||
AttachmentInflater(
|
||||
context = context,
|
||||
container = attachmentContainer,
|
||||
message = message,
|
||||
groups = groups,
|
||||
profiles = profiles
|
||||
).inflate()
|
||||
}
|
||||
}
|
||||
|
||||
if (bubble != null) {
|
||||
val padding =
|
||||
AndroidUtils.px(if (!message.attachments.isNullOrEmpty()) 4 else 15).roundToInt()
|
||||
|
||||
bubble.setPadding(padding)
|
||||
|
||||
|
||||
// TODO: 9/23/2021 use external function
|
||||
bubble.background =
|
||||
if (!message.attachments.isNullOrEmpty() && message.attachments!![0] is VkSticker) null
|
||||
else {
|
||||
if (message.isOut) {
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalOut
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleOut
|
||||
else backgroundNormalOut
|
||||
} else {
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalIn
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleIn
|
||||
else backgroundNormalIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 9/23/2021 use external function
|
||||
bubbleStroke?.background =
|
||||
if (bubble?.background == null) null else {
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStrokeOut
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStrokeOut
|
||||
else backgroundStrokeOut
|
||||
}
|
||||
|
||||
if (bubble != null && text != null) {
|
||||
if (message.text == null) {
|
||||
text.isVisible = false
|
||||
bubble.isVisible = !message.attachments.isNullOrEmpty()
|
||||
bubbleStroke?.isVisible = bubble.isVisible
|
||||
} else {
|
||||
text.isVisible = true
|
||||
bubble.isVisible = true
|
||||
bubbleStroke?.isVisible = true
|
||||
text.text = VkUtils.prepareMessageText(message.text)
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar != null) {
|
||||
val avatarUrl = when {
|
||||
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
||||
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
||||
else -> null
|
||||
}
|
||||
|
||||
avatar.load(avatarUrl) { crossfade(100) }
|
||||
}
|
||||
|
||||
spacer?.isVisible = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
||||
|
||||
if (message.isPeerChat()) {
|
||||
|
||||
val fromDiffSender = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message)
|
||||
val fiveMinAgo = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
||||
|
||||
val change = (prevMessage?.date ?: 0) - message.date
|
||||
|
||||
Log.d(
|
||||
"Fast::MessagesPreparator",
|
||||
"text: ${message.text}; prevText: ${prevMessage?.text}; time change: $change; fromDiffSender: $fromDiffSender; fiveMinAgo: $fiveMinAgo; "
|
||||
)
|
||||
|
||||
title?.isVisible = fromDiffSender || fiveMinAgo
|
||||
|
||||
avatar?.isInvisible = fromDiffSender && fiveMinAgo
|
||||
} else {
|
||||
title?.isVisible = false
|
||||
avatar?.isVisible = false
|
||||
}
|
||||
|
||||
if (title != null) {
|
||||
val titleString = when {
|
||||
message.isUser() && messageUser != null -> messageUser.firstName
|
||||
message.isGroup() && messageGroup != null -> messageGroup.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
title.text = titleString
|
||||
title.measure(0, 0)
|
||||
|
||||
if (bubble != null) {
|
||||
if (title.isVisible) {
|
||||
bubble.minimumWidth = title.measuredWidth + 60
|
||||
} else {
|
||||
bubble.minimumWidth = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachmentSpacer?.isVisible =
|
||||
!message.attachments.isNullOrEmpty() && text?.isVisible == true
|
||||
|
||||
time?.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
|
||||
}
|
||||
}
|
||||
@@ -73,4 +73,11 @@ object AndroidUtils {
|
||||
return color
|
||||
}
|
||||
|
||||
fun bytesToHumanReadableSize(bytes: Double) = when {
|
||||
bytes >= 1 shl 30 -> "%.1f GB".format(bytes / (1 shl 30))
|
||||
bytes >= 1 shl 20 -> "%.1f MB".format(bytes / (1 shl 20))
|
||||
bytes >= 1 shl 10 -> "%.0f KB".format(bytes / (1 shl 10))
|
||||
else -> "$bytes B"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.meloda.fast.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class RoundedCornerLayout extends FrameLayout {
|
||||
private final static float CORNER_RADIUS = 40.0f;
|
||||
|
||||
private Bitmap maskBitmap;
|
||||
private Paint paint, maskPaint;
|
||||
private float cornerRadius;
|
||||
|
||||
public RoundedCornerLayout(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
}
|
||||
|
||||
public RoundedCornerLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundedCornerLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics);
|
||||
|
||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Bitmap offscreenBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas offscreenCanvas = new Canvas(offscreenBitmap);
|
||||
|
||||
super.draw(offscreenCanvas);
|
||||
|
||||
if (maskBitmap == null) {
|
||||
maskBitmap = createMask(getWidth(), getHeight());
|
||||
}
|
||||
|
||||
offscreenCanvas.drawBitmap(maskBitmap, 0f, 0f, maskPaint);
|
||||
canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint);
|
||||
}
|
||||
|
||||
private Bitmap createMask(int width, int height) {
|
||||
Bitmap mask = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
|
||||
Canvas canvas = new Canvas(mask);
|
||||
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.WHITE);
|
||||
|
||||
canvas.drawRect(0, 0, width, height, paint);
|
||||
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
canvas.drawRoundRect(new RectF(0, 0, width, height), cornerRadius, cornerRadius, paint);
|
||||
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.meloda.fast.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Region
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import com.meloda.fast.R
|
||||
|
||||
class RoundedFrameLayout : FrameLayout {
|
||||
/**
|
||||
* The corners than can be changed
|
||||
*/
|
||||
private var topLeftCornerRadius = 0f
|
||||
private var topRightCornerRadius = 0f
|
||||
private var bottomLeftCornerRadius = 0f
|
||||
private var bottomRightCornerRadius = 0f
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null, 0)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||
init(context, attrs, 0)
|
||||
}
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int
|
||||
) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs, defStyleAttr)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) {
|
||||
val typedArray = context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.RoundedFrameLayout, 0, 0
|
||||
)
|
||||
|
||||
topLeftCornerRadius =
|
||||
typedArray.getDimension(R.styleable.RoundedFrameLayout_topLeftCornerRadius, 0f)
|
||||
topRightCornerRadius =
|
||||
typedArray.getDimension(R.styleable.RoundedFrameLayout_topRightCornerRadius, 0f)
|
||||
bottomLeftCornerRadius =
|
||||
typedArray.getDimension(R.styleable.RoundedFrameLayout_bottomLeftCornerRadius, 0f)
|
||||
bottomRightCornerRadius =
|
||||
typedArray.getDimension(R.styleable.RoundedFrameLayout_bottomRightCornerRadius, 0f)
|
||||
|
||||
typedArray.recycle()
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
|
||||
override fun dispatchDraw(canvas: Canvas) {
|
||||
val count: Int = canvas.save()
|
||||
val path = Path()
|
||||
val cornerDimensions = floatArrayOf(
|
||||
topLeftCornerRadius, topLeftCornerRadius,
|
||||
topRightCornerRadius, topRightCornerRadius,
|
||||
bottomRightCornerRadius, bottomRightCornerRadius,
|
||||
bottomLeftCornerRadius, bottomLeftCornerRadius
|
||||
)
|
||||
path.addRoundRect(
|
||||
RectF(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat()),
|
||||
cornerDimensions,
|
||||
Path.Direction.CW
|
||||
)
|
||||
canvas.clipPath(path, Region.Op.INTERSECT)
|
||||
canvas.clipPath(path)
|
||||
super.dispatchDraw(canvas)
|
||||
canvas.restoreToCount(count)
|
||||
}
|
||||
|
||||
fun setTopLeftCornerRadius(topLeftCornerRadius: Float) {
|
||||
this.topLeftCornerRadius = topLeftCornerRadius
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setTopRightCornerRadius(topRightCornerRadius: Float) {
|
||||
this.topRightCornerRadius = topRightCornerRadius
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setBottomLeftCornerRadius(bottomLeftCornerRadius: Float) {
|
||||
this.bottomLeftCornerRadius = bottomLeftCornerRadius
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setBottomRightCornerRadius(bottomRightCornerRadius: Float) {
|
||||
this.bottomRightCornerRadius = bottomRightCornerRadius
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
@@ -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="M15.1,19.37l1,1.74c-0.96,0.44 -2.01,0.73 -3.1,0.84v-2.02C13.74,19.84 14.44,19.65 15.1,19.37zM4.07,13H2.05c0.11,1.1 0.4,2.14 0.84,3.1l1.74,-1C4.35,14.44 4.16,13.74 4.07,13zM15.1,4.63l1,-1.74C15.14,2.45 14.1,2.16 13,2.05v2.02C13.74,4.16 14.44,4.35 15.1,4.63zM19.93,11h2.02c-0.11,-1.1 -0.4,-2.14 -0.84,-3.1l-1.74,1C19.65,9.56 19.84,10.26 19.93,11zM8.9,19.37l-1,1.74c0.96,0.44 2.01,0.73 3.1,0.84v-2.02C10.26,19.84 9.56,19.65 8.9,19.37zM11,4.07V2.05c-1.1,0.11 -2.14,0.4 -3.1,0.84l1,1.74C9.56,4.35 10.26,4.16 11,4.07zM18.36,7.17l1.74,-1.01c-0.63,-0.87 -1.4,-1.64 -2.27,-2.27l-1.01,1.74C17.41,6.08 17.92,6.59 18.36,7.17zM4.63,8.9l-1.74,-1C2.45,8.86 2.16,9.9 2.05,11h2.02C4.16,10.26 4.35,9.56 4.63,8.9zM19.93,13c-0.09,0.74 -0.28,1.44 -0.56,2.1l1.74,1c0.44,-0.96 0.73,-2.01 0.84,-3.1H19.93zM16.83,18.36l1.01,1.74c0.87,-0.63 1.64,-1.4 2.27,-2.27l-1.74,-1.01C17.92,17.41 17.41,17.92 16.83,18.36zM7.17,5.64L6.17,3.89C5.29,4.53 4.53,5.29 3.9,6.17l1.74,1.01C6.08,6.59 6.59,6.08 7.17,5.64zM5.64,16.83L3.9,17.83c0.63,0.87 1.4,1.64 2.27,2.27l1.01,-1.74C6.59,17.92 6.08,17.41 5.64,16.83zM13,7h-2v5.41l4.29,4.29l1.41,-1.41L13,11.59V7z" />
|
||||
</vector>
|
||||
@@ -144,7 +144,7 @@
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/n1_900"
|
||||
android:textSize="24sp"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
@@ -154,6 +154,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.7"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/n1_900"
|
||||
tools:text="Online" />
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="@drawable/ic_play_button_circle_background"
|
||||
android:backgroundTint="@color/a3_200">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_round_play_arrow_24"
|
||||
app:tint="@color/a3_700" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/google_sans_regular"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/n1_800"
|
||||
android:textSize="18sp"
|
||||
tools:text="Даня, дай Фаст" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/artist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:fontFamily="@font/roboto_regular"
|
||||
android:textColor="@color/n1_800"
|
||||
tools:text="Эльчин Оруджев | 0:36" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="@drawable/ic_play_button_circle_background"
|
||||
android:backgroundTint="@color/a3_200">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_attachment_file"
|
||||
app:tint="@color/a3_700" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/google_sans_regular"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/n1_800"
|
||||
android:textSize="18sp"
|
||||
tools:text="Kids" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:fontFamily="@font/roboto_regular"
|
||||
android:textColor="@color/n1_800"
|
||||
tools:text="3.28 TB" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/preview"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
app:fontFamily="@font/google_sans_regular"
|
||||
tools:text="melod1n" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?textColorPrimary"
|
||||
app:fontFamily="@font/roboto_regular"
|
||||
tools:text="vk.com/melod1n" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?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/photosContainer"
|
||||
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>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<com.meloda.fast.widget.BoundedFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/photosContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedFrameLayout>
|
||||
|
||||
</layout>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<Space
|
||||
android:id="@+id/spacer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?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:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="2.5dp">
|
||||
|
||||
<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" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/postTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:alpha="0.9"
|
||||
android:text="@string/message_attachments_wall"
|
||||
android:textColor="?textColorPrimary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
app:fontFamily="@font/google_sans_regular"
|
||||
tools:text="Typical Programmer" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?textColorPrimary"
|
||||
tools:text="1 hour ago" />
|
||||
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -26,7 +26,8 @@
|
||||
android:id="@+id/spacer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
@@ -44,12 +45,14 @@
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.meloda.fast.widget.BoundedFrameLayout
|
||||
<com.meloda.fast.widget.BoundedLinearLayout
|
||||
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"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
@@ -57,11 +60,24 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="15dp"
|
||||
android:autoLink="all"
|
||||
android:textColor="@color/n1_800"
|
||||
tools:text="This" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedFrameLayout>
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
|
||||
<com.meloda.fast.widget.CircleImageView
|
||||
android:id="@+id/unread"
|
||||
@@ -72,6 +88,19 @@
|
||||
android:src="@color/a3_200" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="12dp"
|
||||
android:textColor="?textColorSecondaryVariant"
|
||||
android:visibility="gone"
|
||||
tools:layout_height="18dp"
|
||||
tools:text="12:00"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</layout>
|
||||
@@ -38,25 +38,38 @@
|
||||
android:padding="1.5dp"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.meloda.fast.widget.BoundedFrameLayout
|
||||
<com.meloda.fast.widget.BoundedLinearLayout
|
||||
android:id="@+id/bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_message_out_background">
|
||||
android:background="@drawable/ic_message_out_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp">
|
||||
|
||||
<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|start"
|
||||
android:padding="15dp"
|
||||
android:textColor="@color/n1_900"
|
||||
tools:text="This is test" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedFrameLayout>
|
||||
</FrameLayout>
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/time"
|
||||
@@ -65,12 +78,11 @@
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:textColor="?textColorSecondaryVariant"
|
||||
android:visibility="gone"
|
||||
tools:layout_height="18dp"
|
||||
tools:text="12:00" />
|
||||
tools:text="12:00"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -25,6 +25,13 @@
|
||||
<attr name="bounded_height" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="RoundedFrameLayout">
|
||||
<attr name="topLeftCornerRadius" format="dimension" />
|
||||
<attr name="topRightCornerRadius" format="dimension" />
|
||||
<attr name="bottomLeftCornerRadius" format="dimension" />
|
||||
<attr name="bottomRightCornerRadius" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="WrapTextView">
|
||||
<attr name="fixWrap" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<plurals name="attachment_photos">
|
||||
<item quantity="one">@string/message_attachments_photo_one</item>
|
||||
<item quantity="few">@string/message_attachments_photo_few</item>
|
||||
<item quantity="many">@string/message_attachments_photo_many</item>
|
||||
<item quantity="other">@string/message_attachments_photo_other</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="attachment_videos">
|
||||
<item quantity="one">@string/message_attachments_video_one</item>
|
||||
<item quantity="few">@string/message_attachments_video_few</item>
|
||||
<item quantity="many">@string/message_attachments_video_many</item>
|
||||
<item quantity="other">@string/message_attachments_video_other</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="attachment_audios">
|
||||
<item quantity="one">@string/message_attachments_audio_one</item>
|
||||
<item quantity="few">@string/message_attachments_audio_few</item>
|
||||
<item quantity="many">@string/message_attachments_audio_many</item>
|
||||
<item quantity="other">@string/message_attachments_audio_other</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="attachment_files">
|
||||
<item quantity="one">@string/message_attachments_files_one</item>
|
||||
<item quantity="few">@string/message_attachments_files_few</item>
|
||||
<item quantity="many">@string/message_attachments_files_many</item>
|
||||
<item quantity="other">@string/message_attachments_files_other</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
||||
@@ -43,7 +43,7 @@
|
||||
<string name="week_short">W</string>
|
||||
<string name="day_short">D</string>
|
||||
<string name="time_now">Now</string>
|
||||
<string name="message_input_hint">Start typing here...</string>
|
||||
<string name="message_input_hint">Start typing here…</string>
|
||||
<string name="input_login_hint">Input login</string>
|
||||
<string name="input_password_hint">Input password</string>
|
||||
<string name="input_code_hint">Input code</string>
|
||||
@@ -51,4 +51,62 @@
|
||||
<string name="unknown_error_occurred">Unknown error occurred</string>
|
||||
<string name="authorization_failed">Authorization failed</string>
|
||||
|
||||
|
||||
<string name="message_action_chat_created">%s created «%s»</string>
|
||||
<string name="message_action_chat_renamed">%s renamed chat to «%s»</string>
|
||||
<string name="message_action_chat_photo_update">%s updated the chat photo</string>
|
||||
<string name="message_action_chat_photo_remove">%s deleted the chat photo</string>
|
||||
<string name="message_action_chat_user_left">%s left the chat</string>
|
||||
<string name="message_action_chat_user_kicked">%s kicked %s</string>
|
||||
<string name="message_action_chat_user_returned">%s returned to chat</string>
|
||||
<string name="message_action_chat_user_invited">%s invited %s</string>
|
||||
<string name="message_action_chat_user_joined_by_link">%s joined the chat via link</string>
|
||||
<string name="message_action_chat_user_joined_by_call_link">%s joined the call via link</string>
|
||||
<string name="message_action_chat_pin_message">%s pinned message</string>
|
||||
<string name="message_action_chat_unpin_message">%s unpinned message</string>
|
||||
<string name="message_action_chat_screenshot">%s took a screenshot</string>
|
||||
<string name="message_action_chat_style_update">%s changed chat theme</string>
|
||||
|
||||
|
||||
<string name="message_attachments_photo_one">Photo</string>
|
||||
<string name="message_attachments_photo_few">%d photos</string>
|
||||
<string name="message_attachments_photo_many">%d photos</string>
|
||||
<string name="message_attachments_photo_other">%d photos</string>
|
||||
|
||||
<string name="message_attachments_video_one">Video</string>
|
||||
<string name="message_attachments_video_few">%d videos</string>
|
||||
<string name="message_attachments_video_many">%d videos</string>
|
||||
<string name="message_attachments_video_other">%d videos</string>
|
||||
|
||||
<string name="message_attachments_audio_one">Audio</string>
|
||||
<string name="message_attachments_audio_few">%d audios</string>
|
||||
<string name="message_attachments_audio_many">%d audios</string>
|
||||
<string name="message_attachments_audio_other">%d audios</string>
|
||||
|
||||
<string name="message_attachments_files_one">File</string>
|
||||
<string name="message_attachments_files_few">%d files</string>
|
||||
<string name="message_attachments_files_many">%d files</string>
|
||||
<string name="message_attachments_files_other">%d files</string>
|
||||
|
||||
|
||||
<string name="message_attachments_voice">Voice message</string>
|
||||
<string name="message_attachments_link">Link</string>
|
||||
<string name="message_attachments_mini_app">Mini App</string>
|
||||
<string name="message_attachments_sticker">Sticker</string>
|
||||
<string name="message_attachments_gift">Gift</string>
|
||||
<string name="message_attachments_wall">Post</string>
|
||||
<string name="message_attachments_graffiti">Graffiti</string>
|
||||
<string name="message_attachments_poll">Poll</string>
|
||||
<string name="message_attachments_wall_reply">Wall comment</string>
|
||||
<string name="message_attachments_call">Call</string>
|
||||
<string name="message_attachments_call_in_progress">Current call</string>
|
||||
<string name="message_attachments_event">Event</string>
|
||||
<string name="message_attachments_curator">Curator</string>
|
||||
|
||||
<string name="file_size_in_bytes">%d bytes</string>
|
||||
|
||||
<string name="post_type_community">Community post</string>
|
||||
<string name="post_type_user">User post</string>
|
||||
<string name="post_type_unknown">Post</string>
|
||||
<string name="message_attachments_story">Story</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user