Implement action messages in messages history

This commit is contained in:
2024-07-16 04:52:47 +03:00
committed by GitHub
parent fa5f707e52
commit 9df35bf6bf
16 changed files with 709 additions and 95 deletions
@@ -4,10 +4,12 @@ import android.content.res.Resources
interface ResourceProvider { interface ResourceProvider {
val resources: Resources
fun getString(resId: Int): String fun getString(resId: Int): String
} }
class ResourceProviderImpl(private val resources: Resources) : ResourceProvider { class ResourceProviderImpl(override val resources: Resources) : ResourceProvider {
override fun getString(resId: Int): String { override fun getString(resId: Int): String {
return resources.getString(resId) return resources.getString(resId)
@@ -180,6 +180,13 @@ object AppSettings {
) )
set(value) = put(SettingsKeys.KEY_SHOW_EMOJI_BUTTON, value) set(value) = put(SettingsKeys.KEY_SHOW_EMOJI_BUTTON, value)
var showTimeInActionMessages: Boolean
get() = get(
SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES
)
set(value) = put(SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES, value)
var showDebugCategory: Boolean var showDebugCategory: Boolean
get() = get( get() = get(
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
@@ -29,6 +29,9 @@ object SettingsKeys {
const val DEFAULT_APPEARANCE_LANGUAGE = "" const val DEFAULT_APPEARANCE_LANGUAGE = ""
const val KEY_APPEARANCE_USE_BLUR = "appearance_use_blur" const val KEY_APPEARANCE_USE_BLUR = "appearance_use_blur"
const val DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR = false const val DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR = false
const val KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES =
"appearance_show_time_in_action_messages"
const val DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES = false
const val KEY_FEATURES_FAST_TEXT = "features_fast_text" const val KEY_FEATURES_FAST_TEXT = "features_fast_text"
const val DEFAULT_VALUE_FEATURES_FAST_TEXT = "¯\\_(ツ)_/¯" const val DEFAULT_VALUE_FEATURES_FAST_TEXT = "¯\\_(ツ)_/¯"
@@ -23,6 +23,7 @@ interface UserSettings {
val longPollInBackground: StateFlow<Boolean> val longPollInBackground: StateFlow<Boolean>
val useBlur: StateFlow<Boolean> val useBlur: StateFlow<Boolean>
val showEmojiButton: StateFlow<Boolean> val showEmojiButton: StateFlow<Boolean>
val showTimeInActionMessages: StateFlow<Boolean>
val showDebugCategory: StateFlow<Boolean> val showDebugCategory: StateFlow<Boolean>
fun onUseContactNamesChanged(use: Boolean) fun onUseContactNamesChanged(use: Boolean)
@@ -42,6 +43,7 @@ interface UserSettings {
fun onLongPollInBackgroundChanged(inBackground: Boolean) fun onLongPollInBackgroundChanged(inBackground: Boolean)
fun onUseBlurChanged(use: Boolean) fun onUseBlurChanged(use: Boolean)
fun onShowEmojiButtonChanged(show: Boolean) fun onShowEmojiButtonChanged(show: Boolean)
fun onShowTimeInActionMessagesChanged(show: Boolean)
fun onShowDebugCategoryChanged(show: Boolean) fun onShowDebugCategoryChanged(show: Boolean)
} }
@@ -64,6 +66,8 @@ class UserSettingsImpl : UserSettings {
override val longPollInBackground = MutableStateFlow(AppSettings.Debug.longPollInBackground) override val longPollInBackground = MutableStateFlow(AppSettings.Debug.longPollInBackground)
override val useBlur = MutableStateFlow(AppSettings.Debug.useBlur) override val useBlur = MutableStateFlow(AppSettings.Debug.useBlur)
override val showEmojiButton = MutableStateFlow(AppSettings.Debug.showEmojiButton) override val showEmojiButton = MutableStateFlow(AppSettings.Debug.showEmojiButton)
override val showTimeInActionMessages =
MutableStateFlow(AppSettings.Debug.showTimeInActionMessages)
override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory) override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory)
override fun onUseContactNamesChanged(use: Boolean) { override fun onUseContactNamesChanged(use: Boolean) {
@@ -118,6 +122,10 @@ class UserSettingsImpl : UserSettings {
showEmojiButton.value = show showEmojiButton.value = show
} }
override fun onShowTimeInActionMessagesChanged(show: Boolean) {
showTimeInActionMessages.value = show
}
override fun onShowDebugCategoryChanged(show: Boolean) { override fun onShowDebugCategoryChanged(show: Boolean) {
showDebugCategory.value = show showDebugCategory.value = show
} }
@@ -1,7 +1,6 @@
package com.meloda.app.fast.messageshistory package com.meloda.app.fast.messageshistory
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources
import android.util.Log import android.util.Log
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
@@ -14,14 +13,18 @@ import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.listenValue import com.meloda.app.fast.common.extensions.listenValue
import com.meloda.app.fast.common.extensions.setValue import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.common.extensions.updateValue import com.meloda.app.fast.common.extensions.updateValue
import com.meloda.app.fast.common.provider.ResourceProvider
import com.meloda.app.fast.data.LongPollUpdatesParser import com.meloda.app.fast.data.LongPollUpdatesParser
import com.meloda.app.fast.data.VkMemoryCache import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.data.api.conversations.ConversationsUseCase import com.meloda.app.fast.data.api.conversations.ConversationsUseCase
import com.meloda.app.fast.data.api.messages.MessagesUseCase import com.meloda.app.fast.data.api.messages.MessagesUseCase
import com.meloda.app.fast.data.processState import com.meloda.app.fast.data.processState
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.datastore.SettingsKeys import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.messageshistory.model.ActionMode import com.meloda.app.fast.messageshistory.model.ActionMode
import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState
import com.meloda.app.fast.messageshistory.model.UiItem
import com.meloda.app.fast.messageshistory.navigation.MessagesHistory import com.meloda.app.fast.messageshistory.navigation.MessagesHistory
import com.meloda.app.fast.messageshistory.util.asPresentation import com.meloda.app.fast.messageshistory.util.asPresentation
import com.meloda.app.fast.messageshistory.util.extractAvatar import com.meloda.app.fast.messageshistory.util.extractAvatar
@@ -62,7 +65,8 @@ class MessagesHistoryViewModelImpl(
private val messagesUseCase: MessagesUseCase, private val messagesUseCase: MessagesUseCase,
private val conversationsUseCase: ConversationsUseCase, private val conversationsUseCase: ConversationsUseCase,
private val preferences: SharedPreferences, private val preferences: SharedPreferences,
private val resources: Resources, private val resourceProvider: ResourceProvider,
private val userSettings: UserSettings,
updatesParser: LongPollUpdatesParser, updatesParser: LongPollUpdatesParser,
savedStateHandle: SavedStateHandle savedStateHandle: SavedStateHandle
) : MessagesHistoryViewModel, ViewModel() { ) : MessagesHistoryViewModel, ViewModel() {
@@ -92,6 +96,8 @@ class MessagesHistoryViewModelImpl(
updatesParser.onMessageEdited(::handleEditedMessage) updatesParser.onMessageEdited(::handleEditedMessage)
updatesParser.onMessageIncomingRead(::handleReadIncomingEvent) updatesParser.onMessageIncomingRead(::handleReadIncomingEvent)
updatesParser.onMessageOutgoingRead(::handleReadOutgoingEvent) updatesParser.onMessageOutgoingRead(::handleReadOutgoingEvent)
userSettings.showTimeInActionMessages.listenValue(::toggleShowTimeInActionMessages)
} }
override fun onRefresh() { override fun onRefresh() {
@@ -169,19 +175,23 @@ class MessagesHistoryViewModelImpl(
} }
val newMessage = message.asPresentation( val newMessage = message.asPresentation(
resourceProvider = resourceProvider,
showDate = false, showDate = false,
showName = false, showName = false,
prevMessage = prevMessage, prevMessage = prevMessage,
nextMessage = null nextMessage = null,
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
) )
newMessages.add(0, newMessage) newMessages.add(0, newMessage)
prevMessage?.let { prev -> prevMessage?.let { prev ->
newMessages[1] = prev.asPresentation( newMessages[1] = prev.asPresentation(
resourceProvider = resourceProvider,
showDate = false, showDate = false,
showName = false, showName = false,
prevMessage = prevMessage, prevMessage = prevMessage,
nextMessage = messages.value.first() nextMessage = messages.value.first(),
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
) )
} }
@@ -196,10 +206,12 @@ class MessagesHistoryViewModelImpl(
.indexOfFirstOrNull { it.id == message.id } .indexOfFirstOrNull { it.id == message.id }
?.let { index -> ?.let { index ->
val newMessage = message.asPresentation( val newMessage = message.asPresentation(
resourceProvider = resourceProvider,
showDate = false, showDate = false,
showName = false, showName = false,
prevMessage = messages.value.getOrNull(index + 1), prevMessage = messages.value.getOrNull(index + 1),
nextMessage = messages.value.getOrNull(index - 1) nextMessage = messages.value.getOrNull(index - 1),
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
) )
val newMessages = screenState.value.messages.toMutableList() val newMessages = screenState.value.messages.toMutableList()
@@ -248,10 +260,12 @@ class MessagesHistoryViewModelImpl(
val loadedMessages = fullMessages.mapIndexed { index, message -> val loadedMessages = fullMessages.mapIndexed { index, message ->
message.asPresentation( message.asPresentation(
resourceProvider = resourceProvider,
showDate = false, showDate = false,
showName = false, showName = false,
prevMessage = messages.getOrNull(index + 1), prevMessage = messages.getOrNull(index + 1),
nextMessage = messages.getOrNull(index - 1), nextMessage = messages.getOrNull(index - 1),
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
) )
} }
@@ -268,11 +282,8 @@ class MessagesHistoryViewModelImpl(
?.let { conversation -> ?.let { conversation ->
newState = newState.copy( newState = newState.copy(
title = conversation.extractTitle( title = conversation.extractTitle(
useContactName = preferences.getBoolean( useContactName = AppSettings.General.useContactNames,
SettingsKeys.KEY_USE_CONTACT_NAMES, resources = resourceProvider.resources
SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
),
resources = resources
), ),
avatar = conversation.extractAvatar() avatar = conversation.extractAvatar()
) )
@@ -337,10 +348,12 @@ class MessagesHistoryViewModelImpl(
val newMessages = screenState.value.messages.toMutableList() val newMessages = screenState.value.messages.toMutableList()
val newUiMessage = newMessage.asPresentation( val newUiMessage = newMessage.asPresentation(
resourceProvider = resourceProvider,
showDate = false, showDate = false,
showName = false, showName = false,
prevMessage = messages.value.firstOrNull(), prevMessage = messages.value.firstOrNull(),
nextMessage = null nextMessage = null,
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
) )
newMessages.add(0, newUiMessage) newMessages.add(0, newUiMessage)
@@ -373,7 +386,9 @@ class MessagesHistoryViewModelImpl(
val messages = screenState.value.messages.toMutableList() val messages = screenState.value.messages.toMutableList()
messages.indexOfOrNull(newUiMessage)?.let { index -> messages.indexOfOrNull(newUiMessage)?.let { index ->
messages[index] = messages[index].copy(id = messageId) (messages[index] as? UiItem.Message)?.let { message ->
messages[index] = message.copy(id = messageId)
}
} }
screenState.setValue { old -> old.copy(messages = messages) } screenState.setValue { old -> old.copy(messages = messages) }
@@ -490,6 +505,24 @@ class MessagesHistoryViewModelImpl(
} }
} }
private fun toggleShowTimeInActionMessages(show: Boolean) {
val messages = messages.value
val uiMessages = messages.mapIndexed { index, item ->
item.asPresentation(
resourceProvider = resourceProvider,
showDate = false,
showName = false,
prevMessage = messages.getOrNull(index + 1),
nextMessage = messages.getOrNull(index - 1),
showTimeInActionMessages = show
)
}
screenState.setValue { old ->
old.copy(messages = uiMessages)
}
}
companion object { companion object {
const val MESSAGES_LOAD_COUNT = 30 const val MESSAGES_LOAD_COUNT = 30
} }
@@ -10,7 +10,7 @@ data class MessagesHistoryScreenState(
val title: String, val title: String,
val status: String?, val status: String?,
val avatar: UiImage, val avatar: UiImage,
val messages: List<UiMessage>, val messages: List<UiItem>,
val message: String, val message: String,
val attachments: List<VkAttachment>, val attachments: List<VkAttachment>,
val isLoading: Boolean, val isLoading: Boolean,
@@ -0,0 +1,35 @@
package com.meloda.app.fast.messageshistory.model
import androidx.compose.ui.text.AnnotatedString
import com.meloda.app.fast.common.model.UiImage
sealed class UiItem(
open val id: Int,
val cmId: Int
) {
data class Message(
override val id: Int,
val conversationMessageId: Int,
val text: String?,
val isOut: Boolean,
val fromId: Int,
val date: String,
val randomId: Int,
val isInChat: Boolean,
val name: String,
val showDate: Boolean,
val showAvatar: Boolean,
val showName: Boolean,
val avatar: UiImage,
val isEdited: Boolean
) : UiItem(id, conversationMessageId)
data class ActionMessage(
override val id: Int,
val conversationMessageId: Int,
val text: AnnotatedString,
val actionCmId: Int?
) : UiItem(id, conversationMessageId)
}
@@ -1,20 +0,0 @@
package com.meloda.app.fast.messageshistory.model
import com.meloda.app.fast.common.model.UiImage
data class UiMessage(
val id: Int,
val conversationMessageId: Int,
val text: String?,
val isOut: Boolean,
val fromId: Int,
val date: String,
val randomId: Int,
val isInChat: Boolean,
val name: String,
val showDate: Boolean,
val showAvatar: Boolean,
val showName: Boolean,
val avatar: UiImage,
val isEdited: Boolean
)
@@ -0,0 +1,67 @@
package com.meloda.app.fast.messageshistory.presentation
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.meloda.app.fast.messageshistory.model.UiItem
@Composable
fun ActionMessageItem(
item: UiItem.ActionMessage,
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
Text(
text = item.text,
modifier = modifier
.padding(horizontal = 32.dp)
.clip(RoundedCornerShape(12.dp))
.then(
if (item.actionCmId != null) {
Modifier.clickable(onClick = onClick)
}
else Modifier
)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp))
.fillMaxWidth()
.padding(
horizontal = 32.dp,
vertical = 4.dp
),
textAlign = TextAlign.Center
)
}
@Preview
@Composable
fun ActionMessageItemPreview() {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(10.dp)
) {
ActionMessageItem(
item = UiItem.ActionMessage(
id = 0,
text = buildAnnotatedString {
append("You pinned message \"wow hello there\"")
},
actionCmId = null,
conversationMessageId = 2135
)
)
}
}
@@ -25,12 +25,12 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import coil.imageLoader import coil.imageLoader
import com.meloda.app.fast.messageshistory.model.UiMessage import com.meloda.app.fast.messageshistory.model.UiItem
@Composable @Composable
fun IncomingMessageBubble( fun IncomingMessageBubble(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
message: UiMessage, message: UiItem.Message,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -78,6 +78,8 @@ import com.meloda.app.fast.messageshistory.MessagesHistoryViewModel
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl
import com.meloda.app.fast.messageshistory.model.ActionMode import com.meloda.app.fast.messageshistory.model.ActionMode
import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState
import com.meloda.app.fast.messageshistory.util.firstMessage
import com.meloda.app.fast.messageshistory.util.indexOfMessageByCmId
import com.meloda.app.fast.model.BaseError import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.ui.theme.LocalThemeConfig import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList import com.meloda.app.fast.ui.util.ImmutableList
@@ -142,6 +144,8 @@ fun MessagesHistoryScreen(
) { ) {
val view = LocalView.current val view = LocalView.current
val coroutineScope = rememberCoroutineScope()
val preferences: SharedPreferences = koinInject() val preferences: SharedPreferences = koinInject()
val currentTheme = LocalThemeConfig.current val currentTheme = LocalThemeConfig.current
@@ -250,7 +254,7 @@ fun MessagesHistoryScreen(
onChatMaterialsDropdownItemClicked( onChatMaterialsDropdownItemClicked(
screenState.conversationId, screenState.conversationId,
screenState.messages.first().conversationMessageId screenState.messages.firstMessage().conversationMessageId
) )
}, },
text = { text = {
@@ -313,7 +317,14 @@ fun MessagesHistoryScreen(
immutableMessages = ImmutableList.copyOf(screenState.messages), immutableMessages = ImmutableList.copyOf(screenState.messages),
isPaginating = screenState.isPaginating, isPaginating = screenState.isPaginating,
enableAnimations = animationsEnabled, enableAnimations = animationsEnabled,
messageBarHeight = messageBarHeight messageBarHeight = messageBarHeight,
onRequestScrollToCmId = { cmId ->
coroutineScope.launch {
listState.animateScrollToItem(
index = screenState.messages.indexOfMessageByCmId(cmId)
)
}
}
) )
Column( Column(
@@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.meloda.app.fast.messageshistory.model.UiMessage import com.meloda.app.fast.messageshistory.model.UiItem
import com.meloda.app.fast.ui.theme.LocalThemeConfig import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList import com.meloda.app.fast.ui.util.ImmutableList
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
@@ -31,10 +31,11 @@ fun MessagesList(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
hazeState: HazeState, hazeState: HazeState,
listState: LazyListState, listState: LazyListState,
immutableMessages: ImmutableList<UiMessage>, immutableMessages: ImmutableList<UiItem>,
isPaginating: Boolean, isPaginating: Boolean,
enableAnimations: Boolean, enableAnimations: Boolean,
messageBarHeight: Dp messageBarHeight: Dp,
onRequestScrollToCmId: (cmId: Int) -> Unit = {}
) { ) {
val messages = immutableMessages.toList() val messages = immutableMessages.toList()
val currentTheme = LocalThemeConfig.current val currentTheme = LocalThemeConfig.current
@@ -65,9 +66,28 @@ fun MessagesList(
items( items(
items = messages, items = messages,
key = UiMessage::id, key = UiItem::id,
) { message -> contentType = { item ->
if (message.isOut) { when (item) {
is UiItem.ActionMessage -> "action_message"
is UiItem.Message -> "message"
}
}
) { item ->
when (item) {
is UiItem.ActionMessage -> {
ActionMessageItem(
item = item,
onClick = {
if (item.actionCmId != null) {
onRequestScrollToCmId(item.actionCmId)
}
}
)
}
is UiItem.Message -> {
if (item.isOut) {
OutgoingMessageBubble( OutgoingMessageBubble(
modifier = modifier =
Modifier.then( Modifier.then(
@@ -77,7 +97,7 @@ fun MessagesList(
) )
else Modifier else Modifier
), ),
message = message, message = item,
) )
} else { } else {
IncomingMessageBubble( IncomingMessageBubble(
@@ -89,9 +109,11 @@ fun MessagesList(
) )
else Modifier else Modifier
), ),
message = message, message = item,
) )
} }
}
}
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
@@ -14,12 +14,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.meloda.app.fast.common.extensions.orDots import com.meloda.app.fast.common.extensions.orDots
import com.meloda.app.fast.messageshistory.model.UiMessage import com.meloda.app.fast.messageshistory.model.UiItem
@Composable @Composable
fun OutgoingMessageBubble( fun OutgoingMessageBubble(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
message: UiMessage, message: UiItem.Message,
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
@@ -0,0 +1,17 @@
package com.meloda.app.fast.messageshistory.util
import com.meloda.app.fast.messageshistory.model.UiItem
fun List<UiItem>.firstMessage(): UiItem.Message = first() as UiItem.Message
fun List<UiItem>.indexOfMessageById(messageId: Int): Int =
indexOfFirst { it.id == messageId }
fun List<UiItem>.findMessageById(messageId: Int): UiItem.Message =
first { it.id == messageId } as UiItem.Message
fun List<UiItem>.indexOfMessageByCmId(cmId: Int): Int =
indexOfFirst { it.cmId == cmId }
fun List<UiItem>.findMessageByCmId(cmId: Int): UiItem.Message =
first { it.cmId == cmId } as UiItem.Message
@@ -1,17 +1,22 @@
package com.meloda.app.fast.messageshistory.util package com.meloda.app.fast.messageshistory.util
import android.content.res.Resources import android.content.res.Resources
import com.meloda.app.fast.common.model.UiImage import androidx.compose.ui.text.AnnotatedString
import com.meloda.app.fast.common.model.UiText import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import com.meloda.app.fast.common.UserConfig import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.orDots import com.meloda.app.fast.common.extensions.orDots
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.model.parseString import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.common.provider.ResourceProvider
import com.meloda.app.fast.data.VkMemoryCache import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.ui.R import com.meloda.app.fast.messageshistory.model.UiItem
import com.meloda.app.fast.messageshistory.model.UiMessage
import com.meloda.app.fast.model.api.PeerType import com.meloda.app.fast.model.api.PeerType
import com.meloda.app.fast.model.api.domain.VkConversation import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkMessage import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.ui.R
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import com.meloda.app.fast.ui.R as UiR import com.meloda.app.fast.ui.R as UiR
@@ -85,11 +90,25 @@ fun VkConversation.extractTitle(
}.parseString(resources).orDots() }.parseString(resources).orDots()
fun VkMessage.asPresentation( fun VkMessage.asPresentation(
resourceProvider: ResourceProvider,
showDate: Boolean, showDate: Boolean,
showName: Boolean, showName: Boolean,
prevMessage: VkMessage?, prevMessage: VkMessage?,
nextMessage: VkMessage? nextMessage: VkMessage?,
): UiMessage = UiMessage( showTimeInActionMessages: Boolean
): UiItem = when {
action != null -> UiItem.ActionMessage(
id = id,
conversationMessageId = conversationMessageId,
text = extractActionText(
resources = resourceProvider.resources,
youPrefix = resourceProvider.getString(R.string.you_message_prefix),
showTime = showTimeInActionMessages
) ?: buildAnnotatedString { },
actionCmId = actionConversationMessageId
)
else -> UiItem.Message(
id = id, id = id,
conversationMessageId = conversationMessageId, conversationMessageId = conversationMessageId,
text = text, text = text,
@@ -105,6 +124,8 @@ fun VkMessage.asPresentation(
avatar = extractAvatar(), avatar = extractAvatar(),
isEdited = updateTime != null isEdited = updateTime != null
) )
}
fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean { fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean {
if (isOut) return false if (isOut) return false
@@ -115,3 +136,399 @@ fun VkMessage.extractShowName(prevMessage: VkMessage?): Boolean {
if (isOut || !isPeerChat()) return false if (isOut || !isPeerChat()) return false
return prevMessage == null || prevMessage.fromId != fromId return prevMessage == null || prevMessage.fromId != fromId
} }
fun VkMessage.extractActionText(
resources: Resources,
youPrefix: String,
showTime: Boolean
): AnnotatedString? {
val lastMessage = this
val action = lastMessage.action ?: return null
val formattedMessageDate =
SimpleDateFormat("HH:mm", Locale.getDefault()).format(lastMessage.date * 1000L)
val fromId = lastMessage.fromId
val text = lastMessage.actionText.orDots()
val groupName = lastMessage.group?.name.orDots()
val userName = lastMessage.user?.fullName.orDots()
val actionGroupName = lastMessage.actionGroup?.name.orDots()
val actionUserName = lastMessage.actionUser?.fullName.orDots()
val memberId = lastMessage.actionMemberId
val isMemberUser = (memberId ?: 0) > 0
val isMemberGroup = (memberId ?: 0) < 0
val prefix = when {
lastMessage.fromId == UserConfig.userId -> youPrefix
lastMessage.isGroup() -> groupName
lastMessage.isUser() -> userName
else -> null
}.orDots()
val memberPrefix = when {
memberId == UserConfig.userId -> youPrefix
isMemberUser -> actionUserName
isMemberGroup -> actionGroupName
else -> null
}.orDots()
return buildAnnotatedString {
when (action) {
VkMessage.Action.CHAT_CREATE -> {
val string = UiText.ResourceParams(
UiR.string.message_action_chat_created,
listOf(prefix, text)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
val textStartIndex = string.indexOf(text)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = textStartIndex,
end = textStartIndex + text.length
)
}
VkMessage.Action.CHAT_TITLE_UPDATE -> {
val string = UiText.ResourceParams(
UiR.string.message_action_chat_renamed,
listOf(prefix, text)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
val textStartIndex = string.indexOf(text)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = textStartIndex,
end = textStartIndex + text.length
)
}
VkMessage.Action.CHAT_PHOTO_UPDATE -> {
UiText.ResourceParams(
UiR.string.message_action_chat_photo_update,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_PHOTO_REMOVE -> {
UiText.ResourceParams(
UiR.string.message_action_chat_photo_remove,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_KICK_USER -> {
if (memberId == fromId) {
UiText.ResourceParams(
UiR.string.message_action_chat_user_left,
listOf(memberPrefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = memberPrefix.length
)
} else {
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else lastMessage.actionUser.toString()
val string = UiText.ResourceParams(
UiR.string.message_action_chat_user_kicked,
listOf(prefix, postfix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
val postfixStartIndex = string.indexOf(postfix)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = postfixStartIndex,
end = postfixStartIndex + postfix.length
)
}
}
VkMessage.Action.CHAT_INVITE_USER -> {
if (memberId == lastMessage.fromId) {
UiText.ResourceParams(
UiR.string.message_action_chat_user_returned,
listOf(memberPrefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = memberPrefix.length
)
} else {
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else lastMessage.actionUser.toString()
val string = UiText.ResourceParams(
UiR.string.message_action_chat_user_invited,
listOf(memberPrefix, postfix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
val postfixStartIndex = string.indexOf(postfix)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = postfixStartIndex,
end = postfixStartIndex + postfix.length
)
}
}
VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> {
UiText.ResourceParams(
UiR.string.message_action_chat_user_joined_by_link,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_INVITE_USER_BY_CALL -> {
UiText.ResourceParams(
UiR.string.message_action_chat_user_joined_by_call,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> {
UiText.ResourceParams(
UiR.string.message_action_chat_user_joined_by_call_link,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_PIN_MESSAGE -> {
// TODO: 16/07/2024, Danil Nikolaev: get pinned message by cmid
// val messageText = lastMessage.text.orEmpty().trim()
// val croppedMessage = messageText.take(40)
// val hasMessageText = messageText.isNotEmpty()
UiText.ResourceParams(
UiR.string.message_action_chat_pin_message,
listOf(prefix)
).parseString(resources)
.orEmpty()
// .let { text ->
// if (hasMessageText) {
// text.plus("«%s»".format(croppedMessage))
// .plus(if (messageText.length > 40) "..." else "")
// } else text
// }
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
// if (hasMessageText) {
// val croppedIndex = fullText.indexOf(croppedMessage)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.Medium),
// start = croppedIndex - 1,
// end = croppedIndex - 1 + croppedMessage.length + 1
// )
// }
}
VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
UiText.ResourceParams(
UiR.string.message_action_chat_unpin_message,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_SCREENSHOT -> {
UiText.ResourceParams(
UiR.string.message_action_chat_screenshot,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
VkMessage.Action.CHAT_STYLE_UPDATE -> {
UiText.ResourceParams(
UiR.string.message_action_chat_style_update,
listOf(prefix)
).parseString(resources)
.orEmpty()
.let { text ->
if (showTime) {
text.plus("\n")
.plus(formattedMessageDate)
} else text
}.also(::append)
addStyle(
style = SpanStyle(fontWeight = FontWeight.Medium),
start = 0,
end = prefix.length
)
}
}
}
}
@@ -230,6 +230,12 @@ class SettingsViewModelImpl(
userSettings.onShowEmojiButtonChanged(show) userSettings.onShowEmojiButtonChanged(show)
} }
SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES -> {
val show = newValue as? Boolean
?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES
userSettings.onShowTimeInActionMessagesChanged(show)
}
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY -> { SettingsKeys.KEY_SHOW_DEBUG_CATEGORY -> {
val show = newValue as? Boolean ?: false val show = newValue as? Boolean ?: false
userSettings.onShowDebugCategoryChanged(show) userSettings.onShowDebugCategoryChanged(show)
@@ -349,12 +355,6 @@ class SettingsViewModelImpl(
) )
} }
} }
val debugLongPollBackground = SettingsItem.Switch(
key = SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND,
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
text = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
)
val activityTitle = SettingsItem.Title( val activityTitle = SettingsItem.Title(
key = "activity", key = "activity",
@@ -382,6 +382,12 @@ class SettingsViewModelImpl(
title = UiText.Simple("Show alert after crash"), title = UiText.Simple("Show alert after crash"),
text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)") text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
) )
val debugLongPollBackground = SettingsItem.Switch(
key = SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND,
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
text = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
)
val debugUseBlur = SettingsItem.Switch( val debugUseBlur = SettingsItem.Switch(
key = SettingsKeys.KEY_APPEARANCE_USE_BLUR, key = SettingsKeys.KEY_APPEARANCE_USE_BLUR,
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR, defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR,
@@ -394,6 +400,11 @@ class SettingsViewModelImpl(
text = UiText.Simple("Show emoji button in chat panel"), text = UiText.Simple("Show emoji button in chat panel"),
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
) )
val debugShowTimeInActionMessages = SettingsItem.Switch(
key = SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
title = UiText.Simple("Show time in action messages")
)
val debugHideDebugList = SettingsItem.TitleText( val debugHideDebugList = SettingsItem.TitleText(
key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST, key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST,
@@ -432,7 +443,8 @@ class SettingsViewModelImpl(
debugShowCrashAlert, debugShowCrashAlert,
debugLongPollBackground, debugLongPollBackground,
debugUseBlur, debugUseBlur,
debugShowEmojiButton debugShowEmojiButton,
debugShowTimeInActionMessages
).forEach(debugList::add) ).forEach(debugList::add)
debugList += debugHideDebugList debugList += debugHideDebugList