mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-09 22:06:25 +00:00
afaf7717e8
Fixes #8038 We added a callback arg to the signals, but signals do not support default args. You need to pass the exact number of args.
1298 lines
50 KiB
QML
1298 lines
50 KiB
QML
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
|
||
|
||
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
|
||
}
|
||
|
||
function onMailserverNotWorking() {
|
||
Global.openPopup(mailserverNotWorkingPopupComponent)
|
||
}
|
||
|
||
function onActiveSectionChanged() {
|
||
createChatView.opened = false
|
||
}
|
||
}
|
||
|
||
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
|
||
onOpenLinkInBrowser: {
|
||
if (!browserLayoutContainer.active)
|
||
browserLayoutContainer.active = true;
|
||
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
|
||
}
|
||
|
||
onOpenImagePopup: {
|
||
var popup = imagePopupComponent.createObject(appMain)
|
||
popup.contextMenu = contextMenu
|
||
popup.openPopup(image)
|
||
}
|
||
|
||
onOpenCreateChatView: {
|
||
createChatView.opened = true
|
||
}
|
||
|
||
onCloseCreateChatView: {
|
||
createChatView.opened = false
|
||
}
|
||
|
||
onOpenProfilePopupRequested: {
|
||
Global.openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup})
|
||
Global.profilePopupOpened = true
|
||
}
|
||
onOpenNicknamePopupRequested: {
|
||
Global.openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle})
|
||
}
|
||
onBlockContactRequested: {
|
||
Global.openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
|
||
}
|
||
onUnblockContactRequested: {
|
||
Global.openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey})
|
||
}
|
||
|
||
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: Global.openPopup(displayNamePopupComponent)
|
||
}
|
||
|
||
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.setXPosition = function() { return mouse.x + Style.current.smallPadding }
|
||
contextMenu.setYPosition = function() { return mouse.y - Style.current.smallPadding }
|
||
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 {
|
||
id: changeProfilePicComponent
|
||
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;
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
StatusEmojiPopup {
|
||
id: statusEmojiPopup
|
||
width: 360
|
||
height: 440
|
||
}
|
||
|
||
StatusMainLayout {
|
||
id: appLayout
|
||
|
||
anchors.fill: parent
|
||
|
||
leftPanel: StatusAppNavBar {
|
||
communityTypeRole: "sectionType"
|
||
communityTypeValue: Constants.appSection.community
|
||
sectionModel: appMain.rootStore.mainModuleInst.sectionsModel
|
||
|
||
Component.onCompleted: {
|
||
appMain.rootStore.mainModuleInst.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
|
||
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: 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
|
||
appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id)
|
||
communityContextMenu.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule()
|
||
|
||
}
|
||
|
||
StatusMenuItem {
|
||
text: qsTr("Invite People")
|
||
icon.name: "share-ios"
|
||
enabled: model.canManageUsers
|
||
onTriggered: {
|
||
Global.openInviteFriendsToCommunityPopup(model,
|
||
communityContextMenu.chatCommunitySectionModule,
|
||
null)
|
||
}
|
||
}
|
||
|
||
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: 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()
|
||
}
|
||
|
||
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) || 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: <a href='#'>Critical issues found</a>").arg(communityName)
|
||
|
||
let result = qsTr("‘%1’ was successfully imported from Discord to Status").arg(communityName) + " <a href='#'>"
|
||
if (warnings)
|
||
result += qsTr("Details (%1)").arg(qsTr("%n issue(s)", "", warnings))
|
||
else
|
||
result += qsTr("Details")
|
||
result += "</a>"
|
||
return result
|
||
}
|
||
if (inProgress) {
|
||
let result = qsTr("Importing ‘%1’ from Discord to Status").arg(communityName) + " <a href='#'>"
|
||
if (warnings)
|
||
result += qsTr("Check progress (%1)").arg(qsTr("%n issue(s)", "", warnings))
|
||
else
|
||
result += qsTr("Check progress")
|
||
result += "</a>"
|
||
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 {
|
||
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();
|
||
}
|
||
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: {
|
||
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)
|
||
{
|
||
obj.active = true
|
||
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")
|
||
}
|
||
|
||
onCurrentIndexChanged: {
|
||
var obj = this.children[currentIndex]
|
||
if (!obj)
|
||
return
|
||
|
||
if (obj === browserLayoutContainer && browserLayoutContainer.active == false) {
|
||
browserLayoutContainer.active = true;
|
||
}
|
||
|
||
if (obj === walletLayoutContainer && !walletLayoutContainer.active) {
|
||
walletLayoutContainer.active = true
|
||
walletLayoutContainer.item.showSigningPhrasePopup()
|
||
}
|
||
|
||
if (obj === profileLayoutContainer && !profileLayoutContainer.active) {
|
||
profileLayoutContainer.active = true
|
||
}
|
||
|
||
if (obj === nodeLayoutContainer && !nodeLayoutContainer.active) {
|
||
nodeLayoutContainer.active = true
|
||
}
|
||
|
||
if (obj.onActivated && typeof obj.onActivated === "function") {
|
||
this.children[currentIndex].onActivated()
|
||
}
|
||
}
|
||
|
||
// NOTE:
|
||
// If we ever change stack layout component order we need to updade
|
||
// Constants.appViewStackIndex accordingly
|
||
|
||
ChatLayout {
|
||
id: chatLayoutContainer
|
||
|
||
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 = appMain.rootStore.mainModuleInst.getChatSectionModule()
|
||
}
|
||
}
|
||
|
||
CommunitiesPortalLayout {
|
||
id: communitiesPortalLayoutContainer
|
||
}
|
||
|
||
Loader {
|
||
id: walletLayoutContainer
|
||
active: false
|
||
asynchronous: true
|
||
sourceComponent: WalletLayout {
|
||
store: appMain.rootStore
|
||
contactsStore: appMain.rootStore.profileSectionStore.contactsStore
|
||
emojiPopup: statusEmojiPopup
|
||
sendModal: sendModal
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: browserLayoutContainer
|
||
active: false
|
||
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 {
|
||
id: profileLayoutContainer
|
||
active: false
|
||
asynchronous: true
|
||
sourceComponent: ProfileLayout {
|
||
store: appMain.rootStore.profileSectionStore
|
||
globalStore: appMain.rootStore
|
||
systemPalette: appMain.sysPalette
|
||
emojiPopup: statusEmojiPopup
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: nodeLayoutContainer
|
||
active: false
|
||
asynchronous: true
|
||
sourceComponent: NodeLayout {}
|
||
}
|
||
|
||
Repeater {
|
||
model: appMain.rootStore.mainModuleInst.sectionsModel
|
||
|
||
delegate: DelegateChooser {
|
||
role: "sectionType"
|
||
DelegateChoice {
|
||
roleValue: Constants.appSection.community
|
||
|
||
delegate: Loader {
|
||
property string sectionId: model.id
|
||
active: false
|
||
asynchronous: true
|
||
Layout.fillWidth: true
|
||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||
Layout.fillHeight: true
|
||
|
||
sourceComponent: ChatLayout {
|
||
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
|
||
appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id)
|
||
rootStore.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: createChatView
|
||
|
||
property bool opened: false
|
||
active: opened
|
||
|
||
asynchronous: true
|
||
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
|
||
|
||
sourceComponent: CreateChatView {
|
||
rootStore: chatLayoutContainer.rootStore
|
||
emojiPopup: statusEmojiPopup
|
||
}
|
||
}
|
||
}
|
||
} // ColumnLayout
|
||
|
||
Component {
|
||
id: mailserverNotWorkingPopupComponent
|
||
MailserverConnectionDialog {
|
||
onClosed: {
|
||
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
|
||
}
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: nicknamePopupComponent
|
||
NicknamePopup {
|
||
onEditDone: {
|
||
if (nickname !== newNickname) {
|
||
appMain.rootStore.contactStore.changeContactNickname(publicKey, newNickname)
|
||
Global.nickNameChanged(publicKey, newNickname)
|
||
}
|
||
close()
|
||
}
|
||
onClosed: destroy()
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: unblockContactConfirmationComponent
|
||
UnblockContactConfirmationDialog {
|
||
onUnblockButtonClicked: {
|
||
appMain.rootStore.contactStore.unblockContact(contactAddress)
|
||
Global.contactUnblocked(contactAddress)
|
||
close()
|
||
}
|
||
onClosed: destroy()
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: blockContactConfirmationComponent
|
||
BlockContactConfirmationDialog {
|
||
onBlockButtonClicked: {
|
||
appMain.rootStore.contactStore.blockContact(contactAddress)
|
||
Global.contactBlocked(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
|
||
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 (!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) {
|
||
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.privacyModuleInst = appMain.rootStore.profileSectionStore.privacyStore.privacyModule
|
||
Global.settingsLoaded()
|
||
}
|
||
|
||
Loader {
|
||
id: keycardPopup
|
||
active: false
|
||
sourceComponent: KeycardPopup {
|
||
sharedKeycardModule: appMain.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
|
||
(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)
|
||
}
|
||
}
|