fix(@desktop/sections): make ctrl+k display chats from communities

Closes: #4059
This commit is contained in:
Patryk Osmaczko 2022-02-04 14:07:48 +01:00 committed by osmaczko
parent 16328823a9
commit 914c7b2839
16 changed files with 400 additions and 53 deletions

View File

@ -0,0 +1,35 @@
type
Item* = ref object
chatId: string
name: string
color: string
icon: string
sectionId: string
sectionName: string
proc initItem*(chatId, name, color, icon, sectionId, sectionName: string): Item =
result = Item()
result.chatId = chatId
result.name = name
result.color = color
result.icon = icon
result.sectionId = sectionId
result.sectionName = sectionName
proc chatId*(self: Item): string =
self.chatId
proc name*(self: Item): string =
self.name
proc color*(self: Item): string =
self.color
proc icon*(self: Item): string =
self.icon
proc sectionId*(self: Item): string =
self.sectionId
proc sectionName*(self: Item): string =
self.sectionName

View File

@ -0,0 +1,66 @@
import NimQml, Tables
import chat_search_item
type
ModelRole {.pure.} = enum
ChatId = UserRole + 1
Name
Color
Icon
SectionId
SectionName
QtObject:
type Model* = ref object of QAbstractListModel
items: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc newModel*(): Model =
new(result, delete)
result.setup
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
self.items = items
self.endResetModel()
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.ChatId.int:"chatId",
ModelRole.Name.int:"name",
ModelRole.Color.int:"color",
ModelRole.Icon.int:"icon",
ModelRole.SectionId.int:"sectionId",
ModelRole.SectionName.int:"sectionName",
}.toTable
method data(self: Model, 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.ChatId:
result = newQVariant(item.chatId)
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.Color:
result = newQVariant(item.color)
of ModelRole.Icon:
result = newQVariant(item.icon)
of ModelRole.SectionId:
result = newQVariant(item.sectionId)
of ModelRole.SectionName:
result = newQVariant(item.sectionName)

View File

@ -64,6 +64,9 @@ QtObject:
method rowCount(self: Model, index: QModelIndex = nil): int = method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len return self.items.len
proc items*(self: Model): seq[Item] =
return self.items
method roleNames(self: Model): Table[int, string] = method roleNames(self: Model): Table[int, string] =
{ {
ModelRole.Id.int:"itemId", ModelRole.Id.int:"itemId",

View File

@ -1,7 +1,8 @@
import NimQml, Tables, chronicles, json, sequtils import NimQml, Tables, chronicles, json, sequtils
import io_interface import io_interface
import ../io_interface as delegate_interface import ../io_interface as delegate_interface
import view, controller, item, sub_item, model, sub_model, base_item import view, controller, item, sub_item, sub_model, base_item
import model as chats_model
import ../../shared_models/contacts_item as contacts_item import ../../shared_models/contacts_item as contacts_item
import ../../shared_models/contacts_model as contacts_model import ../../shared_models/contacts_model as contacts_model
@ -355,6 +356,9 @@ method onActiveSectionChange*(self: Module, sectionId: string) =
self.updateNotifications(self.controller.getActiveChatId(), unviewedMessagesCount=0, unviewedMentionsCount=0) self.updateNotifications(self.controller.getActiveChatId(), unviewedMessagesCount=0, unviewedMentionsCount=0)
self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId()) self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId())
method chatsModel*(self: Module): chats_model.Model =
return self.view.chatsModel()
method createPublicChat*(self: Module, chatId: string) = method createPublicChat*(self: Module, chatId: string) =
if(self.controller.isCommunity()): if(self.controller.isCommunity()):
debug "creating public chat is not allowed for community, most likely it's an error in qml", methodName="createPublicChat" debug "creating public chat is not allowed for community, most likely it's an error in qml", methodName="createPublicChat"

View File

@ -8,6 +8,8 @@ import ../../../../../app_service/service/message/service as message_service
import ../../../../../app_service/service/gif/service as gif_service import ../../../../../app_service/service/gif/service as gif_service
import ../../../../../app_service/service/mailservers/service as mailservers_service import ../../../../../app_service/service/mailservers/service as mailservers_service
import ../model as chats_model
import ../../../../core/eventemitter import ../../../../core/eventemitter
method delete*(self: AccessInterface) {.base.} = method delete*(self: AccessInterface) {.base.} =
@ -30,4 +32,7 @@ method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onActiveSectionChange*(self: AccessInterface, sectionId: string) {.base.} = method onActiveSectionChange*(self: AccessInterface, sectionId: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method chatsModel*(self: AccessInterface): chats_model.Model {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -57,6 +57,9 @@ QtObject:
method rowCount(self: SubModel, index: QModelIndex = nil): int = method rowCount(self: SubModel, index: QModelIndex = nil): int =
return self.items.len return self.items.len
proc items*(self: SubModel): seq[SubItem] =
return self.items
method roleNames(self: SubModel): Table[int, string] = method roleNames(self: SubModel): Table[int, string] =
{ {
ModelRole.Id.int:"itemId", ModelRole.Id.int:"itemId",

View File

@ -251,4 +251,7 @@ method resolveENS*(self: Controller, ensName: string, uuid: string = "") =
self.contactsService.resolveENS(ensName, uuid) self.contactsService.resolveENS(ensName, uuid)
method isMnemonicBackedUp*(self: Controller): bool = method isMnemonicBackedUp*(self: Controller): bool =
result = self.privacyService.isMnemonicBackedUp() result = self.privacyService.isMnemonicBackedUp()
method switchTo*(self: Controller, sectionId, chatId: string) =
self.messageService.switchTo(sectionId, chatId, "")

View File

@ -48,4 +48,7 @@ method resolveENS*(self: AccessInterface, ensName: string, uuid: string = "") {.
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method isMnemonicBackedUp*(self: AccessInterface): bool {.base.} = method isMnemonicBackedUp*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method switchTo*(self: AccessInterface, sectionId, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,12 +1,15 @@
import NimQml, tables, json, sugar, sequtils import NimQml, tables, json, sugar, sequtils
import io_interface, view, controller import io_interface, view, controller, chat_search_item, chat_search_model
import ./communities/models/[pending_request_item, pending_request_model] import ./communities/models/[pending_request_item, pending_request_model]
import ../shared_models/[user_item, user_model, section_item, section_model, active_section] import ../shared_models/[user_item, user_model, section_item, section_model, active_section]
import ../../global/app_sections_config as conf import ../../global/app_sections_config as conf
import ../../global/app_signals import ../../global/app_signals
import ../../global/global_singleton import ../../global/global_singleton
import chat_section/[model, sub_item, sub_model]
import chat_section/base_item as chat_section_base_item
import chat_section/item as chat_section_item
import chat_section/module as chat_section_module import chat_section/module as chat_section_module
import wallet_section/module as wallet_section_module import wallet_section/module as wallet_section_module
import browser_section/module as browser_section_module import browser_section/module as browser_section_module
@ -508,6 +511,27 @@ method getCommunitySectionModule*[T](self: Module[T], communityId: string): QVar
return self.communitySectionsModule[communityId].getModuleAsVariant() return self.communitySectionsModule[communityId].getModuleAsVariant()
method rebuildChatSearchModel*[T](self: Module[T]) =
let transformItem = proc(item: chat_section_base_item.BaseItem, sectionId, sectionName: string): chat_search_item.Item =
result = chat_search_item.initItem(item.id(), item.name(), item.color(), item.icon(), sectionId, sectionName)
let transform = proc(items: seq[chat_section_item.Item], sectionId, sectionName: string): seq[chat_search_item.Item] =
for item in items:
if item.type() != ChatType.Unknown.int:
result.add(transformItem(item, sectionId, sectionName))
else:
for subItem in item.subItems().items():
result.add(transformItem(subItem, sectionId, sectionName))
var items = transform(self.chatSectionModule.chatsModel().items(), conf.CHAT_SECTION_ID, conf.CHAT_SECTION_NAME)
for cId in self.communitySectionsModule.keys:
items.add(transform(self.communitySectionsModule[cId].chatsModel().items(), cId, self.view.model().getItemById(cId).name()))
self.view.chatSearchModel().setItems(items)
method switchTo*[T](self: Module[T], sectionId, chatId: string) =
self.controller.switchTo(sectionId, chatId)
method onActiveChatChange*[T](self: Module[T], sectionId: string, chatId: string) = method onActiveChatChange*[T](self: Module[T], sectionId: string, chatId: string) =
self.appSearchModule.onActiveChatChange(sectionId, chatId) self.appSearchModule.onActiveChatChange(sectionId, chatId)

View File

@ -1,5 +1,6 @@
import NimQml import NimQml
import ../../shared_models/section_item import ../../shared_models/section_item
import ../chat_search_item
method viewDidLoad*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -26,4 +27,10 @@ method getContactDetailsAsJson*(self: AccessInterface, publicKey: string): strin
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method resolveENS*(self: AccessInterface, ensName: string, uuid: string) {.base.} = method resolveENS*(self: AccessInterface, ensName: string, uuid: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method rebuildChatSearchModel*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method switchTo*(self: AccessInterface, sectionId, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -3,15 +3,18 @@ import ../shared_models/section_model
import ../shared_models/section_item import ../shared_models/section_item
import ../shared_models/active_section import ../shared_models/active_section
import io_interface import io_interface
import chat_search_model
QtObject: QtObject:
type type
View* = ref object of QObject View* = ref object of QObject
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
model: SectionModel model: section_model.SectionModel
modelVariant: QVariant modelVariant: QVariant
activeSection: ActiveSection activeSection: ActiveSection
activeSectionVariant: QVariant activeSectionVariant: QVariant
chatSearchModel: chat_search_model.Model
chatSearchModelVariant: QVariant
tmpCommunityId: string # shouldn't be used anywhere except in prepareCommunitySectionModuleForCommunityId/getCommunitySectionModule procs tmpCommunityId: string # shouldn't be used anywhere except in prepareCommunitySectionModuleForCommunityId/getCommunitySectionModule procs
proc activeSectionChanged*(self:View) {.signal.} proc activeSectionChanged*(self:View) {.signal.}
@ -21,16 +24,20 @@ QtObject:
self.modelVariant.delete self.modelVariant.delete
self.activeSection.delete self.activeSection.delete
self.activeSectionVariant.delete self.activeSectionVariant.delete
self.chatSearchModel.delete
self.chatSearchModelVariant.delete
self.QObject.delete self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View = proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.delegate = delegate result.delegate = delegate
result.model = newModel() result.model = section_model.newModel()
result.modelVariant = newQVariant(result.model) result.modelVariant = newQVariant(result.model)
result.activeSection = newActiveSection() result.activeSection = newActiveSection()
result.activeSectionVariant = newQVariant(result.activeSection) result.activeSectionVariant = newQVariant(result.activeSection)
result.chatSearchModel = chat_search_model.newModel()
result.chatSearchModelVariant = newQVariant(result.chatSearchModel)
proc load*(self: View) = proc load*(self: View) =
# In some point, here, we will setup some exposed main module related things. # In some point, here, we will setup some exposed main module related things.
@ -54,6 +61,21 @@ QtObject:
read = getModel read = getModel
notify = modelChanged notify = modelChanged
proc chatSearchModel*(self: View): chat_search_model.Model =
return self.chatSearchModel
proc chatSearchModelChanged*(self: View) {.signal.}
proc getChatSearchModel(self: View): QVariant {.slot.} =
return self.chatSearchModelVariant
proc rebuildChatSearchModel*(self: View) {.slot.} =
self.delegate.rebuildChatSearchModel()
QtProperty[QVariant] chatSearchModel:
read = getChatSearchModel
notify = chatSearchModelChanged
proc openStoreToKeychainPopup*(self: View) {.signal.} proc openStoreToKeychainPopup*(self: View) {.signal.}
proc offerToStorePassword*(self: View) = proc offerToStorePassword*(self: View) =
@ -96,6 +118,9 @@ QtObject:
let item = self.model.getItemBySectionType(sectionType.SectionType) let item = self.model.getItemBySectionType(sectionType.SectionType)
self.delegate.setActiveSection(item) self.delegate.setActiveSection(item)
proc switchTo*(self: View, sectionId: string, chatId: string) {.slot.} =
self.delegate.switchTo(sectionId, chatId)
proc setUserStatus*(self: View, status: bool) {.slot.} = proc setUserStatus*(self: View, status: bool) {.slot.} =
self.delegate.setUserStatus(status) self.delegate.setUserStatus(status)

@ -1 +1 @@
Subproject commit d85ed4c3ed95fa1f32d8e50b2746691e5d9e5e4e Subproject commit 5780f183c7b3cf63c3abbc584daa72078241e917

View File

@ -19,6 +19,16 @@ QtObject {
property EmojiReactions emojiReactionsModel: EmojiReactions { property EmojiReactions emojiReactionsModel: EmojiReactions {
} }
property var chatSearchModel: mainModuleInst.chatSearchModel
function rebuildChatSearchModel() {
mainModuleInst.rebuildChatSearchModel()
}
function setActiveSectionChat(sectionId, chatId) {
mainModuleInst.switchTo(sectionId, chatId)
}
// Not Refactored Yet // Not Refactored Yet
// property var chatsModelInst: chatsModel // property var chatsModelInst: chatsModel
// Not Refactored Yet // Not Refactored Yet

View File

@ -286,7 +286,7 @@ Item {
// // that's why we're using it this way // // that's why we're using it this way
mainModule.prepareCommunitySectionModuleForCommunityId(model.id) mainModule.prepareCommunitySectionModuleForCommunityId(model.id)
communityContextMenu.chatCommunitySectionModule = mainModule.getCommunitySectionModule() communityContextMenu.chatCommunitySectionModule = mainModule.getCommunitySectionModule()
} }
StatusMenuItem { StatusMenuItem {
@ -723,12 +723,12 @@ Item {
Global.toastMessage = this; Global.toastMessage = this;
} }
} }
// Add SendModal here as it is used by the Wallet as well as the Browser // Add SendModal here as it is used by the Wallet as well as the Browser
Loader { Loader {
id: sendModal id: sendModal
active: false active: false
function open() { function open() {
this.active = true this.active = true
this.item.open() this.item.open()
@ -783,52 +783,44 @@ Item {
} }
} }
Component { StatusSearchListPopup {
id: statusSmartIdenticonComponent
StatusSmartIdenticon {
property string imageSource: ""
image: StatusImageSettings {
width: channelPicker.imageWidth
height: channelPicker.imageHeight
source: imageSource
isIdenticon: true
}
icon: StatusIconSettings {
width: channelPicker.imageWidth
height: channelPicker.imageHeight
letterSize: 15
color: Theme.palette.miscColor5
}
}
}
StatusInputListPopup {
id: channelPicker id: channelPicker
//% "Where do you want to go?"
title: qsTrId("where-do-you-want-to-go-")
showSearchBox: true
width: 350
x: parent.width / 2 - width / 2 x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2 y: parent.height / 2 - height / 2
// TODO improve this to work with community Chats as well
modelList: mainModule.getChatSectionModule().model searchBoxPlaceholder: qsTr("Where do you want to go?")
getText: function (modelData) { model: rootStore.chatSearchModel
return modelData.name delegate: StatusListItem {
} property var modelData
getId: function (modelData) { property bool isCurrentItem: true
return modelData.itemId function filterAccepts(searchText) {
} return title.includes(searchText)
getImageComponent: function (parent, modelData) { }
return statusSmartIdenticonComponent.createObject(parent, {
imageSource: modelData.identicon, title: modelData ? modelData.name : ""
name: modelData.name label: modelData? modelData.sectionName : ""
}); highlighted: isCurrentItem
sensor.hoverEnabled: false
statusListItemIcon {
name: modelData ? modelData.name : ""
active: true
}
icon {
width: image.width
height: image.height
color: modelData ? modelData.color : ""
}
image {
source: modelData ? modelData.icon : ""
isIdenticon: true
}
} }
onClicked: function (index, id) { onAboutToShow: rootStore.rebuildChatSearchModel()
Global.changeAppSectionBySectionType(Constants.appSection.chat) onSelected: {
mainModule.getChatSectionModule().setActiveItem(id, "") rootStore.setActiveSectionChat(modelData.sectionId, modelData.chatId)
channelPicker.close() close()
} }
} }
} }

View File

@ -0,0 +1,166 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.0
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Popup {
id: root
width: 400
height: 300
property alias model: listView.model
// delegate interface has to be fulfilled
property Component delegate: Item {
property var modelData
property bool isCurrentItem
function filterAccepts(searchText) {
return true
}
}
property string searchBoxPlaceholder: qsTr("Search...")
signal selected(int index, var modelData)
background: Rectangle {
radius: Style.current.radius
color: Style.current.background
border.color: Style.current.border
layer.enabled: true
layer.effect: DropShadow {
verticalOffset: 3
radius: 8
samples: 15
fast: true
cached: true
color: "#22000000"
}
}
ColumnLayout {
anchors.fill: parent
StatusInput {
id: searchBox
Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
input.placeholderText: root.searchBoxPlaceholder
input.icon: StatusIconSettings {
width: 24
height: 24
name: "search"
color: Theme.palette.baseColor1
}
function goToNextAvailableIndex(up) {
var currentIndex = listView.currentIndex
for (var i = 0; i < listView.count; i++) {
currentIndex = up ? (currentIndex === 0 ? listView.count - 1 : currentIndex - 1)
: (currentIndex === listView.count - 1 ? 0 : currentIndex + 1)
listView.currentIndex = currentIndex
if (listView.currentItem.visible) {
return
}
}
listView.currentIndex = 0
}
Keys.onReleased: {
listView.selectByHover = false
if (event.key === Qt.Key_Down) {
searchBox.goToNextAvailableIndex(false)
}
if (event.key === Qt.Key_Up) {
searchBox.goToNextAvailableIndex(true)
}
if (event.key === Qt.Key_Escape) {
return root.close()
}
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
return root.selected(listView.currentIndex,
listView.currentItem.myData)
}
if (!listView.currentItem.visible) {
goToNextAvailableIndex(false)
}
}
onTextChanged: if (text === "") listView.currentIndex = 0
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
property bool selectByHover: false
clip: true
highlightMoveDuration: 200
delegate: Item {
id: delegateItem
property var myData: typeof modelData === "undefined" ? model : modelData
width: listView.width
height: visible ? delegateLoader.height : 0
Loader {
id: delegateLoader
width: parent.width
sourceComponent: root.delegate
onLoaded: {
item.modelData = delegateItem.myData
item.isCurrentItem = Qt.binding(() => delegateItem.ListView.isCurrentItem)
delegateItem.visible = Qt.binding(() => item.filterAccepts(searchBox.text))
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: (mouse) => {
listView.currentIndex = index
root.selected(index, delegateItem.myData)
mouse.accepted = false
}
onContainsMouseChanged: if (containsMouse) listView.currentIndex = index
cursorShape: Qt.PointingHandCursor
}
}
Loader {
anchors.fill: parent
active: !listView.selectByHover
sourceComponent: MouseArea {
hoverEnabled: true
onPositionChanged: listView.selectByHover = true
}
}
}
}
onAboutToShow: {
listView.currentIndex = 0
listView.selectByHover = false
searchBox.text = ""
searchBox.input.edit.forceActiveFocus()
}
}

View File

@ -21,6 +21,7 @@ StatusImageModal 1.0 StatusImageModal.qml
StatusImageRadioButton 1.0 StatusImageRadioButton.qml StatusImageRadioButton 1.0 StatusImageRadioButton.qml
StatusInputListPopup 1.0 StatusInputListPopup.qml StatusInputListPopup 1.0 StatusInputListPopup.qml
StatusNotification 1.0 StatusNotification.qml StatusNotification 1.0 StatusNotification.qml
StatusSearchListPopup 1.0 StatusSearchListPopup.qml
StatusSectionDescItem 1.0 StatusSectionDescItem.qml StatusSectionDescItem 1.0 StatusSectionDescItem.qml
StatusSectionHeadline 1.0 StatusSectionHeadline.qml StatusSectionHeadline 1.0 StatusSectionHeadline.qml
StatusSettingsLineButton 1.0 StatusSettingsLineButton.qml StatusSettingsLineButton 1.0 StatusSettingsLineButton.qml