import QtQuick 2.13 import QtQuick.Controls 2.13 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 import shared 1.0 import shared.controls 1.0 import shared.panels 1.0 import shared.popups 1.0 import shared.popups.keycard 1.0 import shared.status 1.0 import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 import StatusQ.Controls 0.1 import StatusQ.Layout 0.1 import StatusQ.Popups 0.1 import StatusQ.Popups.Dialog 0.1 import StatusQ.Core 0.1 import AppLayouts.Browser.stores 1.0 as BrowserStores import AppLayouts.stores 1.0 import "popups" import "panels" import "activitycenter/popups" import "activitycenter/stores" as AC Item { id: appMain anchors.fill: parent property alias appLayout: appLayout property RootStore rootStore: RootStore {} property AC.RootStore acStore: AC.RootStore {} // set from main.qml property var sysPalette Connections { target: rootStore.mainModuleInst onDisplayUserProfile: Global.openProfilePopup(publicKey) onDisplayKeycardSharedModuleFlow: { keycardPopup.active = true } onDestroyKeycardSharedModuleFlow: { keycardPopup.active = false } } Connections { target: Global onOpenLinkInBrowser: { browserLayoutContainer.item.openUrlInNewTab(link); } onOpenChooseBrowserPopup: { Global.openPopup(chooseBrowserPopupComponent, {link: link}); } onOpenDownloadModalRequested: { const downloadPage = downloadPageComponent.createObject(appMain, { newVersionAvailable: available, downloadURL: url, currentVersion: rootStore.profileSectionStore.getCurrentVersion(), newVersion: version }) return downloadPage } onOpenProfilePopupRequested: { var popup = profilePopupComponent.createObject(appMain); if (parentPopup) { popup.parentPopup = parentPopup; } popup.openPopup(publicKey, state); Global.profilePopupOpened = true; } onOpenActivityCenterPopupRequested: { Global.openPopup(activityCenterPopupComponent) Global.activityCenterPopupOpened = true } onOpenChangeProfilePicPopup: { var popup = changeProfilePicComponent.createObject(appMain); popup.chooseImageToCrop(); } onOpenBackUpSeedPopup: Global.openPopup(backupSeedModalComponent) onDisplayToastMessage: { appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url); } onOpenEditDisplayNamePopup: { var popup = displayNamePopupComponent.createObject(appMain) popup.open() } } function changeAppSectionBySectionId(sectionId) { mainModule.setActiveSectionById(sectionId) } property Component backupSeedModalComponent: BackupSeedModal { id: backupSeedModal anchors.centerIn: parent privacyStore: appMain.rootStore.profileSectionStore.privacyStore onClosed: destroy() } property Component displayNamePopupComponent: DisplayNamePopup { anchors.centerIn: parent profileStore: appMain.rootStore.profileSectionStore.profileStore onClosed: { destroy() } } Component { id: downloadPageComponent DownloadPage { onClosed: { destroy(); } } } 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.setXPosition = function() { return mouse.x + Style.current.smallPadding } contextMenu.setYPosition = function() { return mouse.y - Style.current.smallPadding } contextMenu.show() } } Connections { target: Global onOpenImagePopup: { imagePopup.contextMenu = contextMenu imagePopup.openPopup(image) } } } property Component profilePopupComponent: ProfilePopup { id: profilePopup anchors.centerIn: parent profileStore: appMain.rootStore.profileSectionStore.profileStore contactsStore: appMain.rootStore.profileSectionStore.contactsStore onClosed: { if (profilePopup.parentPopup) { profilePopup.parentPopup.close(); } Global.profilePopupOpened = false; destroy(); } } property Component changeProfilePicComponent: Component { ImageCropWorkflow { title: qsTr("Profile Picture") acceptButtonText: qsTr("Make this my Profile Pic") onImageCropped: { 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 track: Qt.resolvedUrl("../imports/assets/audio/send_message.wav") Component.onCompleted: { Global.sendMessageSound = this; } } Audio { id: notificationSound store: rootStore track: Qt.resolvedUrl("../imports/assets/audio/notification.wav") Component.onCompleted: { Global.notificationSound = this; } } Audio { id: errorSound track: Qt.resolvedUrl("../imports/assets/audio/error.mp3") store: rootStore Component.onCompleted: { Global.errorSound = this; } } AppSearch { id: appSearch store: appMain.rootStore.appSearchStore } StatusEmojiPopup { id: statusEmojiPopup width: 360 height: 440 } StatusMainLayout { id: appLayout anchors.fill: parent leftPanel: StatusAppNavBar { communityTypeRole: "sectionType" communityTypeValue: Constants.appSection.community sectionModel: mainModule.sectionsModel Component.onCompleted: { mainModule.sectionsModel.sectionVisibilityUpdated.connect(function(){ triggerUpdate() }) } property bool communityAdded: false onAboutToUpdateFilteredRegularModel: { communityAdded = false } filterRegularItem: function(item) { if(!item.enabled) return false if(item.sectionType === Constants.appSection.community) if(communityAdded) return false else communityAdded = true return true } filterCommunityItem: function(item) { return item.sectionType === Constants.appSection.community } regularNavBarButton: StatusNavBarTabButton { id: navbar objectName: model.name + "-navbar" anchors.horizontalCenter: parent.horizontalCenter name: model.icon.length > 0? "" : model.name icon.name: model.icon icon.source: model.image tooltip.text: model.name checked: model.active badge.value: model.notificationsCount badge.visible: model.hasNotification badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor badge.border.width: 2 onClicked: { changeAppSectionBySectionId(model.id) } } communityNavBarButton: StatusNavBarTabButton { objectName: "CommunityNavBarButton" anchors.horizontalCenter: parent.horizontalCenter name: model.icon.length > 0? "" : model.name icon.name: model.icon icon.source: model.image tooltip.text: model.name checked: model.active badge.value: model.notificationsCount badge.visible: model.hasNotification badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor badge.border.width: 2 onClicked: { changeAppSectionBySectionId(model.id) } popupMenu: StatusPopupMenu { id: communityContextMenu property var chatCommunitySectionModule openHandler: function () { // // we cannot return QVariant if we pass another parameter in a function call // // that's why we're using it this way mainModule.prepareCommunitySectionModuleForCommunityId(model.id) communityContextMenu.chatCommunitySectionModule = mainModule.getCommunitySectionModule() } StatusMenuItem { text: qsTr("Invite People") icon.name: "share-ios" enabled: model.canManageUsers onTriggered: { Global.openInviteFriendsToCommunityPopup(model, communityContextMenu.chatCommunitySectionModule) } } StatusMenuItem { text: qsTr("View Community") icon.name: "group-chat" onTriggered: Global.openPopup(communityProfilePopup, { store: appMain.rootStore, community: model, communitySectionModule: communityContextMenu.chatCommunitySectionModule }) } StatusMenuSeparator {} StatusMenuItem { text: qsTr("Leave Community") icon.name: "arrow-left" type: StatusMenuItem.Type.Danger onTriggered: communityContextMenu.chatCommunitySectionModule.leaveCommunity() } } } navBarProfileButton: StatusNavBarTabButton { id: profileButton objectName: "statusProfileNavBarTabButton" property bool opened: false name: appMain.rootStore.userProfileInst.name icon.source: appMain.rootStore.userProfileInst.icon width: 32 height: 32 identicon.asset.width: width identicon.asset.height: height identicon.asset.charactersLen: 2 identicon.asset.color: Utils.colorForPubkey(appMain.rootStore.userProfileInst.pubKey) identicon.ringSettings.ringSpecModel: appMain.rootStore.userProfileInst.ensName ? undefined : Utils.getColorHashAsJson(appMain.rootStore.userProfileInst.pubKey, true) badge.visible: true badge.anchors { left: undefined top: undefined right: profileButton.right bottom: profileButton.bottom margins: 0 rightMargin: -badge.border.width bottomMargin: -badge.border.width } badge.implicitHeight: 12 badge.implicitWidth: 12 badge.border.width: 2 badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusAppNavBar.backgroundColor badge.color: { switch(appMain.rootStore.userProfileInst.currentUserStatus){ case Constants.currentUserStatus.automatic: case Constants.currentUserStatus.alwaysOnline: return Style.current.green; default: return Style.current.midGrey; } } onClicked: userStatusContextMenu.opened ? userStatusContextMenu.close() : userStatusContextMenu.open() UserStatusContextMenu { id: userStatusContextMenu y: profileButton.y - userStatusContextMenu.height + profileButton.height x: profileButton.x + profileButton.width + 5 store: appMain.rootStore } } } rightPanel: ColumnLayout { spacing: 0 objectName: "mainRightView" ColumnLayout { id: bannersLayout property var updateBanner: null property var connectedBanner: null readonly property bool isConnected: mainModule.isOnline function processUpdateAvailable() { if (!updateBanner) updateBanner = updateBannerComponent.createObject(this) } function processConnected() { if (!connectedBanner) connectedBanner = connectedBannerComponent.createObject(this) } Layout.fillWidth: true Layout.maximumHeight: implicitHeight spacing: 1 onIsConnectedChanged: { processConnected() } Connections { target: rootStore.aboutModuleInst onAppVersionFetched: { rootStore.setLatestVersionInfo(available, version, url); bannersLayout.processUpdateAvailable() } } ModuleWarning { id: testnetBanner objectName: "testnetBanner" Layout.fillWidth: true text: qsTr("Testnet mode is enabled. All balances, transactions and dApp interactions will be on testnets.") buttonText: qsTr("Turn off") type: ModuleWarning.Danger active: appMain.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled onClicked: { testnetBannerDialog.open() } onCloseClicked: { testnetBannerDialog.open() } StatusDialog { id: testnetBannerDialog width: 400 title: qsTr("Turn off Testnet mode") StatusBaseText { anchors.fill: parent text: qsTr("Closing this banner will turn off Testnet mode.\nAll future transactions will be on mainnet or other active networks.") font.pixelSize: 15 wrapMode: Text.WordWrap } footer: StatusDialogFooter { rightButtons: ObjectModel { StatusButton { type: StatusButton.Danger text: qsTr("Turn off Testnet") onClicked: { appMain.rootStore.profileSectionStore.walletStore.toggleTestNetworksEnabled() testnetBannerDialog.close() } } } } } } ModuleWarning { id: secureYourSeedPhrase objectName: "secureYourSeedPhraseBanner" Layout.fillWidth: true active: !appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner && !appMain.rootStore.profileSectionStore.profileStore.privacyStore.mnemonicBackedUp type: ModuleWarning.Danger text: qsTr("Secure your seed phrase") buttonText: qsTr("Back up now") onClicked: { Global.openBackUpSeedPopup(); } onCloseClicked: { appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner = true } } ModuleWarning { Layout.fillWidth: true readonly property int progress: communitiesPortalLayoutContainer.communitiesStore.discordImportProgress readonly property bool inProgress: progress > 0 && progress < 100 readonly property bool finished: progress >= 100 readonly property bool cancelled: communitiesPortalLayoutContainer.communitiesStore.discordImportCancelled readonly property bool stopped: communitiesPortalLayoutContainer.communitiesStore.discordImportProgressStopped readonly property int errors: communitiesPortalLayoutContainer.communitiesStore.discordImportErrorsCount readonly property int warnings: communitiesPortalLayoutContainer.communitiesStore.discordImportWarningsCount readonly property string communityId: communitiesPortalLayoutContainer.communitiesStore.discordImportCommunityId readonly property string communityName: communitiesPortalLayoutContainer.communitiesStore.discordImportCommunityName active: !cancelled && (inProgress || finished || stopped) type: errors ? ModuleWarning.Type.Danger : ModuleWarning.Type.Success text: { if (finished || stopped) { if (errors) return qsTr("The import of ‘%1’ from Discord to Status was stopped: Critical issues found").arg(communityName) let result = qsTr("‘%1’ was successfully imported from Discord to Status").arg(communityName) + " " if (warnings) result += qsTr("Details (%1)").arg(qsTr("%n issue(s)", "", warnings)) else result += qsTr("Details") result += "" return result } if (inProgress) { let result = qsTr("Importing ‘%1’ from Discord to Status").arg(communityName) + " " if (warnings) result += qsTr("Check progress (%1)").arg(qsTr("%n issue(s)", "", warnings)) else result += qsTr("Check progress") result += "" return result } } onLinkActivated: Global.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup) progressValue: progress closeBtnVisible: finished || stopped buttonText: finished && !errors ? qsTr("Visit your Community") : "" onClicked: function() { communitiesPortalLayoutContainer.communitiesStore.setActiveCommunity(communityId) } onCloseClicked: { hide(); } } Component { id: connectedBannerComponent ModuleWarning { readonly property bool isConnected: bannersLayout.isConnected objectName: "connectionInfoBanner" Layout.fillWidth: true text: isConnected ? qsTr("Connected") : qsTr("Disconnected") type: isConnected ? ModuleWarning.Success : ModuleWarning.Danger function updateState() { if (isConnected) showFor() else show(); } Component.onCompleted: { updateState() } onIsConnectedChanged: { updateState(); } onCloseClicked: { hide(); } onHideStarted: { bannersLayout.connectedBanner = null } onHideFinished: { destroy() } } } Component { id: updateBannerComponent ModuleWarning { readonly property string version: appMain.rootStore.latestVersion readonly property bool updateAvailable: appMain.rootStore.newVersionAvailable objectName: "appVersionUpdateBanner" Layout.fillWidth: true type: ModuleWarning.Success text: updateAvailable ? qsTr("A new version of Status (%1) is available").arg(version) : qsTr("Your version is up to date") buttonText: updateAvailable ? qsTr("Update") : qsTr("Close") function updateState() { if (updateAvailable) show() else showFor(5000) } Component.onCompleted: { updateState() } onUpdateAvailableChanged: { updateState(); } onClicked: { if (updateAvailable) Global.openDownloadModal(appMain.rootStore.newVersionAvailable, appMain.rootStore.latestVersion, appMain.rootStore.downloadURL) else close() } onCloseClicked: { if (updateAvailable) appMain.rootStore.resetLastVersion(); hide() } onHideStarted: { bannersLayout.updateBanner = null } onHideFinished: { destroy() } } } } Item { Layout.fillWidth: true Layout.fillHeight: true StackLayout { id: appView anchors.fill: parent currentIndex: { if(mainModule.activeSection.sectionType === Constants.appSection.chat) { return Constants.appViewStackIndex.chat } else if(mainModule.activeSection.sectionType === Constants.appSection.community) { for(let i = this.children.length - 1; i >=0; i--) { var obj = this.children[i]; if(obj && obj.sectionId && obj.sectionId == mainModule.activeSection.id) { return i } } // Should never be here, correct index must be returned from the for loop above console.error("Wrong section type: ", mainModule.activeSection.sectionType, " or section id: ", mainModule.activeSection.id) return Constants.appViewStackIndex.community } else if(mainModule.activeSection.sectionType === Constants.appSection.communitiesPortal) { return Constants.appViewStackIndex.communitiesPortal } else if(mainModule.activeSection.sectionType === Constants.appSection.wallet) { return Constants.appViewStackIndex.wallet } else if(mainModule.activeSection.sectionType === Constants.appSection.browser) { return Constants.appViewStackIndex.browser } else if(mainModule.activeSection.sectionType === Constants.appSection.profile) { return Constants.appViewStackIndex.profile } else if(mainModule.activeSection.sectionType === Constants.appSection.node) { return Constants.appViewStackIndex.node } // We should never end up here console.error("Unknown section type") } onCurrentIndexChanged: { var obj = this.children[currentIndex]; if(!obj) return if (obj.onActivated && typeof obj.onActivated === "function") { this.children[currentIndex].onActivated() } if(obj === browserLayoutContainer && browserLayoutContainer.active == false){ browserLayoutContainer.active = true; } if(obj === walletLayoutContainer){ walletLayoutContainer.showSigningPhrasePopup(); } } // NOTE: // If we ever change stack layout component order we need to updade // Constants.appViewStackIndex accordingly ChatLayout { id: chatLayoutContainer Layout.fillWidth: true Layout.fillHeight: true chatView.emojiPopup: statusEmojiPopup contactsStore: appMain.rootStore.contactStore rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel rootStore.openCreateChat: createChatView.opened chatView.onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } chatView.onOpenAppSearch: { appSearch.openSearchPopup() } onImportCommunityClicked: { Global.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup); } onCreateCommunityClicked: { Global.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup); } Component.onCompleted: { rootStore.chatCommunitySectionModule = mainModule.getChatSectionModule() } } CommunitiesPortalLayout { id: communitiesPortalLayoutContainer Layout.fillWidth: true Layout.fillHeight: true contentPrefferedWidth: appView.width } WalletLayout { id: walletLayoutContainer Layout.fillWidth: true Layout.fillHeight: true store: appMain.rootStore contactsStore: appMain.rootStore.profileSectionStore.contactsStore emojiPopup: statusEmojiPopup sendModal: sendModal } Component { id: browserLayoutComponent BrowserLayout { globalStore: appMain.rootStore sendTransactionModal: sendModal } } Loader { id: browserLayoutContainer sourceComponent: browserLayoutComponent active: false Layout.fillWidth: true Layout.fillHeight: true // Loaders do not have access to the context, so props need to be set // Adding a "_" to avoid a binding loop // Not Refactored Yet // property var _chatsModel: chatsModel.messageView // Not Refactored Yet // property var _walletModel: walletModel // Not Refactored Yet // property var _utilsModel: utilsModel property var _web3Provider: BrowserStores.Web3ProviderStore.web3ProviderInst } ProfileLayout { id: profileLayoutContainer Layout.fillWidth: true Layout.fillHeight: true store: appMain.rootStore.profileSectionStore globalStore: appMain.rootStore systemPalette: appMain.sysPalette emojiPopup: statusEmojiPopup } NodeLayout { id: nodeLayoutContainer Layout.fillWidth: true Layout.fillHeight: true } Repeater { model: mainModule.sectionsModel delegate: DelegateChooser { id: delegateChooser role: "sectionType" DelegateChoice { roleValue: Constants.appSection.community delegate: ChatLayout { property string sectionId: model.id Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillHeight: true chatView.emojiPopup: statusEmojiPopup contactsStore: appMain.rootStore.contactStore rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel rootStore.openCreateChat: createChatView.opened chatView.onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } chatView.onOpenAppSearch: { appSearch.openSearchPopup() } Component.onCompleted: { // we cannot return QVariant if we pass another parameter in a function call // that's why we're using it this way mainModule.prepareCommunitySectionModuleForCommunityId(model.id) rootStore.chatCommunitySectionModule = mainModule.getCommunitySectionModule() } } } } } } CreateChatView { id: createChatView property bool opened: false rootStore: chatLayoutContainer.rootStore emojiPopup: statusEmojiPopup anchors.top: parent.top anchors.topMargin: 8 anchors.rightMargin: 8 anchors.bottom: parent.bottom anchors.right: parent.right width: parent.width - chatLayoutContainer.chatView.leftPanel.width - anchors.rightMargin - anchors.leftMargin visible: createChatView.opened Connections { target: Global function onOpenCreateChatView() { createChatView.opened = true } function onCloseCreateChatView() { createChatView.opened = false } } Connections { target: mainModule function onActiveSectionChanged() { Global.closeCreateChatView() } } } } Connections { target: rootStore.mainModuleInst function onMailserverNotWorking() { if (!appLayout.mailserverNotWorkingPopup) { appLayout.mailserverNotWorkingPopup = Global.openPopup(mailserverNotWorkingPopupComponent); } } } } // ColumnLayout property var mailserverNotWorkingPopup: null Component { id: mailserverNotWorkingPopupComponent MailserverConnectionDialog { onClosed: { appLayout.mailserverNotWorkingPopup = null destroy() } } } 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 height: appView.height - 56 * 2 // TODO get screen size // Taken from old code top bar height was fixed there to 56 y: 56 store: chatLayoutContainer.rootStore acStore: appMain.acStore chatSectionModule: chatLayoutContainer.rootStore.chatCommunitySectionModule onClosed: { Global.activityCenterPopupOpened = false } } } // Add SendModal here as it is used by the Wallet as well as the Browser Loader { id: sendModal active: false function open(address = "") { this.active = true this.item.addressText = address; this.item.open() } function closed() { // this.sourceComponent = undefined // kill an opened instance this.active = false } property var selectedAccount sourceComponent: SendModal { store: appMain.rootStore contactsStore: appMain.rootStore.profileSectionStore.contactsStore onClosed: { sendModal.closed() } } onLoaded: { if (!!sendModal.selectedAccount) { item.selectedAccount = sendModal.selectedAccount } } } Action { shortcut: "Ctrl+1" onTriggered: { Global.setNthEnabledSectionActive(0) } } Action { shortcut: "Ctrl+2" onTriggered: { Global.setNthEnabledSectionActive(1) } } Action { shortcut: "Ctrl+3" onTriggered: { Global.setNthEnabledSectionActive(2) } } Action { shortcut: "Ctrl+4" onTriggered: { Global.setNthEnabledSectionActive(3) } } Action { shortcut: "Ctrl+5" onTriggered: { Global.setNthEnabledSectionActive(4) } } Action { shortcut: "Ctrl+6" onTriggered: { Global.setNthEnabledSectionActive(5) } } Action { shortcut: "Ctrl+7" onTriggered: { Global.setNthEnabledSectionActive(6) } } Action { shortcut: "Ctrl+8" onTriggered: { Global.setNthEnabledSectionActive(7) } } Action { shortcut: "Ctrl+9" onTriggered: { Global.setNthEnabledSectionActive(8) } } Action { shortcut: "Ctrl+K" onTriggered: { // FIXME the focus is no longer on the AppMain when the popup is opened, so this does not work to close if (channelPicker.opened) { channelPicker.close() } else { channelPicker.open() } } } Action { shortcut: "Ctrl+F" onTriggered: { // FIXME the focus is no longer on the AppMain when the popup is opened, so this does not work to close if (appSearch.opened) { appSearch.closeSearchPopup() } else { appSearch.openSearchPopup() } } } StatusSearchListPopup { id: channelPicker x: parent.width / 2 - width / 2 y: parent.height / 2 - height / 2 searchBoxPlaceholder: qsTr("Where do you want to go?") model: rootStore.chatSearchModel delegate: StatusListItem { property var modelData property bool isCurrentItem: true function filterAccepts(searchText) { return title.includes(searchText) } title: modelData ? modelData.name : "" label: modelData? modelData.sectionName : "" highlighted: isCurrentItem sensor.hoverEnabled: false statusListItemIcon { name: modelData ? modelData.name : "" active: true } asset.width: 30 asset.height: 30 asset.color: modelData ? modelData.color : "" asset.name: modelData ? modelData.icon : "" asset.isImage: asset.name.includes("data") } onAboutToShow: rootStore.rebuildChatSearchModel() onSelected: { rootStore.setActiveSectionChat(modelData.sectionId, modelData.chatId) close() } } } StatusListView { id: toastArea anchors.right: parent.right anchors.rightMargin: 8 anchors.bottom: parent.bottom anchors.bottomMargin: 60 width: 343 height: Math.min(parent.height - 120, toastArea.contentHeight) spacing: 8 verticalLayoutDirection: ListView.BottomToTop model: appMain.rootStore.mainModuleInst.ephemeralNotificationModel delegate: StatusToastMessage { primaryText: model.title secondaryText: model.subTitle icon.name: model.icon loading: model.loading type: model.ephNotifType linkUrl: model.url duration: model.durationInMs onClicked: { appMain.rootStore.mainModuleInst.ephemeralNotificationClicked(model.timestamp) this.open = false } onLinkActivated: { Qt.openUrlExternally(link); } onClose: { appMain.rootStore.mainModuleInst.removeEphemeralNotification(model.timestamp) } } } Component.onCompleted: { Global.appMain = this; Global.pinnedMessagesPopup = pinnedMessagesPopupComponent; Global.communityProfilePopup = communityProfilePopup; const whitelist = appMain.rootStore.messagingStore.getLinkPreviewWhitelist() try { const whiteListedSites = JSON.parse(whitelist) let settingsUpdated = false // Add Status links to whitelist whiteListedSites.push({title: "Status", address: Constants.deepLinkPrefix, imageSite: false}) whiteListedSites.push({title: "Status", address: Constants.joinStatusLink, imageSite: false}) let settings = localAccountSensitiveSettings.whitelistedUnfurlingSites if (!settings) { settings = {} } // Set Status links as true. We intercept thoseURLs so it is privacy-safe if (!settings[Constants.deepLinkPrefix] || !settings[Constants.joinStatusLink]) { settings[Constants.deepLinkPrefix] = true settings[Constants.joinStatusLink] = true settingsUpdated = true } const whitelistedHostnames = [] // Add whitelisted sites in to app settings that are not already there whiteListedSites.forEach(site => { if (!settings.hasOwnProperty(site.address)) { settings[site.address] = false settingsUpdated = true } whitelistedHostnames.push(site.address) }) // Remove any whitelisted sites from app settings that don't exist in the // whitelist from status-go Object.keys(settings).forEach(settingsHostname => { if (!whitelistedHostnames.includes(settingsHostname)) { delete settings[settingsHostname] settingsUpdated = true } }) if (settingsUpdated) { localAccountSensitiveSettings.whitelistedUnfurlingSites = settings } } catch (e) { console.error('Could not parse the whitelist for sites', e) } Global.settingsHasLoaded(); Global.errorSound = errorSound; } Loader { id: keycardPopup active: false sourceComponent: KeycardPopup { sharedKeycardModule: rootStore.mainModuleInst.keycardSharedModule } onLoaded: { keycardPopup.item.open() } } DropAreaPanel { width: appMain.width height: appMain.height activeChatType: chatCommunitySectionModule && chatCommunitySectionModule.activeItem.type enabled: !drag.source && ( // in chat view (mainModule.activeSection.sectionType === Constants.appSection.chat && ( // in a one-to-one chat activeChatType === Constants.chatType.oneToOne || // in a private group chat activeChatType === Constants.chatType.privateGroupChat ) ) || // In community section mainModule.activeSection.sectionType === Constants.appSection.community) } }