chore(community): replace plus button by adhoc btn

Fixes #5677
This commit is contained in:
Jonathan Rainville 2022-05-13 11:27:26 -04:00
parent 95326620c3
commit 6f14921c9b
15 changed files with 332 additions and 246 deletions

View File

@ -125,7 +125,7 @@ proc init*(self: Controller) =
self.delegate.onChatRenamed(args.newName) self.delegate.onChatRenamed(args.newName)
self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args): self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args):
var args = ChatUpdateArgsNew(e) var args = ChatUpdateArgs(e)
for chat in args.chats: for chat in args.chats:
if self.chatId == chat.id: if self.chatId == chat.id:
self.delegate.onChatEdited(chat) self.delegate.onChatEdited(chat)

View File

@ -113,7 +113,7 @@ proc init*(self: Controller) =
self.delegate.onChatMembersAdded(args.ids) self.delegate.onChatMembersAdded(args.ids)
self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args): self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args):
var args = ChatUpdateArgsNew(e) var args = ChatUpdateArgs(e)
for chat in args.chats: for chat in args.chats:
if (chat.id == self.chatId): if (chat.id == self.chatId):
self.delegate.onChatUpdated(chat) self.delegate.onChatUpdated(chat)

View File

@ -105,13 +105,20 @@ proc init*(self: Controller) =
self.delegate.onContactUnblocked(args.contactId) self.delegate.onContactUnblocked(args.contactId)
self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args): self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args):
var args = ChatUpdateArgsNew(e) var args = ChatUpdateArgs(e)
for chat in args.chats: for chat in args.chats:
let belongsToCommunity = chat.communityId.len > 0 let belongsToCommunity = chat.communityId.len > 0
self.delegate.addChatIfDontExist(chat, belongsToCommunity, self.events, self.settingsService, self.delegate.addChatIfDontExist(chat, belongsToCommunity, self.events, self.settingsService,
self.contactService, self.chatService, self.communityService, self.messageService, self.gifService, self.contactService, self.chatService, self.communityService, self.messageService, self.gifService,
self.mailserversService, setChatAsActive = false) self.mailserversService, setChatAsActive = false)
self.events.on(SIGNAL_CHAT_CREATED) do(e: Args):
var args = CreatedChatArgs(e)
let belongsToCommunity = args.chat.communityId.len > 0
self.delegate.addChatIfDontExist(args.chat, belongsToCommunity, self.events, self.settingsService,
self.contactService, self.chatService, self.communityService, self.messageService, self.gifService,
self.mailserversService, setChatAsActive = true)
if (self.isCommunitySection): if (self.isCommunitySection):
self.events.on(SIGNAL_COMMUNITY_CHANNEL_CREATED) do(e:Args): self.events.on(SIGNAL_COMMUNITY_CHANNEL_CREATED) do(e:Args):
let args = CommunityChatArgs(e) let args = CommunityChatArgs(e)

View File

@ -796,16 +796,14 @@ method addChatIfDontExist*(self: Module,
gifService: gif_service.Service, gifService: gif_service.Service,
mailserversService: mailservers_service.Service, mailserversService: mailservers_service.Service,
setChatAsActive: bool = true) = setChatAsActive: bool = true) =
let sectionId = self.controller.getMySectionId() let sectionId = self.controller.getMySectionId()
if(belongsToCommunity and sectionId != chat.communityId or if(belongsToCommunity and sectionId != chat.communityId or
not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey()): not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey()):
return return
if self.doesCatOrChatExist(chat.id): if self.doesCatOrChatExist(chat.id):
if chat.chatType != ChatType.OneToOne: if(chat.chatType != ChatType.OneToOne):
self.onChatRenamed(chat.id, chat.name) self.onChatRenamed(chat.id, chat.name)
return return
self.addNewChat(chat, belongsToCommunity, events, settingsService, contactService, chatService, self.addNewChat(chat, belongsToCommunity, events, settingsService, contactService, chatService,
communityService, messageService, gifService, mailserversService, setChatAsActive) communityService, messageService, gifService, mailserversService, setChatAsActive)

