refactor(@desktop/chat-messages): add/remove reactions

This commit is contained in:
Sale Djenic 2021-12-20 15:21:35 +01:00
parent 13543ae14f
commit 179b0f5a36
24 changed files with 475 additions and 226 deletions

View File

@ -74,6 +74,18 @@ method init*(self: Controller) =
return
self.delegate.onChatUnmuted()
self.events.on(SIGNAL_MESSAGE_REACTION_ADDED) do(e:Args):
let args = MessageAddRemoveReactionArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.onReactionAdded(args.messageId, args.emojiId, args.reactionId)
self.events.on(SIGNAL_MESSAGE_REACTION_REMOVED) do(e:Args):
let args = MessageAddRemoveReactionArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.onReactionRemoved(args.messageId, args.emojiId, args.reactionId)
method getMyChatId*(self: Controller): string =
return self.chatId

View File

@ -58,6 +58,18 @@ method init*(self: Controller) =
return
self.delegate.onPinUnpinMessage(args.messageId, false)
self.events.on(SIGNAL_MESSAGE_REACTION_ADDED) do(e:Args):
let args = MessageAddRemoveReactionArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.onReactionAdded(args.messageId, args.emojiId, args.reactionId)
self.events.on(SIGNAL_MESSAGE_REACTION_REMOVED) do(e:Args):
let args = MessageAddRemoveReactionArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.onReactionRemoved(args.messageId, args.emojiId, args.reactionId)
method getChatId*(self: Controller): string =
return self.chatId
@ -71,20 +83,10 @@ method belongsToCommunity*(self: Controller): bool =
return self.belongsToCommunity
method addReaction*(self: Controller, messageId: string, emojiId: int) =
let (res, err) = self.messageService.addReaction(self.chatId, messageId, emojiId)
if(err.len != 0):
error "an error has occurred while saving reaction: ", err
return
self.messageService.addReaction(self.chatId, messageId, emojiId)
self.delegate.onReactionAdded(messageId, emojiId, res)
method removeReaction*(self: Controller, messageId: string, reactionId: string) =
let (res, err) = self.messageService.removeReaction(reactionId)
if(err.len != 0):
error "an error has occurred while removing reaction: ", err
return
self.delegate.onReactionRemoved(messageId, reactionId)
method removeReaction*(self: Controller, messageId: string, emojiId: int, reactionId: string) =
self.messageService.removeReaction(reactionId, self.chatId, messageId, emojiId)
method pinUnpinMessage*(self: Controller, messageId: string, pin: bool) =
self.messageService.pinUnpinMessage(self.chatId, messageId, pin)

View File

@ -27,7 +27,7 @@ method belongsToCommunity*(self: AccessInterface): bool {.base.} =
method addReaction*(self: AccessInterface, messageId: string, emojiId: int) {.base.} =
raise newException(ValueError, "No implementation available")
method removeReaction*(self: AccessInterface, messageId: string, reactionId: string) {.base.} =
method removeReaction*(self: AccessInterface, messageId: string, emojiId: int, reactionId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method pinUnpinMessage*(self: AccessInterface, messageId: string, pin: bool) {.base.} =

View File

@ -1,9 +1,10 @@
import NimQml
import NimQml, chronicles
import io_interface
import ../io_interface as delegate_interface
import view, controller
import ../../../../shared_models/message_model
import ../../../../shared_models/message_item
import ../../../../shared_models/message_reaction_item
import ../../../../../global/global_singleton
import ../../../../../../app_service/service/contacts/service as contact_service
@ -14,6 +15,9 @@ import eventemitter
export io_interface
logScope:
topics = "messages-module"
const CHAT_IDENTIFIER_MESSAGE_ID = "chat-identifier-message-id"
type
@ -92,7 +96,14 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
for r in reactions:
if(r.messageId == m.id):
item.addReaction(r.emojiId, m.`from`, r.id)
var emojiIdAsEnum: EmojiId
if(message_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)):
let userWhoAddedThisReaction = self.controller.getContactById(r.`from`)
let didIReactWithThisEmoji = userWhoAddedThisReaction.id == singletonInstance.userProfile.getPubKey()
item.addReaction(emojiIdAsEnum, didIReactWithThisEmoji, userWhoAddedThisReaction.id,
userWhoAddedThisReaction.userNameOrAlias(), r.id)
else:
error "wrong emoji id found when loading messages"
for p in pinnedMessages:
if(p.message.id == m.id):
@ -109,26 +120,33 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
self.view.model().prependItems(viewItems)
method toggleReaction*(self: Module, messageId: string, emojiId: int) =
let item = self.view.model().getItemWithMessageId(messageId)
let myName = singletonInstance.userProfile.getName()
if(item.shouldAddReaction(emojiId, myName)):
self.controller.addReaction(messageId, emojiId)
var emojiIdAsEnum: EmojiId
if(message_reaction_item.toEmojiIdAsEnum(emojiId, emojiIdAsEnum)):
let item = self.view.model().getItemWithMessageId(messageId)
let myPublicKey = singletonInstance.userProfile.getPubKey()
if(item.shouldAddReaction(emojiIdAsEnum, myPublicKey)):
self.controller.addReaction(messageId, emojiId)
else:
let reactionId = item.getReactionId(emojiIdAsEnum, myPublicKey)
self.controller.removeReaction(messageId, emojiId, reactionId)
else:
let reactionId = item.getReactionId(emojiId, myName)
self.controller.removeReaction(messageId, reactionId)
error "wrong emoji id found on reaction added response", emojiId
method onReactionAdded*(self: Module, messageId: string, emojiId: int, reactionId: string) =
let myName = singletonInstance.userProfile.getName()
self.view.model().addReaction(messageId, emojiId, myName, reactionId)
var emojiIdAsEnum: EmojiId
if(message_reaction_item.toEmojiIdAsEnum(emojiId, emojiIdAsEnum)):
let myPublicKey = singletonInstance.userProfile.getPubKey()
let myName = singletonInstance.userProfile.getName()
self.view.model().addReaction(messageId, emojiIdAsEnum, true, myPublicKey, myName, reactionId)
else:
error "wrong emoji id found on reaction added response", emojiId
method onReactionRemoved*(self: Module, messageId: string, reactionId: string) =
self.view.model().removeReaction(messageId, reactionId)
method getNamesReactedWithEmojiIdForMessageId*(self: Module, messageId: string, emojiId: int): seq[string] =
let pubKeysForEmojiId = self.view.model().getPubKeysReactedWithEmojiIdForMessageId(messageId, emojiId)
for pk in pubKeysForEmojiId:
let (name, _, _) = self.controller.getContactNameAndImage(pk)
result.add(name)
method onReactionRemoved*(self: Module, messageId: string, emojiId: int, reactionId: string) =
var emojiIdAsEnum: EmojiId
if(message_reaction_item.toEmojiIdAsEnum(emojiId, emojiIdAsEnum)):
self.view.model().removeReaction(messageId, emojiIdAsEnum, reactionId)
else:
error "wrong emoji id found on reaction remove response", emojiId
method pinUnpinMessage*(self: Module, messageId: string, pin: bool) =
self.controller.pinUnpinMessage(messageId, pin)

