chore: consolidate & refactor popup handling

- all remaining global popup components moved into a separate Popups
entity
- removed some static objects from the Global singleton (appMain,
pinnedMessagesPopup, communityProfilePopup, sounds); rationale:
singletons should not contain any state
- fixed support for popups in storybook
- fixed some warnings (most of them broke the popups in one way or the other)
This commit is contained in:
Lukáš Tinkl 2023-02-07 15:21:32 +01:00 committed by Lukáš Tinkl
parent 1481790183
commit f5d6c538c2
28 changed files with 376 additions and 404 deletions

View File

@ -21,6 +21,8 @@ SplitView {
contentWidth: parent.width
store: QtObject {
readonly property bool isProduction: false
function checkForUpdates() {
logs.logEvent("store::checkForUpdates")
}

View File

@ -10,10 +10,17 @@ import Storybook 1.0
import Models 1.0
import utils 1.0
import mainui 1.0
SplitView {
id: root
Logs { id: logs }
Popups {
popupParent: root
rootStore: QtObject {}
}
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
@ -40,16 +47,6 @@ SplitView {
}
}
// TODO: onCompleted handler and localAccountSensitiveSettings are here to allow opening
// "Import Community" and "Create New Community" popups. However those popups shouldn't
// be tightly coupled with `CommunitiesPortalLayout` so it should be refactored in the next step.
// Pressing buttons "Import using key" and "Create new community" should only request for opening
// dialogs, and in Storybook it should be logged in the same way as calls to stores.
// Mentioned popups should have their own pages in the Storybook.
Component.onCompleted: {
Global.appMain = this
}
QtObject {
id: localAccountSensitiveSettings
readonly property bool isDiscordImportToolEnabled: false

View File

@ -6,6 +6,9 @@ import Storybook 1.0
import utils 1.0
import shared.views 1.0
import mainui 1.0
import StatusQ 0.1
SplitView {
id: root
@ -91,6 +94,11 @@ SplitView {
Logs { id: logs }
Popups {
popupParent: root
rootStore: QtObject {}
}
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
@ -114,10 +122,6 @@ SplitView {
publicKey: switchOwnProfile.checked ? "0xdeadbeef" : "0xrandomguy"
Component.onCompleted: {
Global.appMain = root // FIXME this is here for the popups to work
}
profileStore: QtObject {
readonly property string pubkey: "0xdeadbeef"
readonly property string ensName: name.text
@ -286,7 +290,11 @@ SplitView {
TextField {
Layout.fillWidth: true
id: bio
text: "Hello from MockMainModule, I am a mock user and this is my bio."
text: "Hi, I am Alex. I'm an indie developer who mainly works on web products.
I worked for several different companies and created a couple of my own products from scratch. Currently building Telescope and Prepacked.
Say hi, or find me on Twitter, GitHub, or Mastodon."
}
}
}

View File

@ -9,8 +9,6 @@ Feature: User Identity
And the user signs up with username "tester123" and password "TesTEr16843/!@00"
And the user lands on the signed in app
@mayfail
# FIXME test is broken on step `the user's social links are empty`. Issue #9499
Scenario Outline: The user sets display name, bio and social links
Given the user opens app settings screen
And the user opens the profile settings

View File

@ -2,7 +2,6 @@ import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
StatusValidator {
name: "url"
errorMessage: qsTr("Please enter a valid URL")
@ -11,5 +10,3 @@ StatusValidator {
return Utils.isURL(value);
}
}

View File

@ -1,9 +1,6 @@
import QtQuick 2.13
import StatusQ.Controls 0.1
import QtQuick 2.14
QtObject {
id: statusValidator
property string name: ""
property string errorMessage: qsTr("invalid input")
property var validatorObj

View File

@ -3,6 +3,7 @@
#include "StatusQ/QClipboardProxy.h"
#include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h"
#include "StatusQ/rxvalidator.h"
#include <QQmlEngine>
@ -12,4 +13,5 @@ void registerStatusQTypes()
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0 , 1, "QClipboardProxy",
&QClipboardProxy::qmlInstance);
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0 , 1, "StatusSyntaxHighlighter");
qmlRegisterType<RXValidator>("StatusQ", 0 , 1, "RXValidator");
}

View File

@ -79,8 +79,6 @@ ModalPopup {
id: urlInput
anchors.left: parent.left
anchors.right: parent.right
leftPadding: 0
rightPadding: 0
label: qsTr("URL")
input.text: ogUrl
placeholderText: qsTr("Paste URL")

View File

@ -61,16 +61,12 @@ StackLayout {
hasAddedContacts: root.contactsStore.myContactsModel.count > 0
chatCommunitySectionModule: root.rootStore.chatCommunitySectionModule
community: root.rootStore.mainModuleInst ? root.rootStore.mainModuleInst.activeSection
|| {} : {}
|| ({}) : ({})
onBackToCommunityClicked: root.currentIndex = 0
onBackToCommunityClicked: root.currentIndex = 0
// TODO: remove me when migration to new settings is done
onOpenLegacyPopupClicked: Global.openPopup(Global.communityProfilePopup, {
"store": root.rootStore,
"community": community,
"communitySectionModule": chatCommunitySectionModule
})
// TODO: remove me when migration to new settings is done
onOpenLegacyPopupClicked: Global.openCommunityProfilePopupRequested(root.rootStore, community, chatCommunitySectionModule)
}
}
}

View File

@ -1,7 +1,6 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import utils 1.0
import shared.controls 1.0
@ -18,8 +17,8 @@ StatusModal {
property var store
property var contactsStore
property string commandTitle: "Send"
property string finalButtonLabel: "Request address"
property string commandTitle: qsTr("Send")
property string finalButtonLabel: qsTr("Request address")
property var sendChatCommand: function () {}
property bool isRequested: false

View File

@ -37,7 +37,7 @@ QtObject {
// Contact requests related part
property var contactRequestsModel: chatCommunitySectionModule.contactRequestsModel
property var loadingHistoryMessagesInProgress: chatCommunitySectionModule.loadingHistoryMessagesInProgress
property bool loadingHistoryMessagesInProgress: chatCommunitySectionModule.loadingHistoryMessagesInProgress
property var advancedModule: profileSectionModule.advancedModule

View File

@ -99,12 +99,7 @@ ColumnLayout {
console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set")
return
}
Global.openPopup(Global.pinnedMessagesPopup, {
store: rootStore,
messageStore: messageStore,
pinnedMessagesModel: chatContentModule.pinnedMessagesModel,
messageToPin: messageId
})
Global.openPinnedMessagesPopupRequested(rootStore, messageStore, chatContentModule.pinnedMessagesModel, messageId)
}
onToggleReaction: {
@ -243,8 +238,7 @@ ColumnLayout {
chatInput.fileUrlsAndSources
))
{
Global.sendMessageSound.stop();
Qt.callLater(Global.sendMessageSound.play);
Global.playSendMessageSound()
chatInput.textInput.clear();
chatInput.textInput.textFormat = TextEdit.PlainText;

View File

@ -309,12 +309,7 @@ Item {
console.warn("error on open pinned messages - chat content module is not set")
return
}
Global.openPopup(Global.pinnedMessagesPopup, {
store: rootStore,
messageStore: messageStore,
pinnedMessagesModel: chatContentModule.pinnedMessagesModel,
messageToPin: ""
})
Global.openPinnedMessagesPopupRequested(rootStore, messageStore, chatContentModule.pinnedMessagesModel, "")
}
onUnmute: {
if(!chatContentModule) {

View File

@ -66,6 +66,7 @@ StatusDialog {
StatusScrollView {
id: scrollView
implicitHeight: contentHeight + topPadding + bottomPadding
anchors.fill: parent
padding: 0

View File

@ -13,7 +13,6 @@ QtObject {
property bool isTelemetryEnabled: advancedModule? advancedModule.isTelemetryEnabled : false
property bool isAutoMessageEnabled: advancedModule? advancedModule.isAutoMessageEnabled : false
property bool isDebugEnabled: advancedModule? advancedModule.isDebugEnabled : false
property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false
property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false
property var customNetworksModel: advancedModule? advancedModule.customNetworksModel : []

View File

@ -471,8 +471,7 @@ SettingsContentBase {
value = appSettings.volume
volumeSlider.valueChanged.connect(() => {
// play a sound preview, but not on startup
Global.notificationSound.stop()
Global.notificationSound.play()
Global.playNotificationSound()
});
}
}

View File

@ -4,17 +4,14 @@ import QtQuick.Layouts 1.13
import QtMultimedia 5.13
import Qt.labs.qmlmodels 1.0
import Qt.labs.platform 1.1
import Qt.labs.settings 1.0
import QtQml.Models 2.14
import AppLayouts.Wallet 1.0
import AppLayouts.Node 1.0
import AppLayouts.Browser 1.0
import AppLayouts.Chat 1.0
import AppLayouts.Chat.popups 1.0
import AppLayouts.Chat.views 1.0
import AppLayouts.Profile 1.0
import AppLayouts.Profile.popups 1.0
import AppLayouts.CommunitiesPortal 1.0
import utils 1.0
@ -51,15 +48,11 @@ Item {
// set from main.qml
property var sysPalette
property var activePopupComponents: []
signal closeProfilePopup()
Connections {
target: rootStore.mainModuleInst
function onDisplayUserProfile(publicKey: string) {
Global.openProfilePopup(publicKey)
popups.openProfilePopup(publicKey)
}
function onDisplayKeycardSharedModuleFlow() {
@ -83,47 +76,24 @@ Item {
}
function onOpenActivityCenter() {
Global.openPopup(activityCenterPopupComponent)
popups.openPopup(activityCenterPopupComponent)
}
}
Popups {
id: popups
popupParent: appMain
rootStore: appMain.rootStore
Component.onCompleted: {
Global.openSendIDRequestPopup.connect(openSendIDRequestPopup)
Global.openOutgoingIDRequestPopup.connect(openOutgoingIDRequestPopup)
Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup)
Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup)
Global.openContactRequestPopup.connect(openContactRequestPopup)
}
}
Connections {
id: globalConns
target: Global
function onOpenLinkInBrowser(link: string) {
changeAppSectionBySectionId(Constants.appSection.browser)
Qt.callLater(() => browserLayoutContainer.item.openUrlInNewTab(link));
}
function onOpenChooseBrowserPopup(link: string) {
Global.openPopup(chooseBrowserPopupComponent, {link: link});
}
function onOpenDownloadModalRequested(available: bool, version: string, url: string) {
const downloadPage = downloadPageComponent.createObject(appMain,
{
newVersionAvailable: available,
downloadURL: url,
currentVersion: appMain.rootStore.profileSectionStore.getCurrentVersion(),
newVersion: version
})
return downloadPage
}
function onOpenImagePopup(image, contextMenu) {
var popup = imagePopupComponent.createObject(appMain)
popup.contextMenu = contextMenu
popup.openPopup(image)
}
function onOpenCreateChatView() {
createChatView.opened = true
@ -133,76 +103,44 @@ Item {
createChatView.opened = false
}
function onOpenProfilePopupRequested(publicKey: string, parentPopup) {
if (Global.profilePopupOpened) {
appMain.closeProfilePopup()
}
Global.openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup})
Global.profilePopupOpened = true
}
function onOpenNicknamePopupRequested(publicKey: string,nickname: string, subtitle: string) {
Global.openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle})
}
function onBlockContactRequested(publicKey: string, contactName: string) {
Global.openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
}
function onUnblockContactRequested(publicKey: string, contactName: string) {
Global.openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
function onOpenActivityCenterPopupRequested() {
popups.openPopup(activityCenterPopupComponent)
}
function onOpenActivityCenterPopupRequested(publicKey: string, contactName: string) {
Global.openPopup(activityCenterPopupComponent)
}
function onOpenChangeProfilePicPopup(cb) {
var popup = changeProfilePicComponent.createObject(appMain, {callback: cb});
popup.chooseImageToCrop();
}
function onOpenBackUpSeedPopup() {
Global.openPopup(backupSeedModalComponent)
}
function onDisplayToastMessage(title: string, subTitle: string, icon: string, loading: bool, ephNotifType: int, url: string) {
appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url);
}
function onOpenEditDisplayNamePopup() {
Global.openPopup(displayNamePopupComponent)
}
function onOpenPopupRequested(popupComponent, params) {
if (activePopupComponents.includes(popupComponent)) {
return;
}
const popup = popupComponent.createObject(appMain, params);
popup.open();
activePopupComponents.push(popupComponent);
popup.closed.connect(() => {
const removeIndex = activePopupComponents.indexOf(popupComponent);
if (removeIndex !== -1) {
activePopupComponents.splice(removeIndex, 1);
}
})
return popup;
appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url)
}
function onOpenLink(link: string) {
// Qt sometimes inserts random HTML tags; and this will break on invalid URL inside QDesktopServices::openUrl(link)
link = appMain.rootStore.plainText(link);
link = appMain.rootStore.plainText(link)
if (appMain.rootStore.showBrowserSelector) {
Global.openChooseBrowserPopup(link);
popups.openChooseBrowserPopup(link)
} else {
if (appMain.rootStore.openLinksInStatus) {
Global.changeAppSectionBySectionType(Constants.appSection.browser);
Global.openLinkInBrowser(link);
globalConns.onAppSectionBySectionTypeChanged(Constants.appSection.browser)
globalConns.onOpenLinkInBrowser(link)
} else {
Qt.openUrlExternally(link);
Qt.openUrlExternally(link)
}
}
}
function onPlaySendMessageSound() {
sendMessageSound.stop()
sendMessageSound.play()
}
function onPlayNotificationSound() {
notificationSound.stop()
notificationSound.play()
}
function onPlayErrorSound() {
errorSound.stop()
errorSound.play()
}
function onSetNthEnabledSectionActive(nthSection: int) {
if(!appMain.rootStore.mainModuleInst)
return
@ -224,124 +162,22 @@ Item {
appMain.rootStore.mainModuleInst.setActiveSectionById(sectionId)
}
Component {
id: backupSeedModalComponent
BackupSeedModal {
anchors.centerIn: parent
privacyStore: appMain.rootStore.profileSectionStore.privacyStore
onClosed: destroy()
}
}
Component {
id: displayNamePopupComponent
DisplayNamePopup {
anchors.centerIn: parent
profileStore: appMain.rootStore.profileSectionStore.profileStore
onClosed: {
destroy()
}
}
}
Component {
id: downloadPageComponent
DownloadPage {
onClosed: {
destroy();
}
}
}
Component {
id: imagePopupComponent
StatusImageModal {
id: imagePopup
onClicked: {
if (mouse.button === Qt.LeftButton) {
imagePopup.close()
} else if(mouse.button === Qt.RightButton) {
contextMenu.imageSource = imagePopup.imageSource
contextMenu.hideEmojiPicker = true
contextMenu.isRightClickOnImage = true
contextMenu.parent = imagePopup.contentItem
contextMenu.show()
}
}
onClosed: destroy()
}
}
Component {
id: profilePopupComponent
ProfileDialog {
id: profilePopup
profileStore: appMain.rootStore.profileSectionStore.profileStore
contactsStore: appMain.rootStore.profileSectionStore.contactsStore
onClosed: {
if (profilePopup.parentPopup) {
profilePopup.parentPopup.close()
}
Global.profilePopupOpened = false
destroy()
}
Component.onCompleted: {
appMain.closeProfilePopup.connect(profilePopup.close)
}
}
}
Component {
id: changeProfilePicComponent
ImageCropWorkflow {
title: qsTr("Profile Picture")
acceptButtonText: qsTr("Make this my Profile Pic")
onImageCropped: {
if (callback) {
callback(image,
cropRect.x.toFixed(),
cropRect.y.toFixed(),
(cropRect.x + cropRect.width).toFixed(),
(cropRect.y + cropRect.height).toFixed())
return
}
appMain.rootStore.profileSectionStore.profileStore.uploadImage(image,
cropRect.x.toFixed(),
cropRect.y.toFixed(),
(cropRect.x + cropRect.width).toFixed(),
(cropRect.y + cropRect.height).toFixed());
}
}
}
Audio {
id: sendMessageSound
store: rootStore
source: "qrc:/imports/assets/audio/send_message.wav"
Component.onCompleted: {
Global.sendMessageSound = this;
}
}
Audio {
id: notificationSound
store: rootStore
source: "qrc:/imports/assets/audio/notification.wav"
Component.onCompleted: {
Global.notificationSound = this;
}
}
Audio {
id: errorSound
source: "qrc:/imports/assets/audio/error.mp3"
store: rootStore
Component.onCompleted: {
Global.errorSound = this;
}
}
Loader {
@ -452,7 +288,7 @@ Item {
icon.name: "share-ios"
enabled: model.canManageUsers
onTriggered: {
Global.openInviteFriendsToCommunityPopup(model,
popups.openInviteFriendsToCommunityPopup(model,
communityContextMenu.chatCommunitySectionModule,
null)
}
@ -461,11 +297,7 @@ Item {
StatusAction {
text: qsTr("View Community")
icon.name: "group-chat"
onTriggered: Global.openPopup(communityProfilePopup, {
store: appMain.rootStore,
community: model,
communitySectionModule: communityContextMenu.chatCommunitySectionModule
})
onTriggered: popups.openCommunityProfilePopup(appMain.rootStore, model, communityContextMenu.chatCommunitySectionModule)
}
StatusMenuSeparator {}
@ -670,9 +502,7 @@ Item {
text: qsTr("Secure your seed phrase")
buttonText: qsTr("Back up now")
onClicked: {
Global.openBackUpSeedPopup();
}
onClicked: popups.openBackUpSeedPopup()
onCloseClicked: {
appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner = true
@ -719,7 +549,7 @@ Item {
return ""
}
onLinkActivated: Global.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup)
onLinkActivated: popups.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup)
progressValue: progress
closeBtnVisible: finished || stopped
buttonText: finished && !errors ? qsTr("Visit your Community") : ""
@ -880,6 +710,8 @@ Item {
Loader {
id: personalChatLayoutLoader
asynchronous: true
active: appView.currentIndex === Constants.appViewStackIndex.chat
sourceComponent: {
if (appMain.rootStore.mainModuleInst.chatsLoadingFailed) {
return errorStateComponent
@ -939,11 +771,11 @@ Item {
}
onImportCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup);
popups.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup);
}
onCreateCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup);
popups.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup);
}
Component.onCompleted: {
@ -1075,51 +907,9 @@ Item {
}
} // ColumnLayout
Component {
id: chooseBrowserPopupComponent
ChooseBrowserPopup {
onClosed: {
destroy()
}
}
}
Component {
id: communityProfilePopup
CommunityProfilePopup {
anchors.centerIn: parent
contactsStore: appMain.rootStore.contactStore
hasAddedContacts: appMain.rootStore.hasAddedContacts
onClosed: {
destroy()
}
}
}
Component {
id: pinnedMessagesPopupComponent
PinnedMessagesPopup {
id: pinnedMessagesPopup
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
onClosed: destroy()
}
}
Component {
id: genericConfirmationDialog
ConfirmationDialog {
onClosed: {
destroy()
}
}
}
Component {
id: activityCenterPopupComponent
ActivityCenterPopup {
id: activityCenter
// TODO get screen size // Taken from old code top bar height was fixed there to 56
property int _buttonSize: 56
@ -1131,41 +921,6 @@ Item {
}
}
Component {
id: nicknamePopupComponent
NicknamePopup {
onEditDone: {
if (nickname !== newNickname) {
appMain.rootStore.contactStore.changeContactNickname(publicKey, newNickname)
}
close()
}
onClosed: destroy()
}
}
Component {
id: unblockContactConfirmationComponent
UnblockContactConfirmationDialog {
onUnblockButtonClicked: {
appMain.rootStore.contactStore.unblockContact(contactAddress)
close()
}
onClosed: destroy()
}
}
Component {
id: blockContactConfirmationComponent
BlockContactConfirmationDialog {
onBlockButtonClicked: {
appMain.rootStore.contactStore.blockContact(contactAddress)
close()
}
onClosed: destroy()
}
}
// Add SendModal here as it is used by the Wallet as well as the Browser
Loader {
id: sendModal
@ -1354,9 +1109,6 @@ Item {
}
Component.onCompleted: {
Global.appMain = this;
Global.pinnedMessagesPopup = pinnedMessagesPopupComponent;
Global.communityProfilePopup = communityProfilePopup;
const whitelist = appMain.rootStore.messagingStore.getLinkPreviewWhitelist()
try {
const whiteListedSites = JSON.parse(whitelist)

View File

@ -1,18 +1,119 @@
import QtQuick 2.14
import QtQuick 2.15
import AppLayouts.Chat.popups 1.0
import AppLayouts.Profile.popups 1.0
import shared.popups 1.0
import shared.status 1.0
import utils 1.0
QtObject {
id: root
/* required */ property var rootStore
required property var popupParent
required property var rootStore
property var activePopupComponents: []
Component.onCompleted: {
Global.openSendIDRequestPopup.connect(openSendIDRequestPopup)
Global.openOutgoingIDRequestPopup.connect(openOutgoingIDRequestPopup)
Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup)
Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup)
Global.openContactRequestPopup.connect(openContactRequestPopup)
Global.openChooseBrowserPopup.connect(openChooseBrowserPopup)
Global.openDownloadModalRequested.connect(openDownloadModal)
Global.openImagePopup.connect(openImagePopup)
Global.openProfilePopupRequested.connect(openProfilePopup)
Global.openNicknamePopupRequested.connect(openNicknamePopup)
Global.blockContactRequested.connect(openBlockContactPopup)
Global.unblockContactRequested.connect(openUnblockContactPopup)
Global.openChangeProfilePicPopup.connect(openChangeProfilePicPopup)
Global.openBackUpSeedPopup.connect(openBackUpSeedPopup)
Global.openEditDisplayNamePopup.connect(openEditDisplayNamePopup)
Global.openPinnedMessagesPopupRequested.connect(openPinnedMessagesPopup)
Global.openCommunityProfilePopupRequested.connect(openCommunityProfilePopup)
Global.openPopupRequested.connect(openPopup)
}
function openPopup(popupComponent, params = {}, cb = null) {
if (activePopupComponents.includes(popupComponent)) {
return
}
const popup = popupComponent.createObject(popupParent, params)
popup.open()
if (cb)
cb(popup)
activePopupComponents.push(popupComponent)
popup.closed.connect(() => {
const removeIndex = activePopupComponents.indexOf(popupComponent)
if (removeIndex !== -1) {
activePopupComponents.splice(removeIndex, 1)
}
})
}
function openChooseBrowserPopup(link: string) {
openPopup(chooseBrowserPopupComponent, {link: link})
}
function openDownloadModal(available: bool, version: string, url: string) {
const popupProperties = {
newVersionAvailable: available,
downloadURL: url,
currentVersion: rootStore.profileSectionStore.getCurrentVersion(),
newVersion: version
}
openPopup(downloadPageComponent, popupProperties)
}
function openImagePopup(image, contextMenu) {
var popup = imagePopupComponent.createObject(popupParent)
popup.contextMenu = contextMenu
popup.openPopup(image)
}
function openProfilePopup(publicKey: string, parentPopup) {
openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup})
}
function openNicknamePopup(publicKey: string, nickname: string, subtitle: string) {
openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle})
}
function openBlockContactPopup(publicKey: string, contactName: string) {
openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
}
function openUnblockContactPopup(publicKey: string, contactName: string) {
openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
}
function openChangeProfilePicPopup(cb) {
var popup = changeProfilePicComponent.createObject(popupParent, {callback: cb});
popup.chooseImageToCrop()
}
function openBackUpSeedPopup() {
openPopup(backupSeedModalComponent)
}
function openEditDisplayNamePopup() {
openPopup(displayNamePopupComponent)
}
function openCommunityProfilePopup(store, community, communitySectionModule) {
openPopup(communityProfilePopup, { store: store, community: community, communitySectionModule: communitySectionModule})
}
function openSendIDRequestPopup(publicKey, cb) {
const contactDetails = Utils.getContactDetailsAsJson(publicKey, false)
const popup = Global.openPopup(sendIDRequestPopupComponent, {
openPopup(sendIDRequestPopupComponent, {
userPublicKey: publicKey,
userDisplayName: contactDetails.displayName,
userIcon: contactDetails.largeImage,
@ -20,9 +121,7 @@ QtObject {
"header.title": qsTr("Verify %1's Identity").arg(contactDetails.displayName),
challengeText: qsTr("Ask a question that only the real %1 will be able to answer e.g. a question about a shared experience, or ask %1 to enter a code or phrase you have sent to them via a different communication channel (phone, post, etc...).").arg(contactDetails.displayName),
buttonText: qsTr("Send verification request")
})
if (cb)
cb(popup)
}, cb)
}
function openOutgoingIDRequestPopup(publicKey, cb) {
@ -38,9 +137,7 @@ QtObject {
verificationRequestedAt: verificationDetails.requestedAt,
verificationRepliedAt: verificationDetails.repliedAt
}
const popup = Global.openPopup(contactOutgoingVerificationRequestPopupComponent, popupProperties)
if (cb)
cb(popup)
openPopup(contactOutgoingVerificationRequestPopupComponent, popupProperties, cb)
} catch (e) {
console.error("Error getting or parsing verification data", e)
}
@ -52,16 +149,11 @@ QtObject {
publicKey: publicKey
}
const popup = Global.openPopup(contactVerificationRequestPopupComponent, popupProperties)
if (cb) {
cb(popup)
}
openPopup(contactVerificationRequestPopupComponent, popupProperties, cb)
}
function openInviteFriendsToCommunityPopup(community, communitySectionModule, cb) {
const popup = Global.openPopup(inviteFriendsToCommunityPopup, { community, communitySectionModule })
if (cb)
cb(popup)
openPopup(inviteFriendsToCommunityPopup, { community: community, communitySectionModule: communitySectionModule }, cb)
}
function openContactRequestPopup(publicKey, cb) {
@ -73,12 +165,19 @@ QtObject {
userIsEnsVerified: contactDetails.ensVerified
}
const popup = Global.openPopup(sendContactRequestPopupComponent, popupProperties)
if (cb)
cb(popup)
openPopup(sendContactRequestPopupComponent, popupProperties, cb)
}
readonly property list<Component> _d: [
function openPinnedMessagesPopup(store, messageStore, pinnedMessagesModel, messageToPin) {
openPopup(pinnedMessagesPopup, { store: store, messageStore: messageStore,
pinnedMessagesModel: pinnedMessagesModel, messageToPin: messageToPin})
}
function openCommunityPopup(store, community, chatCommunitySectionModule) {
openPopup(communityProfilePopup, {store: store, community: community, chatCommunitySectionModule: chatCommunitySectionModule})
}
readonly property list<Component> _components: [
Component {
id: contactVerificationRequestPopupComponent
ContactVerificationRequestPopup {
@ -136,6 +235,153 @@ QtObject {
onAccepted: root.rootStore.profileSectionStore.contactsStore.sendContactRequest(userPublicKey, message)
onClosed: destroy()
}
},
Component {
id: backupSeedModalComponent
BackupSeedModal {
anchors.centerIn: parent
privacyStore: rootStore.profileSectionStore.privacyStore
onClosed: destroy()
}
},
Component {
id: displayNamePopupComponent
DisplayNamePopup {
anchors.centerIn: parent
profileStore: rootStore.profileSectionStore.profileStore
onClosed: destroy()
}
},
Component {
id: downloadPageComponent
DownloadPage {
onClosed: destroy()
}
},
Component {
id: imagePopupComponent
StatusImageModal {
id: imagePopup
onClicked: {
if (mouse.button === Qt.LeftButton) {
imagePopup.close()
} else if(mouse.button === Qt.RightButton) {
contextMenu.imageSource = imagePopup.imageSource
contextMenu.hideEmojiPicker = true
contextMenu.isRightClickOnImage = true
contextMenu.parent = imagePopup.contentItem
contextMenu.show()
}
}
onClosed: destroy()
}
},
Component {
id: profilePopupComponent
ProfileDialog {
id: profilePopup
profileStore: rootStore.profileSectionStore.profileStore
contactsStore: rootStore.profileSectionStore.contactsStore
onClosed: {
if (profilePopup.parentPopup) {
profilePopup.parentPopup.close()
}
destroy()
}
}
},
Component {
id: changeProfilePicComponent
ImageCropWorkflow {
title: qsTr("Profile Picture")
acceptButtonText: qsTr("Make this my Profile Pic")
onImageCropped: {
if (callback) {
callback(image,
cropRect.x.toFixed(),
cropRect.y.toFixed(),
(cropRect.x + cropRect.width).toFixed(),
(cropRect.y + cropRect.height).toFixed())
return
}
rootStore.profileSectionStore.profileStore.uploadImage(image,
cropRect.x.toFixed(),
cropRect.y.toFixed(),
(cropRect.x + cropRect.width).toFixed(),
(cropRect.y + cropRect.height).toFixed());
}
onDone: destroy()
}
},
Component {
id: chooseBrowserPopupComponent
ChooseBrowserPopup {
onClosed: destroy()
}
},
Component {
id: communityProfilePopup
CommunityProfilePopup {
anchors.centerIn: parent
contactsStore: rootStore.contactStore
hasAddedContacts: rootStore.hasAddedContacts
onClosed: destroy()
}
},
Component {
id: pinnedMessagesPopup
PinnedMessagesPopup {
emojiReactionsModel: rootStore.emojiReactionsModel
onClosed: destroy()
}
},
Component {
id: nicknamePopupComponent
NicknamePopup {
onEditDone: {
if (nickname !== newNickname) {
rootStore.contactStore.changeContactNickname(publicKey, newNickname)
}
close()
}
onClosed: destroy()
}
},
Component {
id: unblockContactConfirmationComponent
UnblockContactConfirmationDialog {
onUnblockButtonClicked: {
rootStore.contactStore.unblockContact(contactAddress)
close()
}
onClosed: destroy()
}
},
Component {
id: blockContactConfirmationComponent
BlockContactConfirmationDialog {
onBlockButtonClicked: {
rootStore.contactStore.blockContact(contactAddress)
close()
}
onClosed: destroy()
}
}
]
}

View File

@ -1,2 +1,3 @@
AppMain 1.0 AppMain.qml
SplashScreen 1.0 SplashScreen.qml
Popups 1.0 Popups.qml

View File

@ -23,6 +23,7 @@ Item {
property bool roundedImage: true
signal imageCropped(var image, var cropRect)
signal done()
function chooseImageToCrop() {
fileDialog.open()
@ -87,6 +88,7 @@ Item {
}
}
]
onClosed: root.done()
} // StatusModal
} // Item

View File

@ -1,5 +1,4 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick 2.14
import utils 1.0
import shared.controls 1.0
@ -61,11 +60,9 @@ StatusModal {
charLimit: maxNicknameLength
validationMode: StatusInput.ValidationMode.IgnoreInvalidInput
validators: [
StatusRegularExpressionValidator {
StatusValidator {
validatorObj: RXValidator { regularExpression: /^[\w\d_ -]*$/u }
validate: function (value) {
return validatorObj.test(value)
}
validate: (value) => validatorObj.test(value)
}
]
Keys.onReleased: {

View File

@ -129,12 +129,10 @@ Rectangle {
StatusBaseText {
Layout.alignment: Qt.AlignVCenter
text: {
if (Global.appMain) {
return "%1%2".arg(SharedStore.RootStore.currencyStore.currentCurrencySymbol)
.arg(Utils.toLocaleString(parseFloat(model.account.balance.amount).toFixed(2), appSettings.locale, {"model.account.currency": true}))
}
// without language/model refactor no way to read currency symbol or `appSettings.locale` before user logs in
return "$%1".arg(Utils.toLocaleString(parseFloat(model.account.balance.amount).toFixed(2), localAppSettings.language, {"model.account.currency": true}))
return LocaleUtils.currencyAmountToLocaleString({
amount: parseFloat(model.account.balance.amount),
symbol: SharedStore.RootStore.currencyStore.currentCurrencySymbol,
displayDecimals: 2})
}
wrapMode: Text.WordWrap
font.pixelSize: Constants.keycard.general.fontSize2

View File

@ -161,12 +161,10 @@ Item {
ColumnLayout {
StatusBaseText {
text: {
if (Global.appMain) {
return "Balance: %1%2".arg(SharedStore.RootStore.currencyStore.currentCurrencySymbol)
.arg(Utils.toLocaleString(root.sharedKeycardModule.keyPairHelper.observedAccount.balance.toFixed(2), appSettings.locale, {"model.account.currency": true}))
}
// without language/model refactor no way to read currency symbol or `appSettings.locale` before user logs in
return "Balance: $%1".arg(Utils.toLocaleString(root.sharedKeycardModule.keyPairHelper.observedAccount.balance.toFixed(2), localAppSettings.language, {"model.account.currency": true}))
return qsTr("Balance: %1").arg(LocaleUtils.currencyAmountToLocaleString({
amount: parseFloat(root.sharedKeycardModule.keyPairHelper.observedAccount.balance),
symbol: SharedStore.RootStore.currencyStore.currentCurrencySymbol,
displayDecimals: 2}))
}
wrapMode: Text.WordWrap
font.pixelSize: Constants.keycard.general.fontSize2

View File

@ -4,3 +4,4 @@ TransactionStore 1.0 TransactionStore.qml
BIP39_en 1.0 BIP39_en.qml
TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml
TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml
ChartStoreBase 1.0 ChartStoreBase.qml

View File

@ -104,6 +104,15 @@ Pane {
}
}
readonly property var conns3: Connections {
target: root.contactsStore.sentContactRequestsModel
function onItemChanged(pubKey) {
if (pubKey === root.publicKey)
d.reload()
}
}
readonly property var timer: Timer {
id: timer
}
@ -171,8 +180,7 @@ Pane {
size: StatusButton.Size.Small
text: qsTr("Send Contact Request")
onClicked: {
Global.openContactRequestPopup(root.publicKey,
popup => popup.accepted.connect(d.reload))
Global.openContactRequestPopup(root.publicKey, null)
}
}
}
@ -395,8 +403,7 @@ Pane {
d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise
onTriggered: {
moreMenu.close()
Global.openContactRequestPopup(root.publicKey,
popup => popup.closed.connect(d.reload))
Global.openContactRequestPopup(root.publicKey, null)
}
}
StatusAction {

View File

@ -365,14 +365,14 @@ StatusMenu {
root.store.copyToClipboard(root.unparsedText)
close()
}
enabled: root.messageContentType === Constants.messageContentType.messageType && !root.isProfile && !emojiContainer.visible
enabled: root.messageContentType === Constants.messageContentType.messageType && replyToMenuItem.enabled
}
StatusAction {
id: copyMessageIdAction
text: qsTr("Copy Message Id")
icon.name: "copy"
enabled: root.isDebugEnabled && !root.isProfile && !root.pinnedPopup && !emojiContainer.visible
enabled: root.isDebugEnabled && replyToMenuItem.enabled
onTriggered: {
root.store.copyToClipboard(root.messageId)
close()

View File

@ -467,7 +467,6 @@ Loader {
disableHover: root.disableHover ||
(root.chatLogView && root.chatLogView.moving) ||
(root.messageContextMenu && root.messageContextMenu.opened) ||
Global.profilePopupOpened ||
Global.popupOpened
hideQuickActions: root.isChatBlocked ||
@ -816,12 +815,7 @@ Loader {
return;
}
Global.openPopup(Global.pinnedMessagesPopup, {
store: root.rootStore,
messageStore: messageStore,
pinnedMessagesModel: chatContentModule.pinnedMessagesModel,
messageToPin: root.messageId
});
Global.openPinnedMessagesPopupRequested(root.rootStore, messageStore, chatContentModule.pinnedMessagesModel, root.messageId)
}
}
},

View File

@ -6,19 +6,14 @@ QtObject {
id: root
property var dragArea
property var appMain
property var applicationWindow
property bool popupOpened: false
property int settingsSubsection: Constants.settingsSubsection.profile
property var userProfile
property var pinnedMessagesPopup
property var communityProfilePopup
property bool profilePopupOpened: false
property var sendMessageSound
property var notificationSound
property var errorSound
signal openPinnedMessagesPopupRequested(var store, var messageStore, var pinnedMessagesModel, string messageToPin)
signal openCommunityProfilePopupRequested(var store, var community, var chatCommunitySectionModule)
signal openLinkInBrowser(string link)
signal openChooseBrowserPopup(string link)
@ -31,7 +26,7 @@ QtObject {
signal displayToastMessage(string title, string subTitle, string icon, bool loading, int ephNotifType, string url)
signal openPopupRequested(var popupComponent, var params);
signal openPopupRequested(var popupComponent, var params)
signal openNicknamePopupRequested(string publicKey, string nickname, string subtitle)
signal openDownloadModalRequested(bool available, string version, string url)
signal openChangeProfilePicPopup(var cb)
@ -51,6 +46,10 @@ QtObject {
signal setNthEnabledSectionActive(int nthSection)
signal appSectionBySectionTypeChanged(int sectionType, int subsection)
signal playSendMessageSound()
signal playNotificationSound()
signal playErrorSound()
function openProfilePopup(publicKey, parentPopup) {
root.openProfilePopupRequested(publicKey, parentPopup)
}
@ -70,9 +69,4 @@ QtObject {
function changeAppSectionBySectionType(sectionType, subsection = 0) {
root.appSectionBySectionTypeChanged(sectionType, subsection);
}
function playErrorSound() {
if (root.errorSound)
root.errorSound.play();
}
}