From e2628338de3a1381e1f6336297133680782722b5 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Fri, 6 Aug 2021 17:31:42 +0200 Subject: [PATCH] feature(@desktop/chat): implement search results for communities, channels Added a part for fetching messages from multiple chats/channels/communities. Fixes: #2934 --- src/app/chat/views/channels_list.nim | 25 +- src/app/chat/views/community_list.nim | 13 +- .../chat/views/message_search/constants.nim | 6 + .../message_search/location_menu_item.nim | 82 ++++++ .../message_search/location_menu_model.nim | 113 ++++++++ .../message_search/location_menu_sub_item.nim | 59 ++++ .../location_menu_sub_model.nim | 117 ++++++++ .../chat/views/message_search/result_item.nim | 80 ++++++ .../views/message_search/result_model.nim | 112 ++++++++ .../views/message_search/view_controller.nim | 265 ++++++++++++++++++ src/status/chat.nim | 60 +++- src/status/chat/async_tasks.nim | 43 ++- src/status/chat/chat.nim | 1 - src/status/libstatus/chat.nim | 10 +- ui/StatusQ | 2 +- ui/app/AppLayouts/Chat/ChatColumn.qml | 106 ++++++- .../Chat/components/SearchPopup.qml | 227 --------------- ui/imports/Backpressure/Backpressure.qml | 4 +- ui/nim-status-client.pro | 1 - vendor/DOtherSide | 2 +- vendor/nim-status-go | 2 +- vendor/nimqml | 2 +- vendor/status-go | 2 +- 23 files changed, 1065 insertions(+), 269 deletions(-) create mode 100644 src/app/chat/views/message_search/constants.nim create mode 100644 src/app/chat/views/message_search/location_menu_item.nim create mode 100644 src/app/chat/views/message_search/location_menu_model.nim create mode 100644 src/app/chat/views/message_search/location_menu_sub_item.nim create mode 100644 src/app/chat/views/message_search/location_menu_sub_model.nim create mode 100644 src/app/chat/views/message_search/result_item.nim create mode 100644 src/app/chat/views/message_search/result_model.nim create mode 100644 src/app/chat/views/message_search/view_controller.nim delete mode 100644 ui/app/AppLayouts/Chat/components/SearchPopup.qml diff --git a/src/app/chat/views/channels_list.nim b/src/app/chat/views/channels_list.nim index e10b475301..dc953e53bf 100644 --- a/src/app/chat/views/channels_list.nim +++ b/src/app/chat/views/channels_list.nim @@ -1,7 +1,6 @@ import NimQml, Tables import ../../../status/chat/[chat, message] import ../../../status/status -import ../../../status/ens import ../../../status/accounts import strutils @@ -39,19 +38,6 @@ QtObject: result.status = status result.setup() - proc userNameOrAlias(self: ChannelsList, pubKey: string): string = - if self.status.chat.contacts.hasKey(pubKey): - return ens.userNameOrAlias(self.status.chat.contacts[pubKey]) - generateAlias(pubKey) - - proc chatName(self: ChannelsList, chatItem: Chat): string = - if not chatItem.chatType.isOneToOne: return chatItem.name - if self.status.chat.contacts.hasKey(chatItem.id) and self.status.chat.contacts[chatItem.id].hasNickname(): - return self.status.chat.contacts[chatItem.id].localNickname - if chatItem.ensName != "": - return "@" & userName(chatItem.ensName).userName(true) - return self.userNameOrAlias(chatItem.id) - method rowCount*(self: ChannelsList, index: QModelIndex = nil): int = self.chats.len proc renderBlock(self: ChannelsList, message: Message): string @@ -66,7 +52,7 @@ QtObject: let chatItemRole = role.ChannelsRoles case chatItemRole: - of ChannelsRoles.Name: result = newQVariant(self.chatName(chatItem)) + of ChannelsRoles.Name: result = newQVariant(self.status.chat.chatName(chatItem)) of ChannelsRoles.Timestamp: result = newQVariant($chatItem.timestamp) of ChannelsRoles.LastMessage: result = newQVariant(self.renderBlock(chatItem.lastMessage)) of ChannelsRoles.ContentType: result = newQVariant(chatItem.lastMessage.contentType.int) @@ -136,6 +122,13 @@ QtObject: if chat.id == chatId: return chat + proc getChannelById*(self: ChannelsList, chatId: string, found: var bool): Chat = + found = false + for chat in self.chats: + if chat.id == chatId: + found = true + return chat + proc getChannelByName*(self: ChannelsList, name: string): Chat = for chat in self.chats: if chat.name == name: @@ -213,7 +206,7 @@ QtObject: proc renderInline(self: ChannelsList, elem: TextItem): string = case elem.textType: - of "mention": result = self.userNameOrAlias(elem.literal) + of "mention": result = self.status.chat.userNameOrAlias(elem.literal) of "link": result = elem.destination else: result = escape_html(elem.literal) diff --git a/src/app/chat/views/community_list.nim b/src/app/chat/views/community_list.nim index 8d8c565e3f..ca49380a4a 100644 --- a/src/app/chat/views/community_list.nim +++ b/src/app/chat/views/community_list.nim @@ -313,4 +313,15 @@ QtObject: return self.communities[comIndex].recalculateMentions() let index = self.createIndex(comIndex, 0, nil) - self.dataChanged(index, index, @[CommunityRoles.UnviewedMentionsCount.int]) \ No newline at end of file + self.dataChanged(index, index, @[CommunityRoles.UnviewedMentionsCount.int]) + + proc getChannelByIdAndBelongingCommunity*(self: CommunityList, chatId: string, + chat: var Chat, community: var Community): bool = + for co in self.communities: + for ch in co.chats: + if ch.id == chatId: + community = co + chat = ch + return true + + return false \ No newline at end of file diff --git a/src/app/chat/views/message_search/constants.nim b/src/app/chat/views/message_search/constants.nim new file mode 100644 index 0000000000..0c90dbb5a3 --- /dev/null +++ b/src/app/chat/views/message_search/constants.nim @@ -0,0 +1,6 @@ +const SEARCH_MENU_LOCATION_CHAT_SECTION_NAME* = "Chat" + +const SEARCH_RESULT_COMMUNITIES_SECTION_NAME* = "Communities" +const SEARCH_RESULT_CHATS_SECTION_NAME* = "Chats" +const SEARCH_RESULT_CHANNELS_SECTION_NAME* = "Channels" +const SEARCH_RESULT_MESSAGES_SECTION_NAME* = "Messages" diff --git a/src/app/chat/views/message_search/location_menu_item.nim b/src/app/chat/views/message_search/location_menu_item.nim new file mode 100644 index 0000000000..f6f34eb454 --- /dev/null +++ b/src/app/chat/views/message_search/location_menu_item.nim @@ -0,0 +1,82 @@ +import json, strformat + +import ../../../../status/chat/[chat] +import ../../../../status/[status] + +import location_menu_sub_model, location_menu_sub_item + +type + MessageSearchLocationMenuItem* = object + value: string + title: string + imageSource: string + iconName: string + iconColor: string + isIdenticon: bool + subItems: MessageSearchLocationMenuSubModel + +proc initMessageSearchLocationMenuItem*(status: Status, + value, title, imageSource: string, + iconName, iconColor: string = "", + isIdenticon: bool = true): MessageSearchLocationMenuItem = + result.value = value + result.title = title + result.imageSource = imageSource + result.iconName = iconName + result.iconColor = iconColor + result.isIdenticon = isIdenticon + result.subItems = newMessageSearchLocationMenuSubModel(status) + +proc `$`*(self: MessageSearchLocationMenuItem): string = + result = fmt"""MenuItem( + value: {self.value}, + title: {self.title}, + isIdenticon: {self.isIdenticon}, + iconName: {self.iconName}, + iconColor: {self.iconColor}, + imageSource:{self.imageSource} + subItems:[ + {$self.subItems} + ]""" + +proc getValue*(self: MessageSearchLocationMenuItem): string = + return self.value + +proc getTitle*(self: MessageSearchLocationMenuItem): string = + return self.title + +proc getImageSource*(self: MessageSearchLocationMenuItem): string = + return self.imageSource + +proc getIconName*(self: MessageSearchLocationMenuItem): string = + return self.iconName + +proc getIconColor*(self: MessageSearchLocationMenuItem): string = + return self.iconColor + +proc getIsIdenticon*(self: MessageSearchLocationMenuItem): bool = + return self.isIdenticon + +proc getSubItems*(self: MessageSearchLocationMenuItem): + MessageSearchLocationMenuSubModel = + self.subItems + +proc prepareSubItems*(self: MessageSearchLocationMenuItem, chats: seq[Chat], + isCommunityChannel: bool) = + self.subItems.prepareItems(chats, isCommunityChannel) + +proc getLocationSubItemForChatId*(self: MessageSearchLocationMenuItem, + chatId: string, found: var bool): MessageSearchLocationMenuSubItem = + self.subItems.getLocationSubItemForChatId(chatId, found) + +proc toJsonNode*(self: MessageSearchLocationMenuItem): JsonNode = + result = %* { + "value": self.value, + "title": self.title, + "imageSource": self.imageSource, + "iconName": self.iconName, + "iconColor": self.iconColor, + "isIdenticon": self.isIdenticon + } + + \ No newline at end of file diff --git a/src/app/chat/views/message_search/location_menu_model.nim b/src/app/chat/views/message_search/location_menu_model.nim new file mode 100644 index 0000000000..2d7ae7b6db --- /dev/null +++ b/src/app/chat/views/message_search/location_menu_model.nim @@ -0,0 +1,113 @@ +import NimQml, Tables, strutils + +import ../../../../status/chat/[chat] +import ../../../../status/[status] + +import location_menu_item, location_menu_sub_item, constants + +type + MessageSearchLocationMenuModelRole {.pure.} = enum + Value = UserRole + 1 + Title + ImageSource + IconName + IconColor + IsIdenticon + SubItems + +QtObject: + type + MessageSearchLocationMenuModel* = ref object of QAbstractListModel + items: seq[MessageSearchLocationMenuItem] + + proc delete(self: MessageSearchLocationMenuModel) = + self.QAbstractListModel.delete + + proc setup(self: MessageSearchLocationMenuModel) = + self.QAbstractListModel.setup + + proc newMessageSearchLocationMenuModel*(): MessageSearchLocationMenuModel = + new(result, delete) + result.setup() + + method rowCount(self: MessageSearchLocationMenuModel, + index: QModelIndex = nil): int = + + return self.items.len + + method roleNames(self: MessageSearchLocationMenuModel): Table[int, string] = + { + MessageSearchLocationMenuModelRole.Value.int:"value", + MessageSearchLocationMenuModelRole.Title.int:"title", + MessageSearchLocationMenuModelRole.ImageSource.int:"imageSource", + MessageSearchLocationMenuModelRole.IconName.int:"iconName", + MessageSearchLocationMenuModelRole.IconColor.int:"iconColor", + MessageSearchLocationMenuModelRole.IsIdenticon.int:"isIdenticon", + MessageSearchLocationMenuModelRole.SubItems.int:"subItems" + }.toTable + + method data(self: MessageSearchLocationMenuModel, 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.MessageSearchLocationMenuModelRole + + case enumRole: + of MessageSearchLocationMenuModelRole.Value: + result = newQVariant(item.getValue) + of MessageSearchLocationMenuModelRole.Title: + result = newQVariant(item.getTitle) + of MessageSearchLocationMenuModelRole.ImageSource: + result = newQVariant(item.getImageSource) + of MessageSearchLocationMenuModelRole.IconName: + result = newQVariant(item.getIconName) + of MessageSearchLocationMenuModelRole.IconColor: + result = newQVariant(item.getIconColor) + of MessageSearchLocationMenuModelRole.IsIdenticon: + result = newQVariant(item.getIsIdenticon) + of MessageSearchLocationMenuModelRole.SubItems: + result = newQVariant(item.getSubItems) + + proc prepareLocationMenu*(self: MessageSearchLocationMenuModel, status: Status, + chats: seq[Chat], communities: seq[Community]) = + + self.beginResetModel() + + self.items = @[] + var item = initMessageSearchLocationMenuItem(status, + SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, + SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, "", "chat", "", false) + item.prepareSubItems(chats, false) + self.items.add(item) + + for co in communities: + item = initMessageSearchLocationMenuItem(status, co.id, co.name, + co.communityImage.thumbnail, "", co.communityColor, + co.communityImage.thumbnail.len == 0) + item.prepareSubItems(co.chats, true) + self.items.add(item) + + self.endResetModel() + + proc getLocationItemForCommunityId*(self: MessageSearchLocationMenuModel, + communityId: string, found: var bool): MessageSearchLocationMenuItem = + + found = false + for i in self.items: + if (i.getValue() == communityId): + found = true + return i + + proc getLocationSubItemForChatId*(self: MessageSearchLocationMenuModel, + chatId: string, found: var bool): MessageSearchLocationMenuSubItem = + + for i in self.items: + let subItem = i.getLocationSubItemForChatId(chatId, found) + if (found): + return subItem diff --git a/src/app/chat/views/message_search/location_menu_sub_item.nim b/src/app/chat/views/message_search/location_menu_sub_item.nim new file mode 100644 index 0000000000..f39c059a5e --- /dev/null +++ b/src/app/chat/views/message_search/location_menu_sub_item.nim @@ -0,0 +1,59 @@ +import json, strformat + +type + MessageSearchLocationMenuSubItem* = object + value: string + text: string + imageSource: string + iconName: string + iconColor: string + isIdenticon: bool + +proc initMessageSearchLocationMenuSubItem*(value, text, imageSource: string, + iconName, iconColor: string = "", + isIdenticon: bool = true): MessageSearchLocationMenuSubItem = + result.value = value + result.text = text + result.imageSource = imageSource + result.iconName = iconName + result.iconColor = iconColor + result.isIdenticon = isIdenticon + +proc `$`*(self: MessageSearchLocationMenuSubItem): string = + result = fmt"""MenuSubItem: + value: {self.value}, + text: {self.text}, + isIdenticon: {self.isIdenticon}, + iconName: {self.iconName}, + iconColor: {self.iconColor}, + imageSource:{self.imageSource}""" + +proc getValue*(self: MessageSearchLocationMenuSubItem): string = + return self.value + +proc getText*(self: MessageSearchLocationMenuSubItem): string = + return self.text + +proc getImageSource*(self: MessageSearchLocationMenuSubItem): string = + return self.imageSource + +proc getIconName*(self: MessageSearchLocationMenuSubItem): string = + return self.iconName + +proc getIconColor*(self: MessageSearchLocationMenuSubItem): string = + return self.iconColor + +proc getIsIdenticon*(self: MessageSearchLocationMenuSubItem): bool = + return self.isIdenticon + +proc toJsonNode*(self: MessageSearchLocationMenuSubItem): JsonNode = + result = %* { + "value": self.value, + "text": self.text, + "imageSource": self.imageSource, + "iconName": self.iconName, + "iconColor": self.iconColor, + "isIdenticon": self.isIdenticon + } + + \ No newline at end of file diff --git a/src/app/chat/views/message_search/location_menu_sub_model.nim b/src/app/chat/views/message_search/location_menu_sub_model.nim new file mode 100644 index 0000000000..d902d2909a --- /dev/null +++ b/src/app/chat/views/message_search/location_menu_sub_model.nim @@ -0,0 +1,117 @@ +import NimQml, Tables, strutils, strformat + +import ../../../../status/chat/[chat] +import ../../../../status/[status] + +import location_menu_sub_item + +type + MessageSearchLocationMenuSubModelRole {.pure.} = enum + Value = UserRole + 1 + Text + ImageSource + IconName + IconColor + IsIdenticon + +QtObject: + type + MessageSearchLocationMenuSubModel* = ref object of QAbstractListModel + status: Status + items: seq[MessageSearchLocationMenuSubItem] + + proc delete(self: MessageSearchLocationMenuSubModel) = + self.QAbstractListModel.delete + + proc setup(self: MessageSearchLocationMenuSubModel) = + self.QAbstractListModel.setup + + proc newMessageSearchLocationMenuSubModel*(status: Status): + MessageSearchLocationMenuSubModel = + new(result, delete) + result.status = status + result.setup() + + proc `$`*(self: MessageSearchLocationMenuSubModel): string = + for i in 0 ..< self.items.len: + result &= fmt""" + [{i}]:({$self.items[i]}) + """ + + proc countChanged*(self: MessageSearchLocationMenuSubModel) {.signal.} + + proc count*(self: MessageSearchLocationMenuSubModel): int {.slot.} = + self.items.len + + QtProperty[int] count: + read = count + notify = countChanged + + method rowCount(self: MessageSearchLocationMenuSubModel, + index: QModelIndex = nil): int = + return self.items.len + + method roleNames(self: MessageSearchLocationMenuSubModel): + Table[int, string] = + { + MessageSearchLocationMenuSubModelRole.Value.int:"value", + MessageSearchLocationMenuSubModelRole.Text.int:"text", + MessageSearchLocationMenuSubModelRole.ImageSource.int:"imageSource", + MessageSearchLocationMenuSubModelRole.IconName.int:"iconName", + MessageSearchLocationMenuSubModelRole.IconColor.int:"iconColor", + MessageSearchLocationMenuSubModelRole.IsIdenticon.int:"isIdenticon" + }.toTable + + method data(self: MessageSearchLocationMenuSubModel, 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.MessageSearchLocationMenuSubModelRole + + case enumRole: + of MessageSearchLocationMenuSubModelRole.Value: + result = newQVariant(item.getValue) + of MessageSearchLocationMenuSubModelRole.Text: + result = newQVariant(item.getText) + of MessageSearchLocationMenuSubModelRole.ImageSource: + result = newQVariant(item.getImageSource) + of MessageSearchLocationMenuSubModelRole.IconName: + result = newQVariant(item.getIconName) + of MessageSearchLocationMenuSubModelRole.IconColor: + result = newQVariant(item.getIconColor) + of MessageSearchLocationMenuSubModelRole.IsIdenticon: + result = newQVariant(item.getIsIdenticon) + + proc prepareItems*(self: MessageSearchLocationMenuSubModel, chats: seq[Chat], + isCommunityChannel: bool) = + self.beginResetModel() + + self.items = @[] + + for c in chats: + var text = self.status.chat.chatName(c) + if (isCommunityChannel): + self.items.add(initMessageSearchLocationMenuSubItem(c.id, text, "", + "channel", c.color, false)) + else: + if (text.endsWith(".stateofus.eth")): + text = text[0 .. ^15] + + self.items.add(initMessageSearchLocationMenuSubItem(c.id, text, + c.identicon, "", c.color, c.identicon.len == 0)) + + self.endResetModel() + + proc getLocationSubItemForChatId*(self: MessageSearchLocationMenuSubModel, + chatId: string, found: var bool): MessageSearchLocationMenuSubItem = + + found = false + for i in self.items: + if (i.getValue() == chatId): + found = true + return i \ No newline at end of file diff --git a/src/app/chat/views/message_search/result_item.nim b/src/app/chat/views/message_search/result_item.nim new file mode 100644 index 0000000000..27fd92b402 --- /dev/null +++ b/src/app/chat/views/message_search/result_item.nim @@ -0,0 +1,80 @@ +import strformat + +type SearchResultItem* = object + itemId: string + content: string + time: string + titleId: string + title: string + sectionName: string + isLetterIdenticon: bool + badgeImage: string + badgePrimaryText: string + badgeSecondaryText: string + badgeIdenticonColor: string + +proc initSearchResultItem*(itemId, content, time, titleId, title, + sectionName: string, + isLetterIdenticon: bool = false, + badgeImage, badgePrimaryText, badgeSecondaryText, + badgeIdenticonColor: string = ""): SearchResultItem = + + result.itemId = itemId + result.content = content + result.time = time + result.titleId = titleId + result.title = title + result.sectionName = sectionName + result.isLetterIdenticon = isLetterIdenticon + result.badgeImage = badgeImage + result.badgePrimaryText = badgePrimaryText + result.badgeSecondaryText = badgeSecondaryText + result.badgeIdenticonColor = badgeIdenticonColor + +proc `$`*(self: SearchResultItem): string = + result = "MessageSearchResultItem(" + result &= fmt"itemId:{self.itemId}, " + result &= fmt"content:{self.content}, " + result &= fmt"time:{self.time}, " + result &= fmt"titleId:{self.titleId}, " + result &= fmt"title:{self.title}" + result &= fmt"sectionName:{self.sectionName}" + result &= fmt"isLetterIdenticon:{self.isLetterIdenticon}" + result &= fmt"badgeImage:{self.badgeImage}" + result &= fmt"badgePrimaryText:{self.badgePrimaryText}" + result &= fmt"badgeSecondaryText:{self.badgeSecondaryText}" + result &= fmt"badgeIdenticonColor:{self.badgeIdenticonColor}" + result &= ")" + +method getItemId*(self: SearchResultItem): string {.base.} = + return self.itemId + +method getContent*(self: SearchResultItem): string {.base.} = + return self.content + +method getTime*(self: SearchResultItem): string {.base.} = + return self.time + +method getTitleId*(self: SearchResultItem): string {.base.} = + return self.titleId + +method getTitle*(self: SearchResultItem): string {.base.} = + return self.title + +method getSectionName*(self: SearchResultItem): string {.base.} = + return self.sectionName + +method getIsLetterIdentIcon*(self: SearchResultItem): bool {.base.} = + return self.isLetterIdenticon + +method getBadgeImage*(self: SearchResultItem): string {.base.} = + return self.badgeImage + +method getBadgePrimaryText*(self: SearchResultItem): string {.base.} = + return self.badgePrimaryText + +method getBadgeSecondaryText*(self: SearchResultItem): string {.base.} = + return self.badgeSecondaryText + +method getBadgeIdenticonColor*(self: SearchResultItem): string {.base.} = + return self.badgeIdenticonColor \ No newline at end of file diff --git a/src/app/chat/views/message_search/result_model.nim b/src/app/chat/views/message_search/result_model.nim new file mode 100644 index 0000000000..f6c089a64a --- /dev/null +++ b/src/app/chat/views/message_search/result_model.nim @@ -0,0 +1,112 @@ +import NimQml, Tables, strutils + +import result_item + +type + MessageSearchResultModelRole {.pure.} = enum + ItemId = UserRole + 1 + Content + Time + TitleId + Title + SectionName + IsLetterIdenticon + BadgeImage + BadgePrimaryText + BadgeSecondaryText + BadgeIdenticonColor + +QtObject: + type + MessageSearchResultModel* = ref object of QAbstractListModel + resultList: seq[SearchResultItem] + + proc delete(self: MessageSearchResultModel) = + self.QAbstractListModel.delete + + proc setup(self: MessageSearchResultModel) = + self.QAbstractListModel.setup + + proc newMessageSearchResultModel*(): MessageSearchResultModel = + new(result, delete) + result.setup() + + ################################################# + # Properties + ################################################# + + proc countChanged*(self: MessageSearchResultModel) {.signal.} + + proc count*(self: MessageSearchResultModel): int {.slot.} = + self.resultList.len + + QtProperty[int] count: + read = count + notify = countChanged + + method rowCount(self: MessageSearchResultModel, index: QModelIndex = nil): int = + return self.resultList.len + + method roleNames(self: MessageSearchResultModel): Table[int, string] = + { + MessageSearchResultModelRole.ItemId.int:"itemId", + MessageSearchResultModelRole.Content.int:"content", + MessageSearchResultModelRole.Time.int:"time", + MessageSearchResultModelRole.TitleId.int:"titleId", + MessageSearchResultModelRole.Title.int:"title", + MessageSearchResultModelRole.SectionName.int:"sectionName", + MessageSearchResultModelRole.IsLetterIdenticon.int:"isLetterIdenticon", + MessageSearchResultModelRole.BadgeImage.int:"badgeImage", + MessageSearchResultModelRole.BadgePrimaryText.int:"badgePrimaryText", + MessageSearchResultModelRole.BadgeSecondaryText.int:"badgeSecondaryText", + MessageSearchResultModelRole.BadgeIdenticonColor.int:"badgeIdenticonColor" + }.toTable + + method data(self: MessageSearchResultModel, index: QModelIndex, role: int): QVariant = + if (not index.isValid): + return + + if (index.row < 0 or index.row >= self.resultList.len): + return + + let item = self.resultList[index.row] + let enumRole = role.MessageSearchResultModelRole + + case enumRole: + of MessageSearchResultModelRole.ItemId: + result = newQVariant(item.getItemId) + of MessageSearchResultModelRole.Content: + result = newQVariant(item.getContent) + of MessageSearchResultModelRole.Time: + result = newQVariant(item.getTime) + of MessageSearchResultModelRole.TitleId: + result = newQVariant(item.getTitleId) + of MessageSearchResultModelRole.Title: + result = newQVariant(item.getTitle) + of MessageSearchResultModelRole.SectionName: + result = newQVariant(item.getSectionName) + of MessageSearchResultModelRole.IsLetterIdenticon: + result = newQVariant(item.getIsLetterIdentIcon) + of MessageSearchResultModelRole.BadgeImage: + result = newQVariant(item.getBadgeImage) + of MessageSearchResultModelRole.BadgePrimaryText: + result = newQVariant(item.getBadgePrimaryText) + of MessageSearchResultModelRole.BadgeSecondaryText: + result = newQVariant(item.getBadgeSecondaryText) + of MessageSearchResultModelRole.BadgeIdenticonColor: + result = newQVariant(item.getBadgeIdenticonColor) + + proc add*(self: MessageSearchResultModel, item: SearchResultItem) = + self.beginInsertRows(newQModelIndex(), self.resultList.len, self.resultList.len) + self.resultList.add(item) + self.endInsertRows() + + proc set*(self: MessageSearchResultModel, items: seq[SearchResultItem]) = + self.beginResetModel() + self.resultList = items + self.endResetModel() + + proc clear*(self: MessageSearchResultModel) = + self.beginResetModel() + self.resultList = @[] + self.endResetModel() \ No newline at end of file diff --git a/src/app/chat/views/message_search/view_controller.nim b/src/app/chat/views/message_search/view_controller.nim new file mode 100644 index 0000000000..6c07e2fb79 --- /dev/null +++ b/src/app/chat/views/message_search/view_controller.nim @@ -0,0 +1,265 @@ +import NimQml, Tables, json, strutils, chronicles + +import result_model, result_item, location_menu_model, location_menu_item, location_menu_sub_item +import constants as sr_constants + +import ../../../../status/[status, types] +import ../../../../status/chat/[message, chat] +import ../../../../status/libstatus/[settings] +import ../communities +import ../channel +import ../chat_item +import ../channels_list +import ../community_list + +logScope: + topics = "search-messages-view-controller" + +type ResultItemInfo = object + communityId*: string + channelId*: string + messageId*: string + +method isEmpty*(self: ResultItemInfo): bool {.base.} = + self.communityId.len == 0 and + self.channelId.len == 0 and + self.messageId.len == 0 + +QtObject: + type MessageSearchViewController* = ref object of QObject + status: Status + channelView: ChannelView + communitiesView: CommunitiesView + resultItems: Table[string, ResultItemInfo] # [resuiltItemId, ResultItemInfo] + + messageSearchResultModel: MessageSearchResultModel + messageSearchLocationMenuModel: MessageSearchLocationMenuModel + meassgeSearchTerm: string + meassgeSearchLocation: string + meassgeSearchSubLocation: string + + proc setup(self: MessageSearchViewController) = + self.QObject.setup + + proc delete*(self: MessageSearchViewController) = + self.messageSearchResultModel.delete + self.messageSearchLocationMenuModel.delete + self.resultItems.clear + self.QObject.delete + + proc newMessageSearchViewController*(status: Status, channelView: ChannelView, + communitiesView: CommunitiesView): MessageSearchViewController = + new(result, delete) + result.status = status + result.channelView = channelView + result.communitiesView = communitiesView + result.resultItems = initTable[string, ResultItemInfo]() + result.messageSearchResultModel = newMessageSearchResultModel() + result.messageSearchLocationMenuModel = newMessageSearchLocationMenuModel() + result.setup + + proc getMessageSearchResultModel*(self: MessageSearchViewController): + QVariant {.slot.} = + newQVariant(self.messageSearchResultModel) + + QtProperty[QVariant] resultModel: + read = getMessageSearchResultModel + + proc getMessageSearchLocationMenuModel*(self: MessageSearchViewController): + QVariant {.slot.} = + newQVariant(self.messageSearchLocationMenuModel) + + QtProperty[QVariant] locationMenuModel: + read = getMessageSearchLocationMenuModel + + proc getSearchLocationObject*(self: MessageSearchViewController): string {.slot.} = + ## This method returns location and subLocation with their details so we + ## may set initial search location on the side of qml. + var found = false + let subItem = self.messageSearchLocationMenuModel.getLocationSubItemForChatId( + self.channelView.activeChannel.id, found + ) + + var jsonObject = %* { + "location": "", + "subLocation": "" + } + + if(found): + jsonObject["subLocation"] = subItem.toJsonNode() + + if(self.channelView.activeChannel.communityId.len == 0): + jsonObject["location"] = %* { + "value":sr_constants.SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, + "title":sr_constants.SEARCH_MENU_LOCATION_CHAT_SECTION_NAME + } + else: + let item = self.messageSearchLocationMenuModel.getLocationItemForCommunityId( + self.channelView.activeChannel.communityId, found + ) + + if(found): + jsonObject["location"] = item.toJsonNode() + + result = Json.encode(jsonObject) + + proc prepareLocationMenuModel*(self: MessageSearchViewController) + {.slot.} = + self.messageSearchLocationMenuModel.prepareLocationMenu( + self.status, + self.channelView.chats.chats, + self.communitiesView.joinedCommunityList.communities) + + proc setSearchLocation*(self: MessageSearchViewController, location: string = "", + subLocation: string = "") {.slot.} = + ## Setting location and subLocation to an empty string means we're + ## searching in all available chats/channels/communities. + self.meassgeSearchLocation = location + self.meassgeSearchSubLocation = subLocation + + proc searchMessages*(self: MessageSearchViewController, searchTerm: string) + {.slot.} = + self.meassgeSearchTerm = searchTerm + self.resultItems.clear + + if (self.meassgeSearchTerm.len == 0): + self.messageSearchResultModel.clear() + return + + var chats: seq[string] + var communities: seq[string] + + if (self.meassgeSearchSubLocation.len > 0): + chats.add(self.meassgeSearchSubLocation) + elif (self.meassgeSearchLocation.len > 0): + # If "Chat" is set for the meassgeSearchLocation that means we need to + # search in all chats from the chat section. + if (self.meassgeSearchLocation != sr_constants.SEARCH_MENU_LOCATION_CHAT_SECTION_NAME): + communities.add(self.meassgeSearchLocation) + else: + for c in self.channelView.chats.chats: + chats.add(c.id) + + if (communities.len == 0 and chats.len == 0): + for c in self.channelView.chats.chats: + chats.add(c.id) + + for co in self.communitiesView.joinedCommunityList.communities: + communities.add(co.id) + + self.status.chat.asyncSearchMessages(communities, chats, + self.meassgeSearchTerm, false) + + proc onSearchMessagesLoaded*(self: MessageSearchViewController, + messages: seq[Message]) = + + self.resultItems.clear + + var items: seq[SearchResultItem] + var channels: seq[SearchResultItem] + let myPublicKey = getSetting[string](Setting.PublicKey, "0x0") + + # Add communities + for co in self.communitiesView.joinedCommunityList.communities: + if(self.meassgeSearchLocation.len == 0 and + co.name.toLower.startsWith(self.meassgeSearchTerm.toLower)): + let item = initSearchResultItem(co.id, "", "", co.id, co.name, + sr_constants.SEARCH_RESULT_COMMUNITIES_SECTION_NAME, false, + co.communityImage.thumbnail, "", "", co.communityColor) + + self.resultItems.add(co.id, ResultItemInfo(communityId: co.id)) + items.add(item) + + # Add channels + if(self.meassgeSearchSubLocation.len == 0 and + self.meassgeSearchLocation.len == 0 or + self.meassgeSearchLocation == co.name): + for c in co.chats: + let chatName = self.status.chat.chatName(c) + var chatNameRaw = chatName + if(chatName.startsWith("@")): + chatNameRaw = chatName[1 ..^ 1] + + if(chatNameRaw.toLower.startsWith(self.meassgeSearchTerm.toLower)): + let item = initSearchResultItem(c.id, "", "", c.id, chatName, + sr_constants.SEARCH_RESULT_CHANNELS_SECTION_NAME, + c.identicon.len > 0, c.identicon, "", "", c.color) + + self.resultItems.add(c.id, ResultItemInfo(communityId: co.id, + channelId: c.id)) + channels.add(item) + + # Add chats + if(self.meassgeSearchLocation.len == 0 or + self.meassgeSearchLocation == sr_constants.SEARCH_RESULT_CHATS_SECTION_NAME): + for c in self.channelView.chats.chats: + let chatName = self.status.chat.chatName(c) + var chatNameRaw = chatName + if(chatName.startsWith("@")): + chatNameRaw = chatName[1 ..^ 1] + + if(chatNameRaw.toLower.startsWith(self.meassgeSearchTerm.toLower)): + let item = initSearchResultItem(c.id, "", "", c.id, chatName, + sr_constants.SEARCH_RESULT_CHATS_SECTION_NAME, + c.identicon.len > 0, c.identicon, "", "", c.color) + + self.resultItems.add(c.id, ResultItemInfo(communityId: "", + channelId: c.id)) + items.add(item) + + # Add channels in order as requested by design + items.add(channels) + + # Add messages + for m in messages: + if (m.contentType != ContentType.Message): + continue + + var found = false + var chat = self.channelView.chats.getChannelById(m.chatId, found) + if (found): + var channel = self.status.chat.chatName(chat) + if (channel.endsWith(".stateofus.eth")): + channel = channel[0 .. ^15] + + var alias = self.status.chat.userNameOrAlias(m.fromAuthor, true) + if (myPublicKey == m.fromAuthor): + alias = "You" + + let item = initSearchResultItem(m.id, m.text, m.timestamp, m.fromAuthor, + alias, sr_constants.SEARCH_RESULT_MESSAGES_SECTION_NAME, + chat.identicon.len == 0, chat.identicon, channel, "", chat.color) + + self.resultItems.add(m.id, ResultItemInfo(communityId: "", + channelId: chat.id, messageId: m.id)) + items.add(item) + else: + var community: Community + if (self.communitiesView.joinedCommunityList. + getChannelByIdAndBelongingCommunity(m.chatId, chat, community)): + + var channel = self.status.chat.chatName(chat) + if (not channel.startsWith("#")): + channel = "#" & channel + if (channel.endsWith(".stateofus.eth")): + channel = channel[0 .. ^15] + + var alias = self.status.chat.userNameOrAlias(m.fromAuthor, true) + if (myPublicKey == m.fromAuthor): + alias = "You" + + let item = initSearchResultItem(m.id, m.text, m.timestamp, m.fromAuthor, + m.alias, sr_constants.SEARCH_RESULT_MESSAGES_SECTION_NAME, + community.communityImage.thumbnail.len == 0, + community.communityImage.thumbnail, community.name, channel, + community.communityColor) + + self.resultItems.add(m.id, ResultItemInfo(communityId: community.id, + channelId: chat.id, messageId: m.id)) + items.add(item) + + self.messageSearchResultModel.set(items) + + proc getItemInfo*(self: MessageSearchViewController, itemId: string): + ResultItemInfo = + self.resultItems.getOrDefault(itemId) diff --git a/src/status/chat.nim b/src/status/chat.nim index 668c475e73..01f6bed652 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -12,7 +12,7 @@ import contacts import chat/[chat, message] import tasks/[qt, task_runner_impl] import signals/messages -import ens +import ens, accounts logScope: topics = "chat-model" @@ -621,6 +621,9 @@ QtObject: return self.channels[chatId].communityId proc asyncSearchMessages*(self: ChatModel, chatId: string, searchTerm: string, caseSensitive: bool) = + ## Asynchronous search for messages which contain the searchTerm and belong + ## to the chat with chatId. + if (chatId.len == 0): info "empty channel id set for fetching more messages" return @@ -628,8 +631,8 @@ QtObject: if (searchTerm.len == 0): return - let arg = AsyncSearchMessageTaskArg( - tptr: cast[ByteAddress](asyncSearchMessagesTask), + let arg = AsyncSearchMessagesInChatTaskArg( + tptr: cast[ByteAddress](asyncSearchMessagesInChatTask), vptr: cast[ByteAddress](self.vptr), slot: "onAsyncSearchMessages", chatId: chatId, @@ -638,6 +641,29 @@ QtObject: ) self.tasks.threadpool.start(arg) + proc asyncSearchMessages*(self: ChatModel, communityIds: seq[string], chatIds: seq[string], searchTerm: string, caseSensitive: bool) = + ## Asynchronous search for messages which contain the searchTerm and belong + ## to either any chat/channel from chatIds array or any channel of community + ## from communityIds array. + + if (communityIds.len == 0 and chatIds.len == 0): + info "either community ids or chat ids or both must be set" + return + + if (searchTerm.len == 0): + return + + let arg = AsyncSearchMessagesInChatsAndCommunitiesTaskArg( + tptr: cast[ByteAddress](asyncSearchMessagesInChatsAndCommunitiesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncSearchMessages", + communityIds: communityIds, + chatIds: chatIds, + searchTerm: searchTerm, + caseSensitive: caseSensitive + ) + self.tasks.threadpool.start(arg) + proc onAsyncSearchMessages*(self: ChatModel, response: string) {.slot.} = let responseObj = response.parseJson if (responseObj.kind != JObject): @@ -774,3 +800,31 @@ QtObject: self.events.emit("messagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: messages)) self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: pinnedMessages)) + + proc userNameOrAlias*(self: ChatModel, pubKey: string, + prettyForm: bool = false): string = + ## Returns ens name or alias, in case if prettyForm is true and ens name + ## ends with ".stateofus.eth" that part will be removed. + var alias: string + if self.contacts.hasKey(pubKey): + alias = ens.userNameOrAlias(self.contacts[pubKey]) + else: + alias = generateAlias(pubKey) + + if (prettyForm and alias.endsWith(".stateofus.eth")): + alias = alias[0 .. ^15] + + return alias + + proc chatName*(self: ChatModel, chatItem: Chat): string = + if (not chatItem.chatType.isOneToOne): + return chatItem.name + + if (self.contacts.hasKey(chatItem.id) and + self.contacts[chatItem.id].hasNickname()): + return self.contacts[chatItem.id].localNickname + + if chatItem.ensName != "": + return "@" & userName(chatItem.ensName).userName(true) + + return self.userNameOrAlias(chatItem.id) diff --git a/src/status/chat/async_tasks.nim b/src/status/chat/async_tasks.nim index 581b434e42..6215f039e6 100644 --- a/src/status/chat/async_tasks.nim +++ b/src/status/chat/async_tasks.nim @@ -1,14 +1,17 @@ -################################################# -# Async search messages by term -################################################# -type - AsyncSearchMessageTaskArg = ref object of QObjectTaskArg - chatId: string +type + AsyncSearchMessagesTaskArg = ref object of QObjectTaskArg searchTerm: string caseSensitive: bool -const asyncSearchMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[AsyncSearchMessageTaskArg](argEncoded) +################################################# +# Async search messages in chat with chatId by term +################################################# +type + AsyncSearchMessagesInChatTaskArg = ref object of AsyncSearchMessagesTaskArg + chatId: string + +const asyncSearchMessagesInChatTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncSearchMessagesInChatTaskArg](argEncoded) var messages: JsonNode var success: bool let response = status_chat.asyncSearchMessages(arg.chatId, arg.searchTerm, arg.caseSensitive, success) @@ -22,6 +25,30 @@ const asyncSearchMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall } arg.finish(responseJson) +################################################# +# Async search messages in chats/channels and communities by term +################################################# +type + AsyncSearchMessagesInChatsAndCommunitiesTaskArg = ref object of AsyncSearchMessagesTaskArg + communityIds: seq[string] + chatIds: seq[string] + +const asyncSearchMessagesInChatsAndCommunitiesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncSearchMessagesInChatsAndCommunitiesTaskArg](argEncoded) + var messages: JsonNode + var success: bool + let response = status_chat.asyncSearchMessages(arg.communityIds, arg.chatIds, arg.searchTerm, arg.caseSensitive, success) + + if(success): + messages = response.parseJson()["result"] + + let responseJson = %*{ + "communityIds": arg.communityIds, + "chatIds": arg.chatIds, + "messages": messages + } + arg.finish(responseJson) + ################################################# # Async load messages ################################################# diff --git a/src/status/chat/chat.nim b/src/status/chat/chat.nim index bf524455e7..ea36b515a7 100644 --- a/src/status/chat/chat.nim +++ b/src/status/chat/chat.nim @@ -246,7 +246,6 @@ proc recalculateUnviewedMessages*(community: var Community) = community.unviewedMessagesCount = total proc recalculateMentions*(community: var Community) = - echo "(recalculateMentions) chatId: ", community.id, " before: ", community.unviewedMentionsCount var total = 0 for chat in community.chats: total += chat.unviewedMentionsCount diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 0fcea006c3..7847c79d87 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -569,7 +569,15 @@ proc unreadActivityCenterNotificationsCount*(): int = proc asyncSearchMessages*(chatId: string, searchTerm: string, caseSensitive: bool, success: var bool): string = success = true try: - result = callPrivateRPC("allChatMessagesWhichMatchTerm".prefix, %* [chatId, searchTerm, caseSensitive]) + result = callPrivateRPC("allMessagesFromChatWhichMatchTerm".prefix, %* [chatId, searchTerm, caseSensitive]) + except RpcException as e: + success = false + result = e.msg + +proc asyncSearchMessages*(communityIds: seq[string], chatIds: seq[string], searchTerm: string, caseSensitive: bool, success: var bool): string = + success = true + try: + result = callPrivateRPC("allMessagesFromChatsAndCommunitiesWhichMatchTerm".prefix, %* [communityIds, chatIds, searchTerm, caseSensitive]) except RpcException as e: success = false result = e.msg diff --git a/ui/StatusQ b/ui/StatusQ index 38b0207055..c68ee3dbe4 160000 --- a/ui/StatusQ +++ b/ui/StatusQ @@ -1 +1 @@ -Subproject commit 38b0207055f81c853830320e8b20e9532e84973b +Subproject commit c68ee3dbe412d8af7cab079b1cb67e0a2a3bf155 diff --git a/ui/app/AppLayouts/Chat/ChatColumn.qml b/ui/app/AppLayouts/Chat/ChatColumn.qml index b382f2d15f..1dc2a56f3b 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn.qml @@ -1,8 +1,8 @@ import QtQuick 2.13 +import Qt.labs.platform 1.1 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.13 import QtGraphicalEffects 1.0 -import Qt.labs.platform 1.1 import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 @@ -165,6 +165,107 @@ Item { } } + StatusSearchLocationMenu { + id: searchPopupMenu + searchPopup: searchPopup + locatioModel: chatsModel.messageSearchViewController.locationMenuModel + + onItemClicked: { + chatsModel.messageSearchViewController.setSearchLocation(firstLevelItemValue, secondLevelItemValue) + if(searchPopup.searchText !== "") + searchMessages(searchPopup.searchText) + } + } + + property var searchMessages: Backpressure.debounce(searchPopup, 400, function (value) { + chatsModel.messageSearchViewController.searchMessages(value) + }) + + StatusSearchPopup { + id: searchPopup + + noResultsLabel: qsTr("No results") + defaultSearchLocationText: qsTr("Anywhere") + + searchOptionsPopupMenu: searchPopupMenu + searchResults: chatsModel.messageSearchViewController.resultModel + onSearchTextChanged: { + searchMessages(searchPopup.searchText); + } + onAboutToHide: { + if (searchPopupMenu.visible) { + searchPopupMenu.close(); + } + //clear menu + for (var i = 2; i <= searchPopupMenu.count; i++) { + searchPopupMenu.removeItem(searchPopupMenu.takeItem(i)); + } + } + onClosed: { + searchPopupMenu.dismiss(); + } + onOpened: { + searchPopup.resetSearchSelection(); + chatsModel.messageSearchViewController.prepareLocationMenuModel() + + const jsonObj = chatsModel.messageSearchViewController.getSearchLocationObject() + + if (!jsonObj) { + return + } + + let obj = JSON.parse(jsonObj) + if (obj.location === "") { + if(obj.subLocation === "") { + chatsModel.messageSearchViewController.setSearchLocation("", "") + } + else { + searchPopup.setSearchSelection(obj.subLocation.text, + "", + obj.subLocation.imageSource, + obj.subLocation.isIdenticon, + obj.subLocation.iconName, + obj.subLocation.identiconColor) + + chatsModel.messageSearchViewController.setSearchLocation("", obj.subLocation.value) + } + } + else { + if (obj.location.title === "Chat") { + searchPopup.setSearchSelection(obj.subLocation.text, + "", + obj.subLocation.imageSource, + obj.subLocation.isIdenticon, + obj.subLocation.iconName, + obj.subLocation.identiconColor) + + chatsModel.messageSearchViewController.setSearchLocation(obj.location.value, obj.subLocation.value) + } + else { + searchPopup.setSearchSelection(obj.location.title, + obj.subLocation.text, + obj.location.imageSource, + obj.location.isIdenticon, + obj.location.iconName, + obj.location.identiconColor) + + chatsModel.messageSearchViewController.setSearchLocation(obj.location.value, obj.subLocation.value) + } + } + } + onResultItemClicked: { + searchPopup.close() + + chatsModel.switchToSearchedItem(itemId) + } + + onResultItemTitleClicked: { + const pk = titleId + const userProfileImage = appMain.getProfileImage(pk) + return openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk)) + } + } + StackLayout { anchors.fill: parent currentIndex: chatsModel.channelView.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1 @@ -253,9 +354,6 @@ Item { notificationCount: chatsModel.activityNotificationList.unreadCount onSearchButtonClicked: searchPopup.open() - SearchPopup { - id: searchPopup - } onMembersButtonClicked: showUsers = !showUsers onNotificationButtonClicked: activityCenter.open() diff --git a/ui/app/AppLayouts/Chat/components/SearchPopup.qml b/ui/app/AppLayouts/Chat/components/SearchPopup.qml deleted file mode 100644 index 5f0219fda3..0000000000 --- a/ui/app/AppLayouts/Chat/components/SearchPopup.qml +++ /dev/null @@ -1,227 +0,0 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import "../../../../imports" -import "../../../../shared" -import "../../../../shared/status" -import "../ChatColumn" - -Popup { - property string chatId: chatsModel.channelView.activeChannel.id - - id: popup - modal: true - - Overlay.modal: Rectangle { - color: Qt.rgba(0, 0, 0, 0.4) - } - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - parent: Overlay.overlay - x: Math.round(((parent ? parent.width : 0) - width) / 2) - y: Math.round(((parent ? parent.height : 0) - height) / 2) - width: 690 - height: { - const noResultHeight = 122 - let minHeight = 560 - const maxHeight = parent.height - 200 - - if (!searchResultContent.visible) { - return noResultHeight - } - - if (minHeight > maxHeight) { - return maxHeight - } - - if (listView.height < minHeight - noResultHeight) { - return minHeight - } - if (listView.height > maxHeight - noResultHeight) { - return maxHeight - } - } - background: Rectangle { - color: Style.current.background - radius: 16 - } - onOpened: { - popupOpened = true - searchInput.forceActiveFocus(Qt.MouseFocusReason) - } - onClosed: { - popupOpened = false - } - padding: 0 - - Connections { - target: chatsModel.channelView - onActiveChannelChanged: { - searchInput.text = "" - } - } - - Item { - id: searchHeader - width: parent.width - height: 64 - - SVGImage { - id: searchImage - source: "../../../img/search.svg" - width: 40 - height: 40 - anchors.left: parent.left - anchors.leftMargin: Style.current.padding - anchors.verticalCenter: parent.verticalCenter - } - - property var searchMessages: Backpressure.debounce(searchInput, 400, function (value) { - chatsModel.messageView.searchMessages(value) - }) - - StyledTextField { - id: searchInput - anchors.left: searchImage.right - anchors.leftMargin: Style.current.smallPadding - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: Style.current.padding - //% "Search" - placeholderText: qsTrId("search") - placeholderTextColor: Style.current.secondaryText - selectByMouse: true - font.pixelSize: 28 - background: Rectangle { - color: Style.current.transparent - } - onTextChanged: { - searchHeader.searchMessages(searchInput.text) - } - } - - Separator { - anchors.bottom: parent.bottom - anchors.topMargin: 0 - } - } - - Rectangle { - id: channelBadge - color: Style.current.inputBackground - border.width: 0 - radius: Style.current.radius - height: 32 - width: childrenRect.width + 2 * inText.anchors.leftMargin - anchors.left: parent.left - anchors.leftMargin: Style.current.padding - anchors.top: searchHeader.bottom - anchors.topMargin: 12 - - StyledText { - id: inText - //% "In:" - text: qsTrId("in-") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: 15 - } - - BadgeContent { - chatId: popup.chatId - name: Utils.removeStatusEns(chatsModel.channelView.activeChannel.name) - identicon: chatsModel.channelView.activeChannel.identicon - communityId: chatsModel.channelView.activeChannel.communityId - anchors.left: inText.right - anchors.leftMargin: 4 - anchors.verticalCenter: parent.verticalCenter - hideSecondIcon: true - } - } - - Item { - id: searchResultContent - visible: chatsModel.messageView.searchResultMessageModel.count > 0 - width: parent.width - anchors.bottom: parent.bottom - anchors.top: channelBadge.bottom - anchors.topMargin: visible ? 13 : 0 - - Separator { - id: sep2 - anchors.top: parent.top - anchors.topMargin: 0 - } - - StyledText { - id: sectionTitle - //% "Messages" - text: qsTrId("messages") - font.pixelSize: 15 - color: Style.current.secondaryText - anchors.top: sep2.bottom - anchors.topMargin: Style.current.smallPadding - anchors.left: parent.left - anchors.leftMargin: Style.current.bigPadding - } - - ScrollView { - id: scrollView - anchors.top: sectionTitle.bottom - anchors.topMargin: 4 - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.smallPadding - width: parent.width - clip: true - - ListView{ - id: listView - model: chatsModel.messageView.searchResultMessageModel - - delegate: Message { - - 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)) - } - - popup.close() - - if(chatsModel.messageView.isMessageDisplayed(model.messageId)) - positionAtMessage(model.messageId) - else - chatsModel.messageView.loadMessagesUntillMessageWithIdIsLoaded(model.messageId) - } - - prevMessageIndex: -1 - prevMsgTimestamp: "" - } - } - } - } -} diff --git a/ui/imports/Backpressure/Backpressure.qml b/ui/imports/Backpressure/Backpressure.qml index 3ef42dbe2a..a033916f00 100644 --- a/ui/imports/Backpressure/Backpressure.qml +++ b/ui/imports/Backpressure/Backpressure.qml @@ -21,13 +21,13 @@ Item { owner = backpressure; } - owner.Component.onDestruction.connect(cleanup); + owner.Component.destruction.connect(cleanup); var obj = Qt.createQmlObject('import QtQuick 2.0; Timer {running: false; repeat: false; interval: ' + timeout + '}', backpressure, "setTimeout"); obj.triggered.connect(function() { callback(); obj.destroy(); - owner.Component.onDestruction.disconnect(cleanup); + owner.Component.destruction.disconnect(cleanup); delete _timers[tid]; }); obj.running = true; diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index af920247c0..cca421a6d1 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -179,7 +179,6 @@ DISTFILES += \ app/AppLayouts/Chat/components/InviteFriendsPopup.qml \ app/AppLayouts/Chat/components/MessageContextMenu.qml \ app/AppLayouts/Chat/components/NicknamePopup.qml \ - app/AppLayouts/Chat/components/SearchPopup.qml \ app/AppLayouts/Chat/components/SuggestedChannels.qml \ app/AppLayouts/Chat/components/GroupInfoPopup.qml \ app/AppLayouts/Chat/data/channelList.js \ diff --git a/vendor/DOtherSide b/vendor/DOtherSide index c18777749a..2bffa67581 160000 --- a/vendor/DOtherSide +++ b/vendor/DOtherSide @@ -1 +1 @@ -Subproject commit c18777749a2574d743ca2b7e5abc654152154f00 +Subproject commit 2bffa67581c4e7546cfb5784f187b85bc6f97388 diff --git a/vendor/nim-status-go b/vendor/nim-status-go index 2b6e504917..f4463f3955 160000 --- a/vendor/nim-status-go +++ b/vendor/nim-status-go @@ -1 +1 @@ -Subproject commit 2b6e50491786ae0d61a97f99edda27b70364838a +Subproject commit f4463f3955a96e162e9881b73ba02f819e0374a4 diff --git a/vendor/nimqml b/vendor/nimqml index 936a4fa8ab..3a2026ebbc 160000 --- a/vendor/nimqml +++ b/vendor/nimqml @@ -1 +1 @@ -Subproject commit 936a4fa8abd452c65cfd9d40f5ac261b3260a47e +Subproject commit 3a2026ebbc8a98ef1ffe15693685feea38286552 diff --git a/vendor/status-go b/vendor/status-go index 9f478db7ad..33747cc283 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 9f478db7ad06d0e84e43edf30c52ff586a9d266c +Subproject commit 33747cc283b65603565c008b522a0148c5645128