View File

@ -7,7 +7,7 @@ method newMessagesLoaded*(self: AccessInterface, messages: seq[MessageDto], reac
method onReactionAdded*(self: AccessInterface, messageId: string, emojiId: int, reactionId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onReactionRemoved*(self: AccessInterface, messageId: string, reactionId: string) {.base.} =
method onReactionRemoved*(self: AccessInterface, messageId: string, emojiId: int, reactionId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onPinUnpinMessage*(self: AccessInterface, messageId: string, pin: bool) {.base.} =

View File

@ -7,10 +7,6 @@ method toggleReaction*(self: AccessInterface, messageId: string, emojiId: int) {
method pinUnpinMessage*(self: AccessInterface, messageId: string, pin: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method getNamesReactedWithEmojiIdForMessageId*(self: AccessInterface, messageId: string, emojiId: int): seq[string]
{.base.} =
raise newException(ValueError, "No implementation available")
method getChatType*(self: AccessInterface): int {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -36,9 +36,6 @@ QtObject:
proc toggleReaction*(self: View, messageId: string, emojiId: int) {.slot.} =
self.delegate.toggleReaction(messageId, emojiId)
proc getNamesReactedWithEmojiIdForMessageId*(self: View, messageId: string, emojiId: int): string {.slot.} =
return $(%* self.delegate.getNamesReactedWithEmojiIdForMessageId(messageId, emojiId))
proc pinMessage*(self: View, messageId: string) {.slot.} =
self.delegate.pinUnpinMessage(messageId, true)

View File

@ -4,6 +4,7 @@ import ../io_interface as delegate_interface
import view, controller
import ../../../shared_models/message_model as pinned_msg_model
import ../../../shared_models/message_item as pinned_msg_item
import ../../../shared_models/message_reaction_item as pinned_msg_reaction_item
import ../../../../global/global_singleton
import input_area/module as input_area_module
@ -151,8 +152,14 @@ proc buildPinnedMessageItem(self: Module, messageId: string, item: var pinned_ms
for r in reactions:
if(r.messageId == m.id):
# m.`from` should be replaced by appropriate ens/alias when we have that part refactored
item.addReaction(r.emojiId, m.`from`, r.id)
var emojiIdAsEnum: pinned_msg_reaction_item.EmojiId
if(pinned_msg_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)):
let userWhoAddedThisReaction = self.controller.getContactById(r.`from`)
let didIReactWithThisEmoji = userWhoAddedThisReaction.id == singletonInstance.userProfile.getPubKey()
item.addReaction(emojiIdAsEnum, didIReactWithThisEmoji, userWhoAddedThisReaction.id,
userWhoAddedThisReaction.userNameOrAlias(), r.id)
else:
error "wrong emoji id found when loading messages"
return true
@ -194,3 +201,19 @@ method onChatMuted*(self: Module) =
method onChatUnmuted*(self: Module) =
self.view.setMuted(false)
method onReactionAdded*(self: Module, messageId: string, emojiId: int, reactionId: string) =
var emojiIdAsEnum: EmojiId
if(pinned_msg_reaction_item.toEmojiIdAsEnum(emojiId, emojiIdAsEnum)):
let myPublicKey = singletonInstance.userProfile.getPubKey()
let myName = singletonInstance.userProfile.getName()
self.view.pinnedModel().addReaction(messageId, emojiIdAsEnum, true, myPublicKey, myName, reactionId)
else:
error "(pinned) wrong emoji id found on reaction added response", emojiId
method onReactionRemoved*(self: Module, messageId: string, emojiId: int, reactionId: string) =
var emojiIdAsEnum: EmojiId
if(pinned_msg_reaction_item.toEmojiIdAsEnum(emojiId, emojiIdAsEnum)):
self.view.pinnedModel().removeReaction(messageId, emojiIdAsEnum, reactionId)
else:
error "(pinned) wrong emoji id found on reaction remove response", emojiId

View File

@ -14,3 +14,9 @@ method onChatMuted*(self: AccessInterface) {.base.} =
method onChatUnmuted*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onReactionAdded*(self: AccessInterface, messageId: string, emojiId: int, reactionId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onReactionRemoved*(self: AccessInterface, messageId: string, emojiId: int, reactionId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,4 +1,6 @@
import Tables, json, strformat
import json, strformat
import message_reaction_model, message_reaction_item
type
ContentType* {.pure.} = enum
@ -38,8 +40,7 @@ type
timestamp: int64
contentType: ContentType
messageType: int
reactions: OrderedTable[int, seq[tuple[publicKey: string, reactionId: string]]] # [emojiId, list of [user publicKey reacted with the emojiId, reaction id]]
reactionIds: seq[string]
reactionsModel: MessageReactionModel
pinned: bool
proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderLocalName, senderIcon: string,
@ -62,6 +63,7 @@ proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderL
result.contentType = contentType
result.messageType = messageType
result.pinned = false
result.reactionsModel = newMessageReactionModel()
proc `$`*(self: Item): string =
result = fmt"""Item(
@ -79,7 +81,8 @@ proc `$`*(self: Item): string =
timestamp:{$self.timestamp},
contentType:{$self.contentType.int},
messageType:{$self.messageType},
pinned:{$self.pinned}
pinned:{$self.pinned},
messageReactions: [{$self.reactionsModel}]
)"""
proc id*(self: Item): string {.inline.} =
@ -139,67 +142,21 @@ proc pinned*(self: Item): bool {.inline.} =
proc `pinned=`*(self: Item, value: bool) {.inline.} =
self.pinned = value
proc shouldAddReaction*(self: Item, emojiId: int, publicKey: string): bool =
for k, values in self.reactions:
if(k != emojiId):
continue
proc reactionsModel*(self: Item): MessageReactionModel {.inline.} =
self.reactionsModel
for t in values:
if(t.publicKey == publicKey):
return false
proc shouldAddReaction*(self: Item, emojiId: EmojiId, userPublicKey: string): bool =
return self.reactionsModel.shouldAddReaction(emojiId, userPublicKey)
return true
proc getReactionId*(self: Item, emojiId: EmojiId, userPublicKey: string): string =
return self.reactionsModel.getReactionId(emojiId, userPublicKey)
proc getReactionId*(self: Item, emojiId: int, publicKey: string): string =
for k, values in self.reactions:
if(k != emojiId):
continue
proc addReaction*(self: Item, emojiId: EmojiId, didIReactWithThisEmoji: bool, userPublicKey: string,
userDisplayName: string, reactionId: string) =
self.reactionsModel.addReaction(emojiId, didIReactWithThisEmoji, userPublicKey, userDisplayName, reactionId)
for t in values:
if(t.publicKey == publicKey):
return t.reactionId
# we should never be here, since this is a controlled call
return ""
proc addReaction*(self: Item, emojiId: int, publicKey: string, reactionId: string) =
if(not self.reactions.contains(emojiId)):
self.reactions[emojiId] = @[]
self.reactions[emojiId].add((publicKey, reactionId))
proc removeReaction*(self: Item, reactionId: string) =
var key = -1
var index = -1
for k, values in self.reactions:
var i = -1
for t in values:
i += 1
if(t.reactionId == reactionId):
key = k
index = i
break
if(key == -1 or index == -1):
return
self.reactions[key].del(index)
if(self.reactions[key].len == 0):
self.reactions.del(key)
proc getPubKeysReactedWithEmojiId*(self: Item, emojiId: int): seq[string] =
if(not self.reactions.contains(emojiId)):
return
for v in self.reactions[emojiId]:
result.add(v.publicKey)
proc getCountsForReactions*(self: Item): seq[JsonNode] =
for k, v in self.reactions:
if(self.reactions[k].len == 0):
continue
result.add(%* {"emojiId": k, "counts": v.len})
proc removeReaction*(self: Item, emojiId: EmojiId, reactionId: string) =
self.reactionsModel.removeReaction(emojiId, reactionId)
proc toJsonNode*(self: Item): JsonNode =
result = %* {

View File

@ -1,6 +1,6 @@
import NimQml, Tables, json, strutils, strformat
import message_item
import message_item, message_reaction_item
type
ModelRole {.pure.} = enum
@ -24,7 +24,7 @@ type
# GapFrom
# GapTo
Pinned
CountsForReactions
Reactions
QtObject:
type
@ -80,7 +80,7 @@ QtObject:
# ModelRole.GapFrom.int:"gapFrom",
# ModelRole.GapTo.int:"gapTo",
ModelRole.Pinned.int:"pinned",
ModelRole.CountsForReactions.int:"countsForReactions",
ModelRole.Reactions.int:"reactions",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -134,8 +134,8 @@ QtObject:
# result = newQVariant(item.gapTo)
of ModelRole.Pinned:
result = newQVariant(item.pinned)
of ModelRole.CountsForReactions:
result = newQVariant($(%* item.getCountsForReactions))
of ModelRole.Reactions:
result = newQVariant(item.reactionsModel)
proc findIndexForMessageId(self: Model, messageId: string): int =
for i in 0 ..< self.items.len:
@ -187,30 +187,26 @@ QtObject:
return self.items[ind]
proc addReaction*(self: Model, messageId: string, emojiId: int, name: string, reactionId: string) =
proc addReaction*(self: Model, messageId: string, emojiId: EmojiId, didIReactWithThisEmoji: bool,
userPublicKey: string, userDisplayName: string, reactionId: string) =
let ind = self.findIndexForMessageId(messageId)
if(ind == -1):
return
self.items[ind].addReaction(emojiId, name, reactionId)
self.items[ind].addReaction(emojiId, didIReactWithThisEmoji, userPublicKey, userDisplayName, reactionId)
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.CountsForReactions.int])
self.dataChanged(index, index, @[ModelRole.Reactions.int])
proc removeReaction*(self: Model, messageId: string, reactionId: string) =
proc removeReaction*(self: Model, messageId: string, emojiId: EmojiId, reactionId: string) =
let ind = self.findIndexForMessageId(messageId)
if(ind == -1):
return
self.items[ind].removeReaction(reactionId)
self.items[ind].removeReaction(emojiId, reactionId)
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.CountsForReactions.int])
proc getPubKeysReactedWithEmojiIdForMessageId*(self: Model, messageId: string, emojiId: int): seq[string] =
for i in 0 ..< self.items.len:
if(self.items[i].id == messageId):
return self.items[i].getPubKeysReactedWithEmojiId(emojiId)
self.dataChanged(index, index, @[ModelRole.Reactions.int])
proc pinUnpinMessage*(self: Model, messageId: string, pin: bool) =
let ind = self.findIndexForMessageId(messageId)

View File

@ -0,0 +1,88 @@
import json, strformat
type
EmojiId* {.pure.} = enum
Heart = 1,
Thumbsup,
Thumbsdown,
Laughing,
Cry,
Angry
type
ReactionDetails* = object
publicKey: string
displayName: string
reactionId: string
type
MessageReactionItem* = object
emojiId: EmojiId
didIReactWithThisEmoji: bool
reactions: seq[ReactionDetails]
proc toEmojiIdAsEnum*(emojiId: int, emojiIdAsEnum: var EmojiId): bool =
if(emojiId >= ord(low(EmojiId)) or emojiId <= ord(high(EmojiId))):
emojiIdAsEnum = EmojiId(emojiId)
return true
return false
proc initMessageReactionItem*(emojiId: EmojiId): MessageReactionItem =
result.emojiId = emojiId
proc `$`*(self: MessageReactionItem): string =
var reactions = ""
for r in self.reactions:
reactions = reactions & "displayName: " & r.displayName & " publicKey: " & r.publicKey & " reactionId: " &
r.reactionId & "\n"
result = fmt"""MessageReactionItem(
emojiId: {self.emojiId},
didIReactWithThisEmoji: {self.didIReactWithThisEmoji},
reactionsCount: {self.reactions.len},
reactions: {reactions}
]"""
proc emojiId*(self: MessageReactionItem): EmojiId {.inline.} =
self.emojiId
proc didIReactWithThisEmoji*(self: MessageReactionItem): bool {.inline.} =
self.didIReactWithThisEmoji
proc numberOfReactions*(self: MessageReactionItem): int {.inline.} =
self.reactions.len
proc jsonArrayOfUsersReactedWithThisEmoji*(self: MessageReactionItem): JsonNode {.inline.} =
var users: seq[string]
for r in self.reactions:
users.add(r.displayName)
return %* users
proc shouldAddReaction*(self: MessageReactionItem, userPublicKey: string): bool =
for r in self.reactions:
if (r.publicKey == userPublicKey):
return false
return true
proc getReactionId*(self: MessageReactionItem, userPublicKey: string): string =
for r in self.reactions:
if (r.publicKey == userPublicKey):
return r.reactionId
return ""
proc addReaction*(self: var MessageReactionItem, didIReactWithThisEmoji: bool, userPublicKey: string,
userDisplayName: string, reactionId: string) =
self.didIReactWithThisEmoji = didIReactWithThisEmoji
self.reactions.add(ReactionDetails(publicKey: userPublicKey, displayName: userDisplayName, reactionId: reactionId))
proc removeReaction*(self: var MessageReactionItem, reactionId: string) =
var index = -1
for i in 0..<self.reactions.len:
if (self.reactions[i].reactionId == reactionId):
index = i
break
if(index == -1):
return
self.reactions.delete(index)

View File

@ -0,0 +1,146 @@
import NimQml, Tables, json, strutils, strformat
import message_reaction_item
type
ModelRole {.pure.} = enum
EmojiId = UserRole + 1
DidIReactWithThisEmoji
NumberOfReactions
JsonArrayOfUsersReactedWithThisEmoji
QtObject:
type
MessageReactionModel* = ref object of QAbstractListModel
items: seq[MessageReactionItem]
proc delete(self: MessageReactionModel) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: MessageReactionModel) =
self.QAbstractListModel.setup
proc newMessageReactionModel*(): MessageReactionModel =
new(result, delete)
result.setup
proc `$`*(self: MessageReactionModel): string =
for i in 0 ..< self.items.len:
result &= fmt"""
[{i}]:({$self.items[i]})
"""
proc countChanged(self: MessageReactionModel) {.signal.}
proc getCount(self: MessageReactionModel): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: MessageReactionModel, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: MessageReactionModel): Table[int, string] =
{
ModelRole.EmojiId.int:"emojiId",
ModelRole.DidIReactWithThisEmoji.int:"didIReactWithThisEmoji",
ModelRole.NumberOfReactions.int:"numberOfReactions",
ModelRole.JsonArrayOfUsersReactedWithThisEmoji.int: "jsonArrayOfUsersReactedWithThisEmoji"
}.toTable
method data(self: MessageReactionModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.EmojiId:
result = newQVariant(item.emojiId.int)
of ModelRole.DidIReactWithThisEmoji:
result = newQVariant(item.didIReactWithThisEmoji)
of ModelRole.NumberOfReactions:
result = newQVariant(item.numberOfReactions)
of ModelRole.JsonArrayOfUsersReactedWithThisEmoji:
# Would be good if we could return QVariant of array (seq) here, but it's not supported in our NimQml,
# because of that we're returning json array as a string.
result = newQVariant($item.jsonArrayOfUsersReactedWithThisEmoji)
proc reactionItemWithEmojiIdExists(self: MessageReactionModel, emojiId: EmojiId): bool =
for it in self.items:
if(it.emojiId == emojiId):
return true
return false
proc getIndexOfTheItemWithEmojiId(self: MessageReactionModel, emojiId: EmojiId): int =
for i in 0..<self.items.len:
if(self.items[i].emojiId == emojiId):
return i
return -1
proc findPositionForTheItemWithEmojiId(self: MessageReactionModel, emojiId: EmojiId): int =
if(self.items.len == 0):
return 0
for i in 0..<self.items.len:
if(emojiId < self.items[i].emojiId):
return i
return self.items.len
proc shouldAddReaction*(self: MessageReactionModel, emojiId: EmojiId, userPublicKey: string): bool =
let ind = self.getIndexOfTheItemWithEmojiId(emojiId)
if(ind == -1):
return true
return self.items[ind].shouldAddReaction(userPublicKey)
proc getReactionId*(self: MessageReactionModel, emojiId: EmojiId, userPublicKey: string): string =
let ind = self.getIndexOfTheItemWithEmojiId(emojiId)
if(ind == -1):
return ""
return self.items[ind].getReactionId(userPublicKey)
proc addReaction*(self: MessageReactionModel, emojiId: EmojiId, didIReactWithThisEmoji: bool, userPublicKey: string,
userDisplayName: string, reactionId: string) =
if(self.reactionItemWithEmojiIdExists(emojiId)):
let ind = self.getIndexOfTheItemWithEmojiId(emojiId)
if(ind == -1):
return
self.items[ind].addReaction(didIReactWithThisEmoji, userPublicKey, userDisplayName, reactionId)
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[]) # all roles
else:
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
var item = initMessageReactionItem(emojiId)
item.addReaction(didIReactWithThisEmoji, userPublicKey, userDisplayName, reactionId)
let position = self.findPositionForTheItemWithEmojiId(emojiId) # Model should maintain items based on the emoji id.
self.beginInsertRows(parentModelIndex, position, position)
self.items.insert(item, position)
self.endInsertRows()
self.countChanged()
proc removeReaction*(self: MessageReactionModel, emojiId: EmojiId, reactionId: string) =
let ind = self.getIndexOfTheItemWithEmojiId(emojiId)
if(ind == -1):
return
self.items[ind].removeReaction(reactionId)
if(self.items[ind].numberOfReactions() == 0):
# remove item if there are no reactions for this emoji id
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginRemoveRows(parentModelIndex, ind, ind)
self.items.delete(ind)
self.endRemoveRows()
self.countChanged()

View File

@ -26,6 +26,8 @@ const SIGNAL_MESSAGE_PINNED* = "new-messagePinned"
const SIGNAL_MESSAGE_UNPINNED* = "new-messageUnpinned"
const SIGNAL_SEARCH_MESSAGES_LOADED* = "new-searchMessagesLoaded"
const SIGNAL_MESSAGES_MARKED_AS_READ* = "new-messagesMarkedAsRead"
const SIGNAL_MESSAGE_REACTION_ADDED* = "new-messageReactionAdded"
const SIGNAL_MESSAGE_REACTION_REMOVED* = "new-messageReactionRemoved"
type
SearchMessagesLoadedArgs* = ref object of Args
@ -46,6 +48,12 @@ type
allMessagesMarked*: bool
messagesIds*: seq[string]
MessageAddRemoveReactionArgs* = ref object of Args
chatId*: string
messageId*: string
emojiId*: int
reactionId*: string
QtObject:
type Service* = ref object of QObject
events: EventEmitter
@ -157,14 +165,13 @@ QtObject:
self.asyncLoadMoreMessagesForChat(chatId)
proc addReaction*(self: Service, chatId: string, messageId: string, emojiId: int):
tuple[result: string, error: string] =
proc addReaction*(self: Service, chatId: string, messageId: string, emojiId: int) =
try:
let response = status_go.addReaction(chatId, messageId, emojiId)
result.error = "response doesn't contain \"error\""
if(response.result.contains("error")):
result.error = response.result["error"].getStr
let errMsg = response.result["error"].getStr
error "error: ", methodName="addReaction", errDesription = errMsg
return
var reactionsArr: JsonNode
@ -172,24 +179,31 @@ QtObject:
if(response.result.getProp("emojiReactions", reactionsArr)):
reactions = map(reactionsArr.getElems(), proc(x: JsonNode): ReactionDto = x.toReactionDto())
var reactionId: string
if(reactions.len > 0):
result.result = reactions[0].id
reactionId = reactions[0].id
let data = MessageAddRemoveReactionArgs(chatId: chatId, messageId: messageId, emojiId: emojiId,
reactionId: reactionId)
self.events.emit(SIGNAL_MESSAGE_REACTION_ADDED, data)
except Exception as e:
result.error = e.msg
error "error: ", methodName="addReaction", errName = e.name, errDesription = e.msg
proc removeReaction*(self: Service, reactionId: string): tuple[result: string, error: string] =
proc removeReaction*(self: Service, reactionId: string, chatId: string, messageId: string, emojiId: int) =
try:
let response = status_go.removeReaction(reactionId)
result.error = "response doesn't contain \"error\""
if(response.result.contains("error")):
result.error = response.result["error"].getStr
let errMsg = response.result["error"].getStr
error "error: ", methodName="removeReaction", errDesription = errMsg
return
let data = MessageAddRemoveReactionArgs(chatId: chatId, messageId: messageId, emojiId: emojiId,
reactionId: reactionId)
self.events.emit(SIGNAL_MESSAGE_REACTION_REMOVED, data)
except Exception as e:
result.error = e.msg
error "error: ", methodName="removeReaction", errName = e.name, errDesription = e.msg
proc pinUnpinMessage*(self: Service, chatId: string, messageId: string, pin: bool) =

View File

@ -125,6 +125,7 @@ ModalPopup {
messageOutgoingStatus: model.outgoingStatus
messageContentType: model.contentType
pinnedMessage: model.pinned
reactionsModel: model.reactions
// This is possible since we have all data loaded before we load qml.
// When we fetch messages to fulfill a gap we have to set them at once.
@ -163,6 +164,7 @@ ModalPopup {
}
MessageContextMenuView {
id: msgContextMenu
reactionModel: root.rootStore.emojiReactionsModel
pinnedPopup: true
pinnedMessage: true
onShouldCloseParentPopup: {
@ -176,6 +178,10 @@ ModalPopup {
onUnpinMessage: {
popup.messageStore.unpinMessage(messageId)
}
onToggleReaction: {
popup.messageStore.toggleReaction(messageId, emojiId)
}
}
}

View File

@ -77,4 +77,57 @@ QtObject {
return messageModule.unpinMessage(messageId)
}
function toggleReaction(messageId, emojiId) {
if(!messageModule)
return
return messageModule.toggleReaction(messageId, emojiId)
}
function lastTwoItems(nodes) {
//% " and "
return nodes.join(qsTrId("-and-"));
}
function showReactionAuthors(jsonArrayOfUsersReactedWithThisEmoji, emojiId) {
let listOfUsers = JSON.parse(jsonArrayOfUsersReactedWithThisEmoji)
if (listOfUsers.error) {
console.error("error parsing users who reacted to a message, error: ", obj.error)
return
}
let tooltip
if (listOfUsers.length === 1) {
tooltip = listOfUsers[0]
} else if (listOfUsers.length === 2) {
tooltip = lastTwoItems(listOfUsers);
} else {
var leftNode = [];
var rightNode = [];
const maxReactions = 12
let maximum = Math.min(maxReactions, listOfUsers.length)
if (listOfUsers.length > maxReactions) {
leftNode = listOfUsers.slice(0, maxReactions);
rightNode = listOfUsers.slice(maxReactions, listOfUsers.length);
return (rightNode.length === 1) ?
lastTwoItems([leftNode.join(", "), rightNode[0]]) :
//% "%1 more"
lastTwoItems([leftNode.join(", "), qsTrId("-1-more").arg(rightNode.length)]);
}
leftNode = listOfUsers.slice(0, maximum - 1);
rightNode = listOfUsers.slice(maximum - 1, listOfUsers.length);
tooltip = lastTwoItems([leftNode.join(", "), rightNode[0]])
}
//% " reacted with "
tooltip += qsTrId("-reacted-with-");
let emojiHtml = Emoji.getEmojiFromId(emojiId);
if (emojiHtml) {
tooltip += emojiHtml;
}
return tooltip
}
}

View File

@ -67,46 +67,6 @@ QtObject {
// chatsModelInst.messageView.deleteMessage(messageId);
}
function lastTwoItems(nodes) {
//% " and "
return nodes.join(qsTrId("-and-"));
}
function showReactionAuthors(fromAccounts, emojiId) {
let tooltip
if (fromAccounts.length === 1) {
tooltip = fromAccounts[0]
} else if (fromAccounts.length === 2) {
tooltip = lastTwoItems(fromAccounts);
} else {
var leftNode = [];
var rightNode = [];
const maxReactions = 12
let maximum = Math.min(maxReactions, fromAccounts.length)
if (fromAccounts.length > maxReactions) {
leftNode = fromAccounts.slice(0, maxReactions);
rightNode = fromAccounts.slice(maxReactions, fromAccounts.length);
return (rightNode.length === 1) ?
lastTwoItems([leftNode.join(", "), rightNode[0]]) :
//% "%1 more"
lastTwoItems([leftNode.join(", "), qsTrId("-1-more").arg(rightNode.length)]);
}
leftNode = fromAccounts.slice(0, maximum - 1);
rightNode = fromAccounts.slice(maximum - 1, fromAccounts.length);
tooltip = lastTwoItems([leftNode.join(", "), rightNode[0]])
}
//% " reacted with "
tooltip += qsTrId("-reacted-with-");
let emojiHtml = Emoji.getEmojiFromId(emojiId);
if (emojiHtml) {
tooltip += emojiHtml;
}
return tooltip
}
function getCommunity(communityId) {
// Not Refactored Yet
// try {

View File

@ -207,6 +207,10 @@ ColumnLayout {
messageToPin: messageId
})
}
onToggleReaction: {
messageStore.toggleReaction(messageId, emojiId)
}
}
StatusImageModal {

View File

@ -312,6 +312,7 @@ Item {
messageOutgoingStatus: model.outgoingStatus
messageContentType: model.contentType
pinnedMessage: model.pinned
reactionsModel: model.reactions
// This is possible since we have all data loaded before we load qml.
// When we fetch messages to fulfill a gap we have to set them at once.

View File

@ -19,6 +19,7 @@ Item {
signal toggleReaction(int emojiID)
signal setMessageActive(string messageId, bool active)
property var store
property bool isCurrentUser
property var emojiReactionsModel
property bool isMessageActive
@ -38,14 +39,14 @@ Item {
width: emojiImage.width + emojiCount.width + (root.imageMargin * 2) + + 8
height: 20
radius: 10
color: modelData.currentUserReacted ?
color: model.didIReactWithThisEmoji ?
(isHovered ? Style.current.emojiReactionActiveBackgroundHovered : Style.current.secondaryBackground) :
(isHovered ? Style.current.emojiReactionBackgroundHovered : Style.current.emojiReactionBackground)
StatusQ.StatusToolTip {
visible: mouseArea.containsMouse
maxWidth: 400
text: showReactionAuthors(modelData.fromAccounts, modelData.emojiId)
text: root.store.showReactionAuthors(model.jsonArrayOfUsersReactedWithThisEmoji, model.emojiId)
}
// Rounded corner to cover one corner
@ -64,7 +65,7 @@ Item {
// This is a workaround to get a "border" around the rectangle including the weird rectangle
Loader {
active: modelData.currentUserReacted
active: model.didIReactWithThisEmoji
anchors.top: parent.top
anchors.topMargin: -1
anchors.left: parent.left
@ -100,7 +101,7 @@ Item {
height: 15
fillMode: Image.PreserveAspectFit
source: {
switch (modelData.emojiId) {
switch (model.emojiId) {
case 1: return Style.svg("emojiReactions/heart")
case 2: return Style.svg("emojiReactions/thumbsUp")
case 3: return Style.svg("emojiReactions/thumbsDown")
@ -117,12 +118,12 @@ Item {
StyledText {
id: emojiCount
text: modelData.count
text: model.numberOfReactions
anchors.verticalCenter: parent.verticalCenter
anchors.left: emojiImage.right
anchors.leftMargin: root.imageMargin
font.pixelSize: 12
color: modelData.currentUserReacted ? Style.current.textColorTertiary : Style.current.textColor
color: model.didIReactWithThisEmoji ? Style.current.textColorTertiary : Style.current.textColor
}
MouseArea {
@ -139,7 +140,7 @@ Item {
emojiContainer.isHovered = false
}
onClicked: {
toggleReaction(modelData.emojiId)
toggleReaction(model.emojiId)
}
}
}

View File

@ -648,7 +648,7 @@ Item {
Loader {
id: emojiReactionLoader
active: emojiReactionsModel.length
active: reactionsModel.count > 0
anchors.bottom: messageContainer.bottom
anchors.bottomMargin: Style.current.halfPadding
anchors.left: messageContainer.left
@ -657,7 +657,8 @@ Item {
sourceComponent: Component {
EmojiReactionsPanel {
id: emojiRect
emojiReactionsModel: emojiReactionsModel
store: messageStore
emojiReactionsModel: reactionsModel
onHoverChanged: {
setHovered(messageId, hovered)
}
@ -670,8 +671,8 @@ Item {
root.messageContextMenu.setXPosition = function() { return (root.messageContextMenu.parent.x + 4)}
root.messageContextMenu.setYPosition = function() { return (-root.messageContextMenu.height - 4)}
}
// Not Refactored Yet
// onToggleReaction: rootStore.chatsModelInst.toggleReaction(messageId, emojiID)
onToggleReaction: messageStore.toggleReaction(messageId, emojiID)
onSetMessageActive: {
setMessageActive(messageId, active);;

View File

@ -57,6 +57,7 @@ StatusPopupMenu {
signal shouldCloseParentPopup()
signal createOneToOneChat(string chatId, string ensName)
signal showReplyArea()
signal toggleReaction(string messageId, int emojiId)
onHeightChanged: {
root.y = setYPosition()
@ -85,8 +86,7 @@ StatusPopupMenu {
emojiId: model.emojiId
reactedByUser: !!root.emojiReactionsReactedByUser[model.emojiId]
onCloseModal: {
// Not Refactored Yet
// chatsModel.toggleReaction(SelectedMessage.messageId, emojiId)
root.toggleReaction(root.messageId, emojiId)
root.close()
}
}

View File

@ -33,6 +33,7 @@ Column {
property string messageOutgoingStatus: ""
property int messageContentType: 1
property bool pinnedMessage: false
property var reactionsModel
property int prevMessageIndex: -1
property var prevMessageAsJsonObj
@ -129,40 +130,6 @@ Column {
property var imageClick: function () {}
property var scrollToBottom: function () {}
property var emojiReactionsModel: {
// Not Refactored Yet
return []
// if (!emojiReactions) {
// return []
// }
// try {
// // group by id
// var allReactions = Object.values(JSON.parse(emojiReactions))
// var byEmoji = {}
// allReactions.forEach(function (reaction) {
// if (!byEmoji[reaction.emojiId]) {
// byEmoji[reaction.emojiId] = {
// emojiId: reaction.emojiId,
// fromAccounts: [],
// count: 0,
// currentUserReacted: false
// }
// }
// byEmoji[reaction.emojiId].count++;
// byEmoji[reaction.emojiId].fromAccounts.push(root.chatsModel.userNameOrAlias(reaction.from));
// if (!byEmoji[reaction.emojiId].currentUserReacted && reaction.from === userProfile.pubKey) {
// byEmoji[reaction.emojiId].currentUserReacted = true
// }
// })
// return Object.values(byEmoji)
// } catch (e) {
// console.error('Error parsing emoji reactions', e)
// return []
// }
}
property var clickMessage: function(isProfileClick,
isSticker = false,
isImage = false,
@ -378,8 +345,7 @@ Column {
// root.parent.clickMessage(isProfileClick, isSticker, isImage, image, emojiOnly, hideEmojiPicker, isReply);
}
onSetMessageActive: {
// Not Refactored Yet - Should do it via messageStore
// root.messageStore.setMessageActive(messageId, active);;
root.setMessageActive(messageId, active);
}
}
}

View File

@ -180,8 +180,10 @@ MouseArea {
Component {
id: emojiReactionsComponent
EmojiReactionsPanel {
// isMessageActive: root.isMessageActive
// emojiReactionsModel: root.emojiReactionsModel
store: messageStore
emojiReactionsModel: reactionsModel
isMessageActive: isMessageActive
isCurrentUser: isCurrentUser
onAddEmojiClicked: {
root.addEmoji(false, false, false, null, true, false);
// Set parent, X & Y positions for the messageContextMenu
@ -189,12 +191,12 @@ MouseArea {
messageContextMenu.setXPosition = function() { return (messageContextMenu.parent.x + 4)}
messageContextMenu.setYPosition = function() { return (-messageContextMenu.height - 4)}
}
// Not Refactored Yet
// onToggleReaction: chatsModel.toggleReaction(messageId, emojiID)
// onSetMessageActive: {
// root.setMessageActive(messageId, active);;
// }
onToggleReaction: messageStore.toggleReaction(messageId, emojiID)
onSetMessageActive: {
root.setMessageActive(messageId, active);
}
}
}