mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-22 11:38:57 +00:00
feature(@desktop/chat): implement search on sqlcipher (status-go side)
Current code adapted to handle future changes on message search (like searching message in multiple channels). Memory leak which was happening in qml assigning (copying) MessageItem to qml variable messageItem (where that qml variable messageItem was never deleted) is fixed. Fixes: #2912
This commit is contained in:
parent
5fc85af04b
commit
1573d7b928
@ -58,18 +58,13 @@ QtObject:
|
||||
timedoutMessages: HashSet[string]
|
||||
userList: UserListView
|
||||
|
||||
proc delete(self: ChatMessageList) =
|
||||
proc delete*(self: ChatMessageList) =
|
||||
self.messages = @[]
|
||||
self.isEdited = initTable[string, bool]()
|
||||
self.messageIndex = initTable[string, int]()
|
||||
self.timedoutMessages = initHashSet[string]()
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: ChatMessageList) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc countChanged*(self: ChatMessageList) {.signal.}
|
||||
|
||||
proc fetchMoreMessagesButton(self: ChatMessageList): Message =
|
||||
result = Message()
|
||||
result.contentType = ContentType.FetchMoreMessagesButton;
|
||||
@ -83,21 +78,34 @@ QtObject:
|
||||
self.messages.add(self.fetchMoreMessagesButton())
|
||||
self.messages.add(self.chatIdentifier(self.id))
|
||||
|
||||
proc newChatMessageList*(chatId: string, status: Status, addFakeMessages: bool = true): ChatMessageList =
|
||||
new(result, delete)
|
||||
result.messages = @[]
|
||||
result.id = chatId
|
||||
proc setup*(self: ChatMessageList, chatId: string, status: Status, addFakeMessages: bool) =
|
||||
self.messages = @[]
|
||||
self.id = chatId
|
||||
|
||||
if addFakeMessages:
|
||||
result.addFakeMessages()
|
||||
self.addFakeMessages()
|
||||
|
||||
self.messageIndex = initTable[string, int]()
|
||||
self.timedoutMessages = initHashSet[string]()
|
||||
self.isEdited = initTable[string, bool]()
|
||||
self.userList = newUserListView(status)
|
||||
self.status = status
|
||||
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newChatMessageList*(chatId: string, status: Status, addFakeMessages: bool): ChatMessageList =
|
||||
new(result, delete)
|
||||
result.setup(chatId, status, addFakeMessages)
|
||||
|
||||
proc countChanged*(self: ChatMessageList) {.signal.}
|
||||
|
||||
proc count*(self: ChatMessageList): int {.slot.} =
|
||||
self.messages.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = count
|
||||
notify = countChanged
|
||||
|
||||
result.messageIndex = initTable[string, int]()
|
||||
result.timedoutMessages = initHashSet[string]()
|
||||
result.isEdited = initTable[string, bool]()
|
||||
result.userList = newUserListView(status)
|
||||
result.status = status
|
||||
result.setup
|
||||
|
||||
proc hasMessage*(self: ChatMessageList, messageId: string): bool =
|
||||
return self.messageIndex.hasKey(messageId)
|
||||
|
||||
@ -157,13 +165,6 @@ QtObject:
|
||||
method rowCount(self: ChatMessageList, index: QModelIndex = nil): int =
|
||||
return self.messages.len
|
||||
|
||||
proc count*(self: ChatMessageList): int {.slot.} =
|
||||
self.messages.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = count
|
||||
notify = countChanged
|
||||
|
||||
proc getReactions*(self:ChatMessageList, messageId: string):string =
|
||||
if not self.messageReactions.hasKey(messageId): return ""
|
||||
result = self.messageReactions[messageId]
|
||||
@ -303,18 +304,28 @@ QtObject:
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.userList.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
self.countChanged()
|
||||
|
||||
proc add*(self: ChatMessageList, messages: seq[Message]) =
|
||||
self.beginInsertRows(newQModelIndex(), self.messages.len, self.messages.len)
|
||||
self.beginInsertRows(newQModelIndex(), self.messages.len, self.messages.len + messages.len - 1)
|
||||
for message in messages:
|
||||
if self.messageIndex.hasKey(message.id): continue
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.userList.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
self.countChanged()
|
||||
|
||||
proc remove*(self: ChatMessageList, messageId: string) =
|
||||
if not self.messageIndex.hasKey(messageId): return
|
||||
|
||||
let index = self.getMessageIndex(messageId)
|
||||
self.beginRemoveRows(newQModelIndex(), index, index)
|
||||
self.messages.delete(index)
|
||||
self.messageIndex.del(messageId)
|
||||
self.endRemoveRows()
|
||||
self.countChanged()
|
||||
|
||||
proc getMessageById*(self: ChatMessageList, messageId: string): Message =
|
||||
if (not self.messageIndex.hasKey(messageId)): return
|
||||
@ -323,9 +334,11 @@ QtObject:
|
||||
proc clear*(self: ChatMessageList, addFakeMessages: bool = true) =
|
||||
self.beginResetModel()
|
||||
self.messages = @[]
|
||||
self.messageIndex.clear
|
||||
if (addFakeMessages):
|
||||
self.addFakeMessages()
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc setMessageReactions*(self: ChatMessageList, messageId: string, newReactions: string)=
|
||||
self.messageReactions[messageId] = newReactions
|
||||
@ -390,13 +403,4 @@ QtObject:
|
||||
newQVariant(self.userList)
|
||||
|
||||
QtProperty[QVariant] userList:
|
||||
read = getUserList
|
||||
|
||||
proc messageSearch*(self: ChatMessageList, searchTerm: string): string {.slot.} =
|
||||
let lowercaseTerm = searchTerm.toLowerAscii
|
||||
var messageIds: seq[string] = @[]
|
||||
for message in self.messages:
|
||||
if message.text.toLowerAscii.contains(lowercaseTerm):
|
||||
# TODO try to send a Variant
|
||||
messageIds.add(message.id)
|
||||
return messageIds.toJson
|
||||
read = getUserList
|
42
src/app/chat/views/message_list_proxy.nim
Normal file
42
src/app/chat/views/message_list_proxy.nim
Normal file
@ -0,0 +1,42 @@
|
||||
import NimQml, strutils
|
||||
|
||||
import message_list
|
||||
import ../../../status/[status]
|
||||
import ../../../status/chat/[message]
|
||||
|
||||
QtObject:
|
||||
type
|
||||
MessageListProxyModel* = ref object of ChatMessageList
|
||||
sourceMessages: seq[Message]
|
||||
|
||||
proc delete(self: MessageListProxyModel) =
|
||||
self.ChatMessageList.delete
|
||||
|
||||
proc setup(self: MessageListProxyModel, status: Status) =
|
||||
self.ChatMessageList.setup("", status, false)
|
||||
|
||||
proc newMessageListProxyModel*(status: Status): MessageListProxyModel =
|
||||
new(result, delete)
|
||||
result.setup(status)
|
||||
|
||||
proc setSourceMessages*(self: MessageListProxyModel, messages: seq[Message]) =
|
||||
self.sourceMessages = messages
|
||||
|
||||
proc setFilter*(self: MessageListProxyModel, filter: string, caseSensitive: bool) =
|
||||
self.clear(false)
|
||||
|
||||
if (filter.len == 0):
|
||||
return
|
||||
|
||||
let pattern = if(caseSensitive): filter else: filter.toLowerAscii
|
||||
|
||||
var matchedMessages: seq[Message] = @[]
|
||||
for message in self.sourceMessages:
|
||||
if (caseSensitive and message.text.contains(pattern) or
|
||||
not caseSensitive and message.text.toLowerAscii.contains(pattern)):
|
||||
matchedMessages.add(message)
|
||||
|
||||
if (matchedMessages.len == 0):
|
||||
return
|
||||
|
||||
self.add(matchedMessages)
|
@ -11,7 +11,7 @@ import ../../../status/profile/profile
|
||||
import ../../../status/tasks/[qt, task_runner_impl]
|
||||
import ../../../status/tasks/marathon/mailserver/worker
|
||||
|
||||
import communities, chat_item, channels_list, communities, community_list, message_list, channel, message_item
|
||||
import communities, chat_item, channels_list, communities, community_list, message_list, channel, message_item, message_list_proxy
|
||||
|
||||
# TODO: remove me
|
||||
import ../../../status/libstatus/chat as libstatus_chat
|
||||
@ -68,10 +68,10 @@ QtObject:
|
||||
type MessageView* = ref object of QAbstractListModel
|
||||
status: Status
|
||||
messageList*: OrderedTable[string, ChatMessageList]
|
||||
searchResultMessageModel*: MessageListProxyModel
|
||||
pinnedMessagesList*: OrderedTable[string, ChatMessageList]
|
||||
channelView*: ChannelView
|
||||
communities*: CommunitiesView
|
||||
observedMessageItem*: MessageItem
|
||||
pubKey*: string
|
||||
loadingMessages*: bool
|
||||
unreadMessageCnt: int
|
||||
@ -88,7 +88,7 @@ QtObject:
|
||||
self.pinnedMessagesList = initOrderedTable[string, ChatMessageList]()
|
||||
self.channelOpenTime = initTable[string, int64]()
|
||||
self.QAbstractListModel.delete
|
||||
self.observedMessageItem.delete
|
||||
self.searchResultMessageModel.delete
|
||||
|
||||
proc newMessageView*(status: Status, channelView: ChannelView, communitiesView: CommunitiesView): MessageView =
|
||||
new(result, delete)
|
||||
@ -100,7 +100,7 @@ QtObject:
|
||||
result.messageList[status_utils.getTimelineChatId()] = newChatMessageList(status_utils.getTimelineChatId(), result.status, false)
|
||||
result.loadingMessages = false
|
||||
result.unreadMessageCnt = 0
|
||||
result.observedMessageItem = newMessageItem(status)
|
||||
result.searchResultMessageModel = newMessageListProxyModel(status)
|
||||
result.unreadDirectMessagesAndMentionsCount = 0
|
||||
result.setup
|
||||
|
||||
@ -296,23 +296,6 @@ QtObject:
|
||||
read = getMessageList
|
||||
notify = activeChannelChanged
|
||||
|
||||
proc observedMessageItemChanged*(self: MessageView) {.signal.}
|
||||
|
||||
proc setObservedMessageItem*(self: MessageView, chatId: string, messageId: string) {.slot.} =
|
||||
if(messageId == ""): return
|
||||
if (not self.messageList.hasKey(chatId)): return
|
||||
let message = self.messageList[chatId].getMessageById(messageId)
|
||||
if (message.id == ""):
|
||||
return
|
||||
self.observedMessageItem.setMessageItem(message)
|
||||
self.observedMessageItemChanged()
|
||||
|
||||
proc getObservedMessageItem*(self: MessageView): QVariant {.slot.} = newQVariant(self.observedMessageItem)
|
||||
QtProperty[QVariant] observedMessageItem:
|
||||
read = getObservedMessageItem
|
||||
write = setObservedMessageItem
|
||||
notify = observedMessageItemChanged
|
||||
|
||||
proc getPinnedMessagesList(self: MessageView): QVariant {.slot.} =
|
||||
self.upsertChannel(self.channelView.activeChannel.id)
|
||||
return newQVariant(self.pinnedMessagesList[self.channelView.activeChannel.id])
|
||||
@ -512,4 +495,18 @@ QtObject:
|
||||
for message in messageList.messages:
|
||||
if (message.id == messageId):
|
||||
return chatId
|
||||
|
||||
|
||||
proc getSearchResultMessageModel*(self: MessageView): QVariant {.slot.} =
|
||||
newQVariant(self.searchResultMessageModel)
|
||||
|
||||
# we just need to expose model to qml, there is no need for exposing notifying signal
|
||||
QtProperty[QVariant] searchResultMessageModel:
|
||||
read = getSearchResultMessageModel
|
||||
|
||||
proc searchMessages*(self: MessageView, searchTerm: string) {.slot.} =
|
||||
# channelId is used here only to support message search in currently selected channel
|
||||
# later when we decide to apply message search over multiple channels MessageListProxyModel
|
||||
# will be updated to support setting list of sourcer messages.
|
||||
let channelId = self.channelView.activeChannel.id
|
||||
self.searchResultMessageModel.setSourceMessages(self.messageList[channelId].messages)
|
||||
self.searchResultMessageModel.setFilter(searchTerm, false)
|
||||
|
@ -6,7 +6,6 @@ import "../../../../shared/status"
|
||||
import "../ChatColumn"
|
||||
|
||||
Popup {
|
||||
property var searchResults
|
||||
property string chatId: chatsModel.channelView.activeChannel.id
|
||||
|
||||
id: popup
|
||||
@ -24,7 +23,8 @@ Popup {
|
||||
const noResultHeight = 122
|
||||
let minHeight = 560
|
||||
const maxHeight = parent.height - 200
|
||||
if (!searchResults || !searchResults.length || !searchResultContent.visible) {
|
||||
|
||||
if (!searchResultContent.visible) {
|
||||
return noResultHeight
|
||||
}
|
||||
|
||||
@ -32,10 +32,10 @@ Popup {
|
||||
return maxHeight
|
||||
}
|
||||
|
||||
if (messageColumn.height < minHeight - noResultHeight) {
|
||||
if (listView.height < minHeight - noResultHeight) {
|
||||
return minHeight
|
||||
}
|
||||
if (messageColumn.height > maxHeight - noResultHeight) {
|
||||
if (listView.height > maxHeight - noResultHeight) {
|
||||
return maxHeight
|
||||
}
|
||||
}
|
||||
@ -75,19 +75,7 @@ Popup {
|
||||
}
|
||||
|
||||
property var searchMessages: Backpressure.debounce(searchInput, 400, function (value) {
|
||||
if (value === "") {
|
||||
searchResultContent.visible = false
|
||||
return
|
||||
}
|
||||
|
||||
// TODO add loading?
|
||||
const messageIdsStr = chatsModel.messageView.messageList.messageSearch(value)
|
||||
try {
|
||||
searchResultContent.visible = true
|
||||
searchResults = JSON.parse(messageIdsStr)
|
||||
} catch (e) {
|
||||
console.error ("Error parsing search result", e)
|
||||
}
|
||||
chatsModel.messageView.searchMessages(value)
|
||||
})
|
||||
|
||||
StyledTextField {
|
||||
@ -105,7 +93,9 @@ Popup {
|
||||
background: Rectangle {
|
||||
color: Style.current.transparent
|
||||
}
|
||||
onTextChanged: Qt.callLater(searchHeader.searchMessages, searchInput.text)
|
||||
onTextChanged: {
|
||||
searchHeader.searchMessages(searchInput.text)
|
||||
}
|
||||
}
|
||||
|
||||
Separator {
|
||||
@ -150,7 +140,7 @@ Popup {
|
||||
|
||||
Item {
|
||||
id: searchResultContent
|
||||
visible: !!popup.searchResults && popup.searchResults.length > 0
|
||||
visible: chatsModel.messageView.searchResultMessageModel.count > 0
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: channelBadge.bottom
|
||||
@ -183,66 +173,53 @@ Popup {
|
||||
width: parent.width
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: messageColumn
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
ListView{
|
||||
id: listView
|
||||
model: chatsModel.messageView.searchResultMessageModel
|
||||
|
||||
Repeater {
|
||||
model: popup.searchResults
|
||||
delegate: Message {
|
||||
|
||||
delegate: Message {
|
||||
property var messageItem: ({})
|
||||
|
||||
function getMessage() {
|
||||
chatsModel.messageView.setObservedMessageItem(popup.chatId, modelData)
|
||||
return chatsModel.messageView.observedMessageItem
|
||||
anchors.right: undefined
|
||||
messageId: model.messageId
|
||||
fromAuthor: model.fromAuthor
|
||||
chatId: model.chatId
|
||||
userName: model.userName
|
||||
alias: model.alias
|
||||
localName: model.localName
|
||||
message: model.message
|
||||
plainText: model.plainText
|
||||
identicon: model.identicon
|
||||
isCurrentUser: model.isCurrentUser
|
||||
timestamp: model.timestamp
|
||||
sticker: model.sticker
|
||||
contentType: model.contentType
|
||||
outgoingStatus: model.outgoingStatus
|
||||
responseTo: model.responseTo
|
||||
imageClick: imagePopup.openPopup.bind(imagePopup)
|
||||
linkUrls: model.linkUrls
|
||||
communityId: model.communityId
|
||||
hasMention: model.hasMention
|
||||
stickerPackId: model.stickerPackId
|
||||
pinnedBy: model.pinnedBy
|
||||
pinnedMessage: model.isPinned
|
||||
activityCenterMessage: true
|
||||
clickMessage: function (isProfileClick) {
|
||||
if (isProfileClick) {
|
||||
const pk = model.fromAuthor
|
||||
const userProfileImage = appMain.getProfileImage(pk)
|
||||
return openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
messageItem = getMessage()
|
||||
}
|
||||
popup.close()
|
||||
|
||||
anchors.right: undefined
|
||||
messageId: messageItem.messageId
|
||||
fromAuthor: messageItem.fromAuthor
|
||||
chatId: messageItem.chatId
|
||||
userName: messageItem.userName
|
||||
alias: messageItem.alias
|
||||
localName: messageItem.localName
|
||||
message: messageItem.message
|
||||
plainText: messageItem.plainText
|
||||
identicon: messageItem.identicon
|
||||
isCurrentUser: messageItem.isCurrentUser
|
||||
timestamp: messageItem.timestamp
|
||||
sticker: messageItem.sticker
|
||||
contentType: messageItem.contentType
|
||||
outgoingStatus: messageItem.outgoingStatus
|
||||
responseTo: messageItem.responseTo
|
||||
imageClick: imagePopup.openPopup.bind(imagePopup)
|
||||
linkUrls: messageItem.linkUrls
|
||||
communityId: messageItem.communityId
|
||||
hasMention: messageItem.hasMention
|
||||
stickerPackId: messageItem.stickerPackId
|
||||
pinnedBy: messageItem.pinnedBy
|
||||
pinnedMessage: messageItem.isPinned
|
||||
activityCenterMessage: true
|
||||
clickMessage: function (isProfileClick) {
|
||||
if (isProfileClick) {
|
||||
const pk = messageItem.fromAuthor
|
||||
const userProfileImage = appMain.getProfileImage(pk)
|
||||
return openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
|
||||
}
|
||||
|
||||
popup.close()
|
||||
|
||||
positionAtMessage(messageItem.messageId)
|
||||
}
|
||||
|
||||
prevMessageIndex: -1
|
||||
prevMsgTimestamp: ""
|
||||
positionAtMessage(model.messageId)
|
||||
}
|
||||
|
||||
prevMessageIndex: -1
|
||||
prevMsgTimestamp: ""
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user