import QtGraphicalEffects 1.0 import QtQuick 2.7 import QtQuick.Controls 2.4 import QtQuick.Controls.Material 2.0 import QtQuick.Layouts 1.3 import QtQuick.Window 2.11 import "opaque" import "opaque/fonts" import "opaque/fonts/MutantStandard.js" as Mutant import "opaque/theme" import "overlays" import "panes" import "widgets" import "utils.js" as Utils ApplicationWindow { id: windowItem width: 1200 height: 800 visible: true title: "cwtch" + "" font.family: Fonts.applicationFontRegular.name font.styleName: "Light" readonly property real ratio: height / width FontAwesome { // PRETTY BUTTON ICONS id: awesome resource: "qrc:/qml/opaque/fonts/fontawesome.ttf" } FontLoader { source: "qrc:/qml/opaque/fonts/AdobeBlank.ttf" } function parse(text, size, isntEditable) { // REPLACE EMOJI WITH TAGS var retText = Utils.htmlEscaped(text) retText = retText.replace(/\n/g,"
").replace(/\s\s/g, "  ") // mutant standard stickers if (isntEditable) retText = Mutant.standard.parse(retText, 1.5 * gcd.themeScale * Theme.chatSize) return retText } function ptToPx(pt) { return Screen.pixelDensity * 25.4 * pt / 72 } function pxToPt(px) { return px * 72 / (Screen.pixelDensity * 25.4) } StackView { id: rootStack anchors.fill: parent property bool splash: true // Splash pane initialItem: Rectangle { color: Theme.backgroundMainColor visible: true SplashPane { id: splashPane anchors.fill: parent running: true } } replaceEnter: Transition { PropertyAnimation{ property: "opacity" from: 0 to: 1 duration: 200 } } replaceExit: Transition { PropertyAnimation{ property: "opacity" from: 1 to: 0 duration: 200 } } // The actual app property Item mainLayout: Rectangle { color: Theme.backgroundMainColor Toolbar { id: toolbar onLeftMenu: { gcd.requestSettings() parentStack.pane = parentStack.settingsPane } onBack: { backFn() } onRightMenu: { // If a group is selected.... if (Utils.isGroup(gcd.selectedConversation)) { theStack.pane = theStack.groupProfilePane gcd.requestGroupSettings(gcd.selectedConversation) } else { // if a peer is selected.. if (theStack.pane == theStack.userProfilePane) { theStack.pane = theStack.messagePane } else { theStack.pane = theStack.userProfilePane gcd.requestPeerSettings(gcd.selectedConversation) } } } } StackLayout { id: parentStack readonly property int managementPane: 0 readonly property int settingsPane: 1 readonly property int addEditProfilePane: 2 readonly property int profilePane: 3 readonly property int addEditServerPane: 4 currentIndex: gcd.firstTime ? parentStack.settingsPane : parentStack.managementPane anchors.right: parent.right anchors.left: parent.left anchors.bottom: statusbar.top anchors.top: toolbar.bottom property alias pane: parentStack.currentIndex Rectangle { // Profile login/management pane Layout.fillHeight: true Layout.fillWidth: true visible: false color: Theme.backgroundMainColor ProfileManagerPane { id: profilesPane anchors.fill: parent } } Rectangle { // Settings pane Layout.fillHeight: true Layout.fillWidth: true color: Theme.backgroundPaneColor SettingsPane { id: settingsPane anchors.fill: parent } } Rectangle { // Profile Add / Edit pane Layout.fillHeight: true Layout.fillWidth: true color: Theme.backgroundPaneColor ProfileAddEditPane{ id: profileAddEditPane anchors.fill: parent } } RowLayout { // Profile Pane (contact list + overlays) Layout.fillHeight: true Layout.fillWidth: true spacing: 0 Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS color: Theme.backgroundMainColor Layout.fillHeight: true Layout.minimumWidth: Theme.sidePaneMinSize * gcd.themeScale Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : (Theme.sidePaneMinSize * gcd.themeScale) Layout.fillWidth: theStack.pane == theStack.emptyPane ? true : false visible: (windowItem.width >= (Theme.doublePaneMinSize * gcd.themeScale) && !Qt.inputMethod.visible) || theStack.pane == theStack.emptyPane ContactList { anchors.top: parent.top anchors.left: parent.left anchors.bottom: parent.bottom anchors.right: (divider.visible ? divider.left : parent.right) //anchors.topMargin: 10 * gcd.themeScale dualPane: theStack.pane != theStack.emptyPane || theStack.pane == undefined } Rectangle { id: divider width: 2 anchors.right: parent.right height: parent.height - (20 * gcd.themeScale) anchors.verticalCenter: parent.verticalCenter visible: theStack.pane != theStack.emptyPane //Layout.fillHeight: true color: Theme.dividerColor } } Rectangle { // THE RIGHT PANE WHERE THE MESSAGES AND STUFF GO color: Theme.backgroundPaneColor Layout.fillWidth: true Layout.fillHeight: true StackLayout { id: theStack anchors.fill: parent currentIndex: 0 property alias pane: theStack.currentIndex readonly property int emptyPane: 0 readonly property int messagePane: 1 readonly property int userProfilePane: 2 readonly property int groupProfilePane: 3 readonly property int addPeerGroupPane: 4 readonly property int serverInfoPane: 5 Item { anchors.fill: parent } // empty Rectangle { color: Theme.backgroundMainColor Layout.fillWidth: true Layout.fillHeight: true OverlayPane { // messagePane anchors.fill: parent anchors.topMargin: 10// * gcd.themeScale } } PeerSettingsPane { anchors.fill: parent } GroupSettingsPane{ anchors.fill: parent } AddPeerGroupPane { id: addPeerGroupPaneInstance anchors.fill: parent } ServerInfoPane { anchors.fill: parent } onCurrentIndexChanged: { gcd.setProfilePaneState(theStack.currentIndex) parentStack.updateToolbar() if (currentIndex == emptyPane) { toolbar.hideTitle() toolbar.rightMenuVisible = false } else if (currentIndex == addPeerGroupPane) { //: New Connection toolbar.setTitle(qsTr('new-connection-pane-title')) toolbar.rightMenuVisible = false addPeerGroupPaneInstance.reset() } } onWidthChanged: {toolbar.titleWidth = width} } } } Rectangle { // Server Add / Edit pane Layout.fillHeight: true Layout.fillWidth: true color: Theme.backgroundPaneColor ServerAddEditPane{ id: serverAddEditPane anchors.fill: parent } } Connections { target: gcd onChangeRootPane: function(pane) { parentStack.currentIndex = pane } onChangeProfilePane: function(pane) { theStack.currentIndex = pane } } focus: true Keys.onPressed: { if (event.key == Qt.Key_Back) { event.accepted = true backFn() } } onCurrentIndexChanged : { gcd.setRootPaneState(parentStack.currentIndex) updateToolbar(); statusbar.resetHeight() } function updateToolbar() { if (rootStack.splash == true) { toolbar.hideTitle() toolbar.rightMenuVisible = false toolbar.visible = false } else { toolbar.visible = true if (currentIndex == managementPane) { toolbar.hideTitle() toolbar.rightMenuVisible = false toolbar.color = Theme.toolbarMainColor toolbar.leftMenuVisible = true toolbar.backVisible = false } else { toolbar.leftMenuVisible = false toolbar.backVisible = true if (currentIndex == profilePane && theStack.currentIndex == theStack.emptyPane) { toolbar.hideTitle() toolbar.rightMenuVisible = false toolbar.color = Theme.toolbarMainColor } else { toolbar.color = Theme.toolbarAltColor } } } } Component.onCompleted: updateToolbar() Connections { target: Theme onThemeChanged: { parentStack.updateToolbar() } } } Statusbar { id: statusbar } } } function backFn() { if (parentStack.currentIndex == parentStack.managementPane) { androidCwtchActivity.rootHomeButtonHandle() } else if (parentStack.currentIndex != parentStack.profilePane) { parentStack.currentIndex = parentStack.managementPane } else { if (theStack.currentIndex == theStack.emptyPane) { gcd.selectedProfile = "none" gcd.reloadProfileList() parentStack.pane = parentStack.managementPane } else if (theStack.currentIndex == theStack.userProfilePane || theStack.currentIndex == theStack.groupProfilePane) { theStack.currentIndex = theStack.messagePane } else { theStack.currentIndex = theStack.emptyPane } } } PropertyAnimation { id: anmPopup; easing.type: Easing.InQuart; duration: 7000; target: popup; property: "opacity"; to: 0; } Rectangle { // THE ERROR MESSAGE POPUP id: popup anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 20 width: lblPopup.width + 30 height: lblPopup.height + 8 * gcd.themeScale color: "#000000" opacity: 0.5 radius: 15 visible: false // We store error messages in QML referenced by an ID so they can be called from go // and the qupdate / qml i18n system can easily find and translate them here property var errorMessages: (new Map([ //: 0 profiles loaded with that password ["0-profiles", qsTr("error-0-profiles-loaded-for-password")], ])) Label { id: lblPopup anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter font.pixelSize: 18 * gcd.themeScale color: "#FFFFFF" } } Connections { // POPUPS ARE INVOKED BY GO FUNCS target: gcd onInvokePopup: function(str) { if (popup.errorMessages.get(str) != undefined) { str = popup.errorMessages.get(str) } lblPopup.text = str popup.opacity = 0.5 popup.visible = true anmPopup.restart() } onLoaded: function() { //parentStack.pane = parentStack.managementPane rootStack.replace(rootStack.mainLayout) splashPane.running = false rootStack.splash = false parentStack.updateToolbar() statusbar.resetHeight() } onNotify: function(onion) { // If we are processing QML it means the app is open, and as such we don't want to // Send a notification - in the future we should probably use an API like this to Cancel notifications // Until then I am leaving this here for documentation. // androidCwtchActivity.channel = onion // androidCwtchActivity.notification = "Message from " + onion; // Basic Desktop notification windowItem.alert(0) } } Component.onCompleted: Mutant.standard.imagePath = gcd.assetPath; Connections { target: Qt.application onStateChanged: function() { // https://doc.qt.io/qt-5/qt.html#ApplicationState-enum if (Qt.application.state == 4) { // Active gcd.onActivate() } } } }