View File

@ -75,7 +75,7 @@ proc init*(self: Controller) =
self.delegate.removeItemWithId(args.chatId) self.delegate.removeItemWithId(args.chatId)
self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args): self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args):
var args = ChatUpdateArgsNew(e) var args = ChatUpdateArgs(e)
for chat in args.chats: for chat in args.chats:
let belongsToCommunity = chat.communityId.len > 0 let belongsToCommunity = chat.communityId.len > 0
self.delegate.addChat(chat) self.delegate.addChat(chat)

View File

@ -186,7 +186,7 @@ QtObject:
let (chats, messages) = self.chatService.parseChatResponse(response) let (chats, messages) = self.chatService.parseChatResponse(response)
self.events.emit(chat_service.SIGNAL_CHAT_UPDATE, self.events.emit(chat_service.SIGNAL_CHAT_UPDATE,
ChatUpdateArgsNew(messages: messages, chats: chats)) ChatUpdateArgs(messages: messages, chats: chats))
except Exception as e: except Exception as e:
error "Error marking as accepted", msg = e.msg error "Error marking as accepted", msg = e.msg

View File

@ -24,8 +24,7 @@ logScope:
include ../../common/json_utils include ../../common/json_utils
type type
# TODO remove New when refactored ChatUpdateArgs* = ref object of Args
ChatUpdateArgsNew* = ref object of Args
chats*: seq[ChatDto] chats*: seq[ChatDto]
messages*: seq[MessageDto] messages*: seq[MessageDto]
# TODO refactor that part # TODO refactor that part
@ -37,6 +36,9 @@ type
# statusUpdates*: seq[StatusUpdate] # statusUpdates*: seq[StatusUpdate]
# deletedMessages*: seq[RemovedMessage] # deletedMessages*: seq[RemovedMessage]
CreatedChatArgs* = ref object of Args
chat*: ChatDto
ChatArgs* = ref object of Args ChatArgs* = ref object of Args
communityId*: string # This param should be renamed to `sectionId`, that will avoid some confusions one may have. communityId*: string # This param should be renamed to `sectionId`, that will avoid some confusions one may have.
chatId*: string chatId*: string
@ -86,6 +88,7 @@ const SIGNAL_CHAT_MEMBER_REMOVED* = "chatMemberRemoved"
const SIGNAL_CHAT_MEMBER_UPDATED* = "chatMemberUpdated" const SIGNAL_CHAT_MEMBER_UPDATED* = "chatMemberUpdated"
const SIGNAL_CHAT_SWITCH_TO_OR_CREATE_1_1_CHAT* = "switchToOrCreateOneToOneChat" const SIGNAL_CHAT_SWITCH_TO_OR_CREATE_1_1_CHAT* = "switchToOrCreateOneToOneChat"
const SIGNAL_CHAT_ADDED_OR_UPDATED* = "chatAddedOrUpdated" const SIGNAL_CHAT_ADDED_OR_UPDATED* = "chatAddedOrUpdated"
const SIGNAL_CHAT_CREATED* = "chatCreated"
QtObject: QtObject:
type Service* = ref object of QObject type Service* = ref object of QObject
@ -117,7 +120,7 @@ QtObject:
if (chatDto.active): if (chatDto.active):
chats.add(chatDto) chats.add(chatDto)
self.updateOrAddChat(chatDto) self.updateOrAddChat(chatDto)
self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgsNew(messages: receivedData.messages, chats: chats)) self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgs(messages: receivedData.messages, chats: chats))
if (receivedData.clearedHistories.len > 0): if (receivedData.clearedHistories.len > 0):
for clearedHistoryDto in receivedData.clearedHistories: for clearedHistoryDto in receivedData.clearedHistories:
@ -227,7 +230,7 @@ QtObject:
proc emitUpdate(self: Service, response: RpcResponse[JsonNode]) = proc emitUpdate(self: Service, response: RpcResponse[JsonNode]) =
var (chats, messages) = self.parseChatResponse(response) var (chats, messages) = self.parseChatResponse(response)
self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgsNew(messages: messages, chats: chats)) self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgs(messages: messages, chats: chats))
proc getAllChats*(self: Service): seq[ChatDto] = proc getAllChats*(self: Service): seq[ChatDto] =
return toSeq(self.chats.values) return toSeq(self.chats.values)
@ -283,6 +286,8 @@ QtObject:
let response = status_group_chat.createOneToOneChat(communityID, chatId, ensName) let response = status_group_chat.createOneToOneChat(communityID, chatId, ensName)
result = self.createChatFromResponse(response) result = self.createChatFromResponse(response)
if result.success:
self.events.emit(SIGNAL_CHAT_CREATED, CreatedChatArgs(chat: result.chatDto))
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg
error "error: ", errDesription error "error: ", errDesription
@ -517,6 +522,8 @@ QtObject:
try: try:
let response = status_group_chat.createGroupChat(communityID, name, members) let response = status_group_chat.createGroupChat(communityID, name, members)
result = self.createChatFromResponse(response) result = self.createChatFromResponse(response)
if result.success:
self.events.emit(SIGNAL_CHAT_CREATED, CreatedChatArgs(chat: result.chatDto))
except Exception as e: except Exception as e:
error "error while creating group chat", msg = e.msg error "error while creating group chat", msg = e.msg

