mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-20 13:31:38 +00:00
desktop, android: show group reactions
This commit is contained in:
@@ -1962,6 +1962,12 @@ class ACIReaction(
|
||||
val chatReaction: CIReaction
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberReaction(
|
||||
val groupMember: GroupMember,
|
||||
val reactionTs: Instant
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class CIReaction(
|
||||
val chatDir: CIDirection,
|
||||
|
||||
+14
@@ -951,6 +951,14 @@ object ChatController {
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiGetReactionMembers(rh: Long?, groupId: Long, itemId: Long, reaction: MsgReaction): List<MemberReaction>? {
|
||||
val userId = currentUserId("apiGetReactionMembers")
|
||||
val r = sendCmd(rh, CC.ApiGetReactionMembers(userId, groupId, itemId, reaction))
|
||||
if (r is CR.ReactionMembers) return r.memberReactions
|
||||
Log.e(TAG, "apiGetReactionMembers bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List<Long>, mode: CIDeleteMode): List<ChatItemDeletion>? {
|
||||
val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode))
|
||||
if (r is CR.ChatItemsDeleted) return r.chatItemDeletions
|
||||
@@ -3092,6 +3100,7 @@ sealed class CC {
|
||||
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
|
||||
class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List<Long>): CC()
|
||||
class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
|
||||
class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC()
|
||||
class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List<Long>): CC()
|
||||
class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List<Long>, val ttl: Int?): CC()
|
||||
class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC()
|
||||
@@ -3253,6 +3262,7 @@ sealed class CC {
|
||||
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
|
||||
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}"
|
||||
is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
|
||||
is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}"
|
||||
is ApiForwardChatItems -> {
|
||||
val ttlStr = if (ttl != null) "$ttl" else "default"
|
||||
"/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}"
|
||||
@@ -3409,6 +3419,7 @@ sealed class CC {
|
||||
is ApiDeleteChatItem -> "apiDeleteChatItem"
|
||||
is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem"
|
||||
is ApiChatItemReaction -> "apiChatItemReaction"
|
||||
is ApiGetReactionMembers -> "apiGetReactionMembers"
|
||||
is ApiForwardChatItems -> "apiForwardChatItems"
|
||||
is ApiPlanForwardChatItems -> "apiPlanForwardChatItems"
|
||||
is ApiNewGroup -> "apiNewGroup"
|
||||
@@ -5429,6 +5440,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR()
|
||||
@Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR()
|
||||
@Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR()
|
||||
@Serializable @SerialName("reactionMembers") class ReactionMembers(val user: UserRef, val memberReactions: List<MemberReaction>): CR()
|
||||
@Serializable @SerialName("chatItemsDeleted") class ChatItemsDeleted(val user: UserRef, val chatItemDeletions: List<ChatItemDeletion>, val byUser: Boolean): CR()
|
||||
@Serializable @SerialName("forwardPlan") class ForwardPlan(val user: UserRef, val itemsCount: Int, val chatItemIds: List<Long>, val forwardConfirmation: ForwardConfirmation? = null): CR()
|
||||
// group events
|
||||
@@ -5610,6 +5622,7 @@ sealed class CR {
|
||||
is ChatItemUpdated -> "chatItemUpdated"
|
||||
is ChatItemNotChanged -> "chatItemNotChanged"
|
||||
is ChatItemReaction -> "chatItemReaction"
|
||||
is ReactionMembers -> "reactionMembers"
|
||||
is ChatItemsDeleted -> "chatItemsDeleted"
|
||||
is ForwardPlan -> "forwardPlan"
|
||||
is GroupCreated -> "groupCreated"
|
||||
@@ -5783,6 +5796,7 @@ sealed class CR {
|
||||
is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem))
|
||||
is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem))
|
||||
is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}")
|
||||
is ReactionMembers -> withUser(user, "memberReactions: ${json.encodeToString(memberReactions)}")
|
||||
is ChatItemsDeleted -> withUser(user, "${chatItemDeletions.map { (deletedChatItem, toChatItem) -> "deletedChatItem: ${json.encodeToString(deletedChatItem)}\ntoChatItem: ${json.encodeToString(toChatItem)}" }} \nbyUser: $byUser")
|
||||
is ForwardPlan -> withUser(user, "itemsCount: $itemsCount\nchatItemIds: ${json.encodeToString(chatItemIds)}\nforwardConfirmation: ${json.encodeToString(forwardConfirmation)}")
|
||||
is GroupCreated -> withUser(user, json.encodeToString(groupInfo))
|
||||
|
||||
+1
-1
@@ -1062,7 +1062,7 @@ fun BoxScope.ChatItemsList(
|
||||
highlightedItems.value = setOf()
|
||||
}
|
||||
}
|
||||
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
||||
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+70
-4
@@ -28,6 +28,7 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.math.*
|
||||
|
||||
@@ -84,6 +85,7 @@ fun ChatItemView(
|
||||
setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit,
|
||||
showItemDetails: (ChatInfo, ChatItem) -> Unit,
|
||||
reveal: (Boolean) -> Unit,
|
||||
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||
developerTools: Boolean,
|
||||
showViaProxy: Boolean,
|
||||
showTimestamp: Boolean,
|
||||
@@ -116,14 +118,52 @@ fun ChatItemView(
|
||||
fun ChatItemReactions() {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.chatItemOffset(cItem, itemSeparation.largeGap, inverted = true, revealed = true)) {
|
||||
cItem.reactions.forEach { r ->
|
||||
val showReactionMenu = remember { mutableStateOf(false) }
|
||||
val reactionMembers = remember { mutableStateOf(emptyList<MemberReaction>()) }
|
||||
|
||||
var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp))
|
||||
if (cInfo.featureEnabled(ChatFeature.Reactions) && (cItem.allowAddReaction || r.userReacted)) {
|
||||
modifier = modifier.clickable {
|
||||
setReaction(cInfo, cItem, !r.userReacted, r.reaction)
|
||||
}
|
||||
if (cInfo.featureEnabled(ChatFeature.Reactions)) {
|
||||
modifier = modifier
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
if (cItem.allowAddReaction || r.userReacted) {
|
||||
setReaction(cInfo, cItem, !r.userReacted, r.reaction)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
if (cInfo is ChatInfo.Group) {
|
||||
withBGApi {
|
||||
try {
|
||||
val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction)
|
||||
if (members != null) {
|
||||
showReactionMenu.value = true
|
||||
reactionMembers.value = members
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "hatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
ReactionIcon(r.reaction.text, fontSize = 12.sp)
|
||||
DefaultDropdownMenu(showMenu = showReactionMenu) {
|
||||
reactionMembers.value.forEach { m ->
|
||||
ItemAction(
|
||||
text = m.groupMember.displayName,
|
||||
composable = { ProfileImage(44.dp, m.groupMember.image) },
|
||||
onClick = {
|
||||
if (cInfo is ChatInfo.Group && cInfo.groupInfo.membership.groupMemberId != m.groupMember.groupMemberId) {
|
||||
showMemberInfo(cInfo.groupInfo, m.groupMember)
|
||||
showReactionMenu.value = false
|
||||
} else {
|
||||
showReactionMenu.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (r.totalReacted > 1) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
@@ -781,6 +821,30 @@ fun ItemAction(text: String, icon: Painter, color: Color = Color.Unspecified, on
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ItemAction(
|
||||
text: String,
|
||||
composable: @Composable () -> Unit,
|
||||
color: Color = Color.Unspecified,
|
||||
onClick: () -> Unit) {
|
||||
val finalColor = if (color == Color.Unspecified) {
|
||||
MenuTextColor
|
||||
} else color
|
||||
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1F)
|
||||
.padding(end = 15.dp),
|
||||
color = finalColor
|
||||
)
|
||||
composable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) {
|
||||
val finalColor = if (color == Color.Unspecified) {
|
||||
@@ -1101,6 +1165,7 @@ fun PreviewChatItemView(
|
||||
setReaction = { _, _, _, _ -> },
|
||||
showItemDetails = { _, _ -> },
|
||||
reveal = {},
|
||||
showMemberInfo = { _, _ ->},
|
||||
developerTools = false,
|
||||
showViaProxy = false,
|
||||
showTimestamp = true,
|
||||
@@ -1145,6 +1210,7 @@ fun PreviewChatItemViewDeletedContent() {
|
||||
setReaction = { _, _, _, _ -> },
|
||||
showItemDetails = { _, _ -> },
|
||||
reveal = {},
|
||||
showMemberInfo = { _, _ ->},
|
||||
developerTools = false,
|
||||
showViaProxy = false,
|
||||
preview = true,
|
||||
|
||||
Reference in New Issue
Block a user