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 SortFilterProxyModel 0.2 import "panels" import "activitycenter/popups" import "activitycenter/stores" Item { id: appMain property alias appLayout: appLayout property RootStore rootStore: RootStore {} property ActivityCenterStore activityCenterStore: ActivityCenterStore {} // set from main.qml property var sysPalette property var activePopupComponents: [] signal closeProfilePopup() Connections { target: rootStore.mainModuleInst function onDisplayUserProfile(publicKey: string) { Global.openProfilePopup(publicKey) } function onDisplayKeycardSharedModuleFlow() { keycardPopup.active = true } function onDestroyKeycardSharedModuleFlow() { keycardPopup.active = false } function onMailserverWorking() { mailserverConnectionBanner.hide() } function onMailserverNotWorking() { mailserverConnectionBanner.show() } function onActiveSectionChanged() { createChatView.opened = false } function onOpenActivityCenter() { Global.openPopup(activityCenterPopupComponent) } } Popups { 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 { 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 } function onCloseCreateChatView() { 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(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; } 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); if (appMain.rootStore.showBrowserSelector) { Global.openChooseBrowserPopup(link); } else { if (appMain.rootStore.openLinksInStatus) { Global.changeAppSectionBySectionType(Constants.appSection.browser); Global.openLinkInBrowser(link); } else { Qt.openUrlExternally(link); } } } function onSetNthEnabledSectionActive(nthSection: int) { if(!appMain.rootStore.mainModuleInst) return appMain.rootStore.mainModuleInst.setNthEnabledSectionActive(nthSection) } function onAppSectionBySectionTypeChanged(sectionType: int, subsection: int) { if(!appMain.rootStore.mainModuleInst) return appMain.rootStore.mainModuleInst.setActiveSectionBySectionType(sectionType) if (sectionType === Constants.appSection.profile) { Global.settingsSubsection = subsection; } } } function changeAppSectionBySectionId(sectionId) { 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 { id: appSearch active: false asynchronous: true function openSearchPopup() { if (!active) active = true item.openSearchPopup() } function closeSearchPopup() { if (item) item.closeSearchPopup() active = false } sourceComponent: AppSearch { store: appMain.rootStore.appSearchStore onClosed: appSearch.active = false } } StatusEmojiPopup { id: statusEmojiPopup width: 360 height: 440 } Loader { id: statusStickersPopupLoader active: appMain.rootStore.mainModuleInst.chatsLoaded sourceComponent: StatusStickersPopup { id: statusStickersPopup store: personalChatLayoutLoader.item.rootStore } } StatusMainLayout { id: appLayout anchors.fill: parent leftPanel: StatusAppNavBar { chatItemsModel: SortFilterProxyModel { sourceModel: appMain.rootStore.mainModuleInst.sectionsModel filters: [ ValueFilter { roleName: "sectionType" value: Constants.appSection.chat }, ValueFilter { roleName: "enabled" value: true } ] } chatItemDelegate: navbarButton communityItemsModel: SortFilterProxyModel { sourceModel: appMain.rootStore.mainModuleInst.sectionsModel filters: [ ValueFilter { roleName: "sectionType" value: Constants.appSection.community }, ValueFilter { roleName: "enabled" value: true } ] } communityItemDelegate: StatusNavBarTabButton { objectName: "CommunityNavBarButton" anchors.horizontalCenter: parent.horizontalCenter name: model.icon.length > 0? "" : model.name icon.name: model.icon icon.source: model.image identicon.asset.color: (hovered || identicon.highlighted || checked) ? model.color : icon.color 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: Component { StatusMenu { 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 appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id) communityContextMenu.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule() } StatusAction { text: qsTr("Invite People") icon.name: "share-ios" enabled: model.canManageUsers onTriggered: { Global.openInviteFriendsToCommunityPopup(model, communityContextMenu.chatCommunitySectionModule, null) } } StatusAction { text: qsTr("View Community") icon.name: "group-chat" onTriggered: Global.openPopup(communityProfilePopup, { store: appMain.rootStore, community: model, communitySectionModule: communityContextMenu.chatCommunitySectionModule }) } StatusMenuSeparator {} StatusAction { text: qsTr("Leave Community") icon.name: "arrow-left" type: StatusAction.Type.Danger onTriggered: communityContextMenu.chatCommunitySectionModule.leaveCommunity() } } } } regularItemsModel: SortFilterProxyModel { sourceModel: appMain.rootStore.mainModuleInst.sectionsModel filters: [ RangeFilter { roleName: "sectionType" minimumValue: Constants.appSection.wallet maximumValue: Constants.appSection.communitiesPortal }, ValueFilter { roleName: "enabled" value: true } ] } regularItemDelegate: navbarButton delegateHeight: 40 profileComponent: StatusNavBarTabButton { id: profileButton objectName: "statusProfileNavBarTabButton" property bool opened: false name: appMain.rootStore.userProfileInst.name icon.source: appMain.rootStore.userProfileInst.icon implicitWidth: 32 implicitHeight: 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: Utils.getColorHashAsJson(appMain.rootStore.userProfileInst.pubKey, appMain.rootStore.userProfileInst.preferredName) 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 } } Component { id: navbarButton 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: Utils.translatedSectionName(model.sectionType, 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) } } } } rightPanel: ColumnLayout { spacing: 0 objectName: "mainRightView" ColumnLayout { id: bannersLayout enabled: !localAppSettings.testEnvironment visible: enabled property var updateBanner: null property var connectedBanner: null readonly property bool isConnected: appMain.rootStore.mainModuleInst.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() } Component.onCompleted: { if (!isConnected) processConnected() } Connections { target: rootStore.aboutModuleInst function onAppVersionFetched(available: bool, version: string, url: string) { 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) || communitiesPortalLayoutContainer.communitiesStore.discordImportInProgress 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 } return "" } 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(); } } ModuleWarning { id: downloadingArchivesBanner Layout.fillWidth: true active: communitiesPortalLayoutContainer.communitiesStore.downloadingCommunityHistoryArchives type: ModuleWarning.Danger text: qsTr("Downloading message history archives, DO NOT CLOSE THE APP until this banner disappears.") closeBtnVisible: false } ModuleWarning { id: mailserverConnectionBanner type: ModuleWarning.Warning text: qsTr("Can not connect to store node. Retrying automatically") onCloseClicked: hide() Layout.fillWidth: true } Component { id: connectedBannerComponent ModuleWarning { id: connectedBanner property bool isConnected: true 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: { connectedBanner.isConnected = Qt.binding(() => bannersLayout.isConnected); } onIsConnectedChanged: { updateState(); } onCloseClicked: { hide(); } onHideFinished: { destroy() bannersLayout.connectedBanner = null } } } 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() } onHideFinished: { destroy() bannersLayout.updateBanner = null } } } } Item { Layout.fillWidth: true Layout.fillHeight: true StackLayout { id: appView anchors.fill: parent currentIndex: { const activeSectionType = appMain.rootStore.mainModuleInst.activeSection.sectionType if (activeSectionType === Constants.appSection.chat) return Constants.appViewStackIndex.chat if (activeSectionType === Constants.appSection.community) { for (let i = this.children.length - 1; i >=0; i--) { var obj = this.children[i] if (obj && obj.sectionId && obj.sectionId === appMain.rootStore.mainModuleInst.activeSection.id) { return i } } // Should never be here, correct index must be returned from the for loop above console.error("Wrong section type:", appMain.rootStore.mainModuleInst.activeSection.sectionType, "or section id: ", appMain.rootStore.mainModuleInst.activeSection.id) return Constants.appViewStackIndex.community } if (activeSectionType === Constants.appSection.communitiesPortal) return Constants.appViewStackIndex.communitiesPortal if (activeSectionType === Constants.appSection.wallet) return Constants.appViewStackIndex.wallet if (activeSectionType === Constants.appSection.browser) return Constants.appViewStackIndex.browser if (activeSectionType === Constants.appSection.profile) return Constants.appViewStackIndex.profile if (activeSectionType === Constants.appSection.node) return Constants.appViewStackIndex.node // We should never end up here console.error("AppMain: Unknown section type") } // NOTE: // If we ever change stack layout component order we need to updade // Constants.appViewStackIndex accordingly Loader { id: personalChatLayoutLoader sourceComponent: { if (appMain.rootStore.mainModuleInst.chatsLoadingFailed) { return errorStateComponent } if (appMain.rootStore.mainModuleInst.chatsLoaded) { return personalChatLayoutComponent } return loadingStateComponent } Component { id: loadingStateComponent Item { anchors.fill: parent Row { anchors.centerIn: parent spacing: 6 StatusBaseText { text: qsTr("Loading...") } LoadingAnimation {} } } } Component { id: errorStateComponent Item { anchors.fill: parent StatusBaseText { text: qsTr("Error loading chats, try closing the app and restarting") anchors.centerIn: parent } } } Component { id: personalChatLayoutComponent ChatLayout { id: chatLayoutContainer chatView.emojiPopup: statusEmojiPopup chatView.stickersPopup: statusStickersPopupLoader.item 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 = appMain.rootStore.mainModuleInst.getChatSectionModule() } } } } CommunitiesPortalLayout { id: communitiesPortalLayoutContainer } Loader { active: appView.currentIndex === Constants.appViewStackIndex.wallet asynchronous: true sourceComponent: WalletLayout { store: appMain.rootStore contactsStore: appMain.rootStore.profileSectionStore.contactsStore emojiPopup: statusEmojiPopup sendModalPopup: sendModal } onLoaded: item.showSigningPhrasePopup() } Loader { id: browserLayoutContainer active: appView.currentIndex === Constants.appViewStackIndex.browser asynchronous: true sourceComponent: BrowserLayout { globalStore: appMain.rootStore sendTransactionModal: sendModal } // 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 } Loader { active: appView.currentIndex === Constants.appViewStackIndex.profile asynchronous: true sourceComponent: ProfileLayout { store: appMain.rootStore.profileSectionStore globalStore: appMain.rootStore systemPalette: appMain.sysPalette emojiPopup: statusEmojiPopup } } Loader { active: appView.currentIndex === Constants.appViewStackIndex.node asynchronous: true sourceComponent: NodeLayout {} } Repeater { model: appMain.rootStore.mainModuleInst.sectionsModel delegate: DelegateChooser { role: "sectionType" DelegateChoice { roleValue: Constants.appSection.community delegate: Loader { readonly property string sectionId: model.id active: sectionId === appMain.rootStore.mainModuleInst.activeSection.id asynchronous: true Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillHeight: true sourceComponent: ChatLayout { chatView.emojiPopup: statusEmojiPopup chatView.stickersPopup: statusStickersPopupLoader.item 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 appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id) rootStore.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule() } } } } } } } Loader { id: createChatView property bool opened: false active: appMain.rootStore.mainModuleInst.chatsLoaded && opened asynchronous: true anchors.top: parent.top anchors.topMargin: 8 anchors.rightMargin: 8 anchors.bottom: parent.bottom anchors.right: parent.right width: active ? parent.width - Constants.chatSectionLeftColumnWidth - anchors.rightMargin - anchors.leftMargin : 0 sourceComponent: CreateChatView { rootStore: personalChatLayoutLoader.item.rootStore emojiPopup: statusEmojiPopup stickersPopup: statusStickersPopupLoader.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 x: parent.width - width - Style.current.smallPadding y: parent.y + _buttonSize height: appView.height - _buttonSize * 2 store: personalChatLayoutLoader.item.rootStore activityCenterStore: appMain.activityCenterStore } } 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 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 property bool isBridgeTx sourceComponent: SendModal { onClosed: { sendModal.closed() sendModal.isBridgeTx = false } } onLoaded: { if (!!sendModal.selectedAccount) { item.selectedAccount = sendModal.selectedAccount } if(isBridgeTx) item.isBridgeTx = sendModal.isBridgeTx } } 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 (!channelPickerLoader.active) channelPickerLoader.active = true if (channelPickerLoader.item.opened) { channelPickerLoader.item.close() channelPickerLoader.active = false } else { channelPickerLoader.item.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.active) { appSearch.closeSearchPopup() } else { appSearch.openSearchPopup() } } } Loader { id: channelPickerLoader active: false asynchronous: true sourceComponent: StatusSearchListPopup { searchBoxPlaceholder: qsTr("Where do you want to go?") model: rootStore.chatSearchModel delegate: StatusListItem { property var modelData property bool isCurrentItem: true function filterAccepts(searchText) { const lowerCaseSearchText = searchText.toLowerCase() return title.toLowerCase().includes(lowerCaseSearchText) || label.toLowerCase().includes(lowerCaseSearchText) } 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 objectName: "ephemeralNotificationList" 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.settingsLoaded() } Loader { id: keycardPopup active: false sourceComponent: KeycardPopup { anchors.centerIn: parent sharedKeycardModule: appMain.rootStore.mainModuleInst.keycardSharedModule } onLoaded: { keycardPopup.item.open() } } Connections { target: appMain.rootStore.mainModuleInst function onActiveSectionChanged() { let communitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule() if (communitySectionModule) rootDropAreaPanel.activeChatType = communitySectionModule.activeItem.type } } DropAreaPanel { id: rootDropAreaPanel width: appMain.width height: appMain.height activeChatType: appMain.rootStore.mainModuleInst.getCommunitySectionModule() ? appMain.rootStore.mainModuleInst.getCommunitySectionModule().activeItem.type : 0 enabled: !drag.source && ( // in chat view (appMain.rootStore.mainModuleInst.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 appMain.rootStore.mainModuleInst.activeSection.sectionType === Constants.appSection.community) } }