View File

@ -32,7 +32,6 @@ Item {
property var rootStore property var rootStore
property var contactsStore property var contactsStore
property var chatSectionModule
property var emojiPopup property var emojiPopup
property Component pinnedMessagesPopupComponent property Component pinnedMessagesPopupComponent
@ -166,12 +165,6 @@ Item {
onShareChatKeyClicked: Global.openProfilePopup(userProfile.pubKey); onShareChatKeyClicked: Global.openProfilePopup(userProfile.pubKey);
} }
CreateChatView {
rootStore: root.rootStore
emojiPopup: root.emojiPopup
visible: mainModule.activeSection.sectionType === Constants.appSection.chat && root.rootStore.openCreateChat
}
// This is kind of a solution for applying backend refactored changes with the minimal qml changes. // This is kind of a solution for applying backend refactored changes with the minimal qml changes.
// The best would be if we made qml to follow the struct we have on the backend side. // The best would be if we made qml to follow the struct we have on the backend side.
Repeater { Repeater {
@ -288,7 +281,6 @@ Item {
Component.onCompleted: { Component.onCompleted: {
parentModule.prepareChatContentModuleForChatId(model.itemId) parentModule.prepareChatContentModuleForChatId(model.itemId)
chatContentModule = parentModule.getChatContentModule() chatContentModule = parentModule.getChatContentModule()
root.checkForCreateChatOptions(model.itemId) root.checkForCreateChatOptions(model.itemId)
} }
} }
@ -409,7 +401,7 @@ Item {
height: root.height - 56 * 2 // TODO get screen size // Taken from old code top bar height was fixed there to 56 height: root.height - 56 * 2 // TODO get screen size // Taken from old code top bar height was fixed there to 56
y: 56 y: 56
store: root.rootStore store: root.rootStore
chatSectionModule: root.chatSectionModule chatSectionModule: root.parentModule
messageContextMenu: contextmenu messageContextMenu: contextmenu
} }

View File

@ -53,6 +53,13 @@ StatusAppThreePanelLayout {
} }
} }
Connections {
target: Global
onCloseCreateChatView: {
root.rootStore.openCreateChat = false
}
}
leftPanel: Loader { leftPanel: Loader {
id: contactColumnLoader id: contactColumnLoader
sourceComponent: root.rootStore.chatCommunitySectionModule.isCommunity()? sourceComponent: root.rootStore.chatCommunitySectionModule.isCommunity()?
@ -65,7 +72,6 @@ StatusAppThreePanelLayout {
parentModule: root.rootStore.chatCommunitySectionModule parentModule: root.rootStore.chatCommunitySectionModule
rootStore: root.rootStore rootStore: root.rootStore
contactsStore: root.contactsStore contactsStore: root.contactsStore
chatSectionModule: root.rootStore.chatCommunitySectionModule
pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent
stickersLoaded: root.stickersLoaded stickersLoaded: root.stickersLoaded
emojiPopup: root.emojiPopup emojiPopup: root.emojiPopup

View File

@ -4,10 +4,12 @@ import QtQuick.Dialogs 1.2
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import utils 1.0 import utils 1.0
import shared 1.0 import shared 1.0
import shared.popups 1.0 import shared.popups 1.0
@ -35,56 +37,49 @@ Item {
signal infoButtonClicked signal infoButtonClicked
signal manageButtonClicked signal manageButtonClicked
StatusChatInfoToolBar { StatusChatInfoButton {
id: communityHeader id: communityHeader
anchors.top: parent.top title: communityData.name
anchors.horizontalCenter: parent.horizontalCenter subTitle: communityData.members.count <= 1 ?
chatInfoButton.title: communityData.name
chatInfoButton.subTitle: communityData.members.count <= 1 ?
//% "1 Member" //% "1 Member"
qsTrId("1-member") : qsTrId("1-member") :
//% "%1 Members" //% "%1 Members"
qsTrId("-1-members").arg(communityData.members.count) qsTrId("-1-members").arg(communityData.members.count)
image.source: communityData.image
chatInfoButton.image.source: communityData.image icon.color: communityData.color
chatInfoButton.icon.color: communityData.color onClicked: root.infoButtonClicked()
menuButton.visible: communityData.amISectionAdmin && communityData.canManageUsers anchors.top: parent.top
chatInfoButton.onClicked: root.infoButtonClicked() anchors.topMargin: 5
anchors.left: parent.left
popupMenu: StatusPopupMenu { anchors.leftMargin: Style.current.halfPadding
StatusMenuItem { anchors.right: (implicitWidth > parent.width - 50) ? adHocChatButton.left : undefined
//% "Create channel" anchors.rightMargin: Style.current.halfPadding
text: qsTrId("create-channel") type: StatusChatInfoButton.Type.OneToOneChat
icon.name: "channel"
enabled: communityData.amISectionAdmin
onTriggered: Global.openPopup(createChannelPopup)
} }
StatusMenuItem { StatusIconTabButton {
//% "Create category" id: adHocChatButton
text: qsTrId("create-category") icon.name: "edit"
icon.name: "channel-category" anchors.verticalCenter: communityHeader.verticalCenter
enabled: communityData.amISectionAdmin anchors.right: parent.right
onTriggered: Global.openPopup(createCategoryPopup) anchors.rightMargin: 14
} checked: root.store.openCreateChat
highlighted: root.store.openCreateChat
StatusMenuSeparator {} onClicked: {
root.store.openCreateChat = !root.store.openCreateChat;
StatusMenuItem { if (!root.store.openCreateChat) {
//% "Invite people" Global.closeCreateChatView()
text: qsTrId("invite-people") } else {
icon.name: "share-ios" Global.openCreateChatView()
enabled: communityData.canManageUsers
onTriggered: Global.openPopup(inviteFriendsToCommunityPopup, {
community: communityData,
hasAddedContacts: root.hasAddedContacts,
communitySectionModule: root.communitySectionModule
})
} }
} }
StatusToolTip {
text: qsTr("Start chat")
visible: parent.hovered
}
} // StatusChatInfoToolBar } // StatusChatInfoToolBar
Loader { Loader {
id: membershipRequests id: membershipRequests
@ -111,7 +106,9 @@ Item {
ScrollView { ScrollView {
id: chatGroupsContainer id: chatGroupsContainer
anchors.top: membershipRequests.bottom anchors.top: membershipRequests.bottom
anchors.bottom: parent.bottom anchors.topMargin: Style.current.padding
anchors.bottom: createChatOrCommunity.top
anchors.horizontalCenter: parent.horizontalCenter
topPadding: Style.current.padding topPadding: Style.current.padding
@ -466,6 +463,53 @@ Item {
} // Column } // Column
} // ScrollView } // ScrollView
Loader {
id: createChatOrCommunity
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
active: communityData.amISectionAdmin
sourceComponent: Component {
StatusBaseText {
color: Theme.palette.baseColor1
height: visible ? implicitHeight : 0
text: qsTr("Create channel or category")
font.underline: true
font.pixelSize: 13
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (createChatOrCatMenu.opened) {
createChatOrCatMenu.close()
return
}
createChatOrCatMenu.open()
createChatOrCatMenu.y = - createChatOrCatMenu.height - 5
}
}
StatusPopupMenu {
id: createChatOrCatMenu
closePolicy: Popup.CloseOnPressOutsideParent
StatusMenuItem {
text: qsTr("Create channel")
icon.name: "channel"
onTriggered: Global.openPopup(createChannelPopup)
}
StatusMenuItem {
text: qsTrId("Create category")
icon.name: "channel-category"
onTriggered: Global.openPopup(createCategoryPopup)
}
}
}
}
}
Component { Component {
id: createChannelPopup id: createChannelPopup
CreateChannelPopup { CreateChannelPopup {

View File

@ -120,6 +120,11 @@ Item {
highlighted: root.store.openCreateChat highlighted: root.store.openCreateChat
onClicked: { onClicked: {
root.store.openCreateChat = !root.store.openCreateChat; root.store.openCreateChat = !root.store.openCreateChat;
if (!root.store.openCreateChat) {
Global.closeCreateChatView()
} else {
Global.openCreateChatView()
}
} }
StatusToolTip { StatusToolTip {

View File

@ -14,7 +14,6 @@ import shared.status 1.0
Page { Page {
id: root id: root
anchors.fill: parent
Behavior on anchors.bottomMargin { NumberAnimation { duration: 30 }} Behavior on anchors.bottomMargin { NumberAnimation { duration: 30 }}
property ListModel contactsModel: ListModel { } property ListModel contactsModel: ListModel { }
@ -22,6 +21,7 @@ Page {
property var emojiPopup: null property var emojiPopup: null
Keys.onEscapePressed: { Keys.onEscapePressed: {
Global.closeCreateChatView()
root.rootStore.openCreateChat = false; root.rootStore.openCreateChat = false;
} }
@ -37,10 +37,8 @@ Page {
} }
} }
Connections { onVisibleChanged: {
target: rootStore if (visible) {
onOpenCreateChatChanged: {
if (root.rootStore.openCreateChat) {
for (var i = 0; i < contactsModelListView.count; i ++) { for (var i = 0; i < contactsModelListView.count; i ++) {
var entry = contactsModelListView.itemAtIndex(i); var entry = contactsModelListView.itemAtIndex(i);
contactsModel.insert(contactsModel.count, contactsModel.insert(contactsModel.count,
@ -53,7 +51,6 @@ Page {
tagSelector.namesModel.clear(); tagSelector.namesModel.clear();
} }
} }
}
function createChat() { function createChat() {
if (tagSelector.namesModel.count === 1) { if (tagSelector.namesModel.count === 1) {
@ -66,22 +63,15 @@ Page {
groupName += (tagSelector.namesModel.get(i).name + (i === tagSelector.namesModel.count - 1 ? "" : "&")); groupName += (tagSelector.namesModel.get(i).name + (i === tagSelector.namesModel.count - 1 ? "" : "&"));
pubKeys.push(tagSelector.namesModel.get(i).pubKey); pubKeys.push(tagSelector.namesModel.get(i).pubKey);
} }
root.rootStore.chatCommunitySectionModule.createGroupChat("",groupName, JSON.stringify(pubKeys)); root.rootStore.chatCommunitySectionModule.createGroupChat("", groupName, JSON.stringify(pubKeys));
} }
chatInput.textInput.clear(); chatInput.textInput.clear();
chatInput.textInput.textFormat = TextEdit.PlainText; chatInput.textInput.textFormat = TextEdit.PlainText;
chatInput.textInput.textFormat = TextEdit.RichText; chatInput.textInput.textFormat = TextEdit.RichText;
Global.changeAppSectionBySectionType(Constants.appSection.chat)
} }
visible: (opacity > 0.01)
onVisibleChanged: {
if (!visible) {
tagSelector.namesModel.clear();
}
}
opacity: (root.rootStore.openCreateChat) ? 1.0 : 0.0
Behavior on opacity { NumberAnimation {}} Behavior on opacity { NumberAnimation {}}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
@ -95,8 +85,6 @@ Page {
height: tagSelector.height height: tagSelector.height
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 8 anchors.topMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
clip: true clip: true
StatusTagSelector { StatusTagSelector {
id: tagSelector id: tagSelector

View File

@ -0,0 +1 @@
CreateChatView 1.0 CreateChatView.qml

View File

@ -11,6 +11,7 @@ import AppLayouts.Node 1.0
import AppLayouts.Browser 1.0 import AppLayouts.Browser 1.0
import AppLayouts.Chat 1.0 import AppLayouts.Chat 1.0
import AppLayouts.Chat.popups 1.0 import AppLayouts.Chat.popups 1.0
import AppLayouts.Chat.views 1.0
import AppLayouts.Profile 1.0 import AppLayouts.Profile 1.0
import AppLayouts.Profile.popups 1.0 import AppLayouts.Profile.popups 1.0
import AppLayouts.CommunitiesPortal 1.0 import AppLayouts.CommunitiesPortal 1.0
@ -441,11 +442,15 @@ Item {
} }
} }
Item {
width: parent.width
Layout.fillHeight: true
StackLayout { StackLayout {
id: appView id: appView
width: parent.width
Layout.fillHeight: true anchors.fill: parent
currentIndex: { currentIndex: {
if(mainModule.activeSection.sectionType === Constants.appSection.chat) { if(mainModule.activeSection.sectionType === Constants.appSection.chat) {
@ -558,7 +563,6 @@ Item {
globalStore: appMain.rootStore globalStore: appMain.rootStore
sendTransactionModal: sendModal sendTransactionModal: sendModal
} }
} }
Loader { Loader {
@ -638,6 +642,38 @@ Item {
} }
} }
CreateChatView {
property bool opened: false
id: createChatView
rootStore: chatLayoutContainer.rootStore
emojiPopup: statusEmojiPopup
anchors.top: parent.top
anchors.topMargin: 8
anchors.rightMargin: 8
anchors.bottom: parent.bottom
anchors.right: parent.right
width: chatLayoutContainer.chatView.width - chatLayoutContainer.chatView.leftPanel.width - anchors.rightMargin - anchors.leftMargin
visible: createChatView.opened
Connections {
target: Global
onOpenCreateChatView: {
createChatView.opened = true
}
onCloseCreateChatView: {
createChatView.opened = false
}
}
Connections {
target: mainModule
onActiveSectionChanged: {
Global.closeCreateChatView()
}
}
}
}
Connections { Connections {
target: rootStore.mainModuleInst target: rootStore.mainModuleInst
onMailserverNotWorking: { onMailserverNotWorking: {

View File

@ -26,6 +26,8 @@ QtObject {
signal openDownloadModalRequested() signal openDownloadModalRequested()
signal settingsLoaded() signal settingsLoaded()
signal openBackUpSeedPopup() signal openBackUpSeedPopup()
signal openCreateChatView()
signal closeCreateChatView()
signal openProfilePopupRequested(string publicKey, var parentPopup, bool openNicknamePopup) signal openProfilePopupRequested(string publicKey, var parentPopup, bool openNicknamePopup)
signal openChangeProfilePicPopup() signal openChangeProfilePicPopup()