diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml index a0671e76..3f4bdfea 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml @@ -1,251 +1,252 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton Item { id: root - - property ColorScheme colorScheme - property var user + enum ViewType { + SmallView, + LargeView + } property var _spacing: 12 * ProtonStyle.px - - property color progressColor : { - if (!root.enabled) return root.colorScheme.text_weak - if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak - if (root.user && root.user.isSyncing) return root.colorScheme.text_weak - if (root.progressRatio < .50) return root.colorScheme.signal_success - if (root.progressRatio < .75) return root.colorScheme.signal_warning - return root.colorScheme.signal_danger + property ColorScheme colorScheme + property color progressColor: { + if (!root.enabled) + return root.colorScheme.text_weak; + if (root.type === AccountDelegate.SmallView) + return root.colorScheme.text_weak; + if (root.user && root.user.isSyncing) + return root.colorScheme.text_weak; + if (root.progressRatio < .50) + return root.colorScheme.signal_success; + if (root.progressRatio < .75) + return root.colorScheme.signal_warning; + return root.colorScheme.signal_danger; } property real progressRatio: { if (!root.user) - return 0 - return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes) + return 0; + return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes); } property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0) + property var type: AccountDelegate.SmallView property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0) - - function reasonableFraction(used, total){ - var usedSafe = root.reasonableBytes(used) - var totalSafe = root.reasonableBytes(total) - if (totalSafe == 0 || usedSafe == 0) return 0 - if (totalSafe <= usedSafe) return 1 - return usedSafe / totalSafe - } - - function reasonableBytes(bytes){ - var safeBytes = bytes+0 - if (safeBytes != bytes) return 0 - if (safeBytes < 0) return 0 - return Math.ceil(safeBytes) - } - - function spaceWithUnits(bytes){ - if (bytes*1 !== bytes || bytes == 0 ) return "0 kB" - var units = ['B',"kB", "MB", "GB", "TB"]; - var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024))); - - return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i] - } + property var user function primaryEmail() { - return root.user ? root.user.primaryEmailOrUsername() : "" + return root.user ? root.user.primaryEmailOrUsername() : ""; + } + function reasonableBytes(bytes) { + const safeBytes = bytes + 0; + if (safeBytes !== bytes) + return 0; + if (safeBytes < 0) + return 0; + return Math.ceil(safeBytes); + } + function reasonableFraction(used, total) { + const usedSafe = root.reasonableBytes(used); + const totalSafe = root.reasonableBytes(total); + if (totalSafe === 0 || usedSafe === 0) + return 0; + if (totalSafe <= usedSafe) + return 1; + return usedSafe / totalSafe; + } + function spaceWithUnits(bytes) { + if (bytes * 1 !== bytes || bytes === 0) + return "0 kB"; + const units = ['B', "kB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes * 10 / Math.pow(1024, i)) / 10 + " " + units[i]; } // width expected to be set by parent object - implicitHeight : children[0].implicitHeight - - enum ViewType{ - SmallView, LargeView - } - property var type : AccountDelegate.SmallView + implicitHeight: children[0].implicitHeight RowLayout { spacing: root._spacing anchors { - top: root.top left: root.left right: root.right + top: root.top } - Rectangle { id: avatar - Layout.fillHeight: true Layout.preferredWidth: height - + color: root.colorScheme.background_avatar radius: ProtonStyle.avatar_radius - color: root.colorScheme.background_avatar - Label { - colorScheme: root.colorScheme anchors.fill: parent - text: root.user ? root.user.avatarText.toUpperCase(): "" + color: "#FFFFFF" + colorScheme: root.colorScheme + font.weight: Font.Normal + horizontalAlignment: Qt.AlignHCenter + text: root.user ? root.user.avatarText.toUpperCase() : "" type: { switch (root.type) { - case AccountDelegate.SmallView: return Label.Body - case AccountDelegate.LargeView: return Label.Title + case AccountDelegate.SmallView: + return Label.Body; + case AccountDelegate.LargeView: + return Label.Title; } } - font.weight: Font.Normal - color: "#FFFFFF" - horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter } } - ColumnLayout { id: account Layout.fillHeight: true Layout.fillWidth: true - spacing: 0 Label { id: labelEmail Layout.maximumWidth: root.width - (root._spacing + avatar.width) colorScheme: root.colorScheme + elide: Text.ElideMiddle text: primaryEmail() type: { switch (root.type) { - case AccountDelegate.SmallView: return Label.Body - case AccountDelegate.LargeView: return Label.Title + case AccountDelegate.SmallView: + return Label.Body; + case AccountDelegate.LargeView: + return Label.Title; } } - elide: Text.ElideMiddle MouseArea { id: labelArea - anchors.fill:parent + anchors.fill: parent hoverEnabled: true } - ToolTip { id: toolTipEmail - visible: labelArea.containsMouse && labelEmail.truncated - text: primaryEmail() delay: 1000 + text: primaryEmail() + visible: labelArea.containsMouse && labelEmail.truncated background: Rectangle { border.color: root.colorScheme.background_strong color: root.colorScheme.background_norm } - contentItem: Text { color: root.colorScheme.text_norm text: toolTipEmail.text } } } - - Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 } - + Item { + implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 + } RowLayout { spacing: 0 + Label { + color: root.progressColor colorScheme: root.colorScheme text: { if (!root.user) - return qsTr("Signed out") + return qsTr("Signed out"); switch (root.user.state) { case EUserState.SignedOut: default: - return qsTr("Signed out") + return qsTr("Signed out"); case EUserState.Locked: - return qsTr("Connecting") + dotsTimer.dots + return qsTr("Connecting") + dotsTimer.dots; case EUserState.Connected: if (root.user.isSyncing) - return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots + return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots; else - return root.usedSpace + return root.usedSpace; } } - - Timer { // dots animation while connecting & syncing. - id:dotsTimer - property string dots: "" - interval: 500; - repeat: true; - running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing)) - onTriggered: { - dots += "." - if (dots.length > 3) - dots = "" - } - onRunningChanged: { - dots = "" - } - } - - color: root.progressColor type: { switch (root.type) { - case AccountDelegate.SmallView: return Label.Caption - case AccountDelegate.LargeView: return Label.Body + case AccountDelegate.SmallView: + return Label.Caption; + case AccountDelegate.LargeView: + return Label.Body; + } + } + + Timer { + // dots animation while connecting & syncing. + id: dotsTimer + + property string dots: "" + + interval: 500 + repeat: true + running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing)) + + onRunningChanged: { + dots = ""; + } + onTriggered: { + dots += "."; + if (dots.length > 3) + dots = ""; } } } - Label { - colorScheme: root.colorScheme - text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : "" color: root.colorScheme.text_weak + colorScheme: root.colorScheme + text: root.user && root.user.state === EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : "" type: { switch (root.type) { - case AccountDelegate.SmallView: return Label.Caption - case AccountDelegate.LargeView: return Label.Body + case AccountDelegate.SmallView: + return Label.Caption; + case AccountDelegate.LargeView: + return Label.Body; } } } } - - Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 } - + Item { + implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 + } Rectangle { id: progress_bar - visible: root.user ? root.type == AccountDelegate.LargeView : false - width: 140 * ProtonStyle.px + color: root.colorScheme.border_weak height: 4 * ProtonStyle.px radius: ProtonStyle.progress_bar_radius - color: root.colorScheme.border_weak + visible: root.user ? root.type === AccountDelegate.LargeView : false + width: 140 * ProtonStyle.px Rectangle { id: progress_bar_filled - radius: ProtonStyle.progress_bar_radius color: root.progressColor - visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false + radius: ProtonStyle.progress_bar_radius + visible: root.user ? parent.visible && (root.user.state === EUserState.Connected) : false + width: Math.min(1, Math.max(0.02, root.progressRatio)) * parent.width + anchors { - top : parent.top - bottom : parent.bottom - left : parent.left + bottom: parent.bottom + left: parent.left + top: parent.top } - width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width } } } - Item { Layout.fillWidth: true } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index fa9702a8..ff196d43 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -1,42 +1,35 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton Item { id: root + + property bool _connected: root.user ? root.user.state === EUserState.Connected : false + property int _contentWidth: 640 + property int _detailsMargin: 25 + property int _lineThickness: 1 + property int _spacing: 20 + property int _topMargin: 32 property ColorScheme colorScheme property var notifications property var user - signal showSignIn - signal showSetupGuide(var user, string address) - - property int _contentWidth: 640 - property int _topMargin: 32 - property int _detailsMargin: 25 - property int _spacing: 20 - property int _lineThickness: 1 - property bool _connected: root.user ? root.user.state === EUserState.Connected : false + signal showSignIn Rectangle { anchors.fill: parent @@ -45,6 +38,7 @@ Item { ScrollView { id: scrollView anchors.fill: parent + Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds ColumnLayout { @@ -54,16 +48,16 @@ Item { Rectangle { id: topArea - color: root.colorScheme.background_norm - clip: true Layout.fillWidth: true + clip: true + color: root.colorScheme.background_norm implicitHeight: childrenRect.height ColumnLayout { id: topLayout - width: _contentWidth anchors.horizontalCenter: parent.horizontalCenter spacing: _spacing + width: _contentWidth RowLayout { // account delegate with action buttons @@ -73,83 +67,82 @@ Item { AccountDelegate { Layout.fillWidth: true colorScheme: root.colorScheme - user: root.user - type: AccountDelegate.LargeView enabled: _connected + type: AccountDelegate.LargeView + user: root.user } - Button { Layout.alignment: Qt.AlignTop colorScheme: root.colorScheme - text: qsTr("Sign out") secondary: true + text: qsTr("Sign out") visible: _connected + onClicked: { if (!root.user) return; root.user.logout(); } } - Button { Layout.alignment: Qt.AlignTop colorScheme: root.colorScheme - text: qsTr("Sign in") secondary: true + text: qsTr("Sign in") visible: root.user ? (root.user.state === EUserState.SignedOut) : false + onClicked: { if (!root.user) return; root.showSignIn(); } } - Button { Layout.alignment: Qt.AlignTop colorScheme: root.colorScheme icon.source: "/qml/icons/ic-trash.svg" secondary: true + visible: root.user ? root.user.state !== EUserState.Locked : false + onClicked: { if (!root.user) return; root.notifications.askDeleteAccount(root.user); } - visible: root.user ? root.user.state !== EUserState.Locked : false } } - Rectangle { Layout.fillWidth: true - height: root._lineThickness color: root.colorScheme.border_weak + height: root._lineThickness } - SettingsItem { - colorScheme: root.colorScheme - text: qsTr("Email clients") + Layout.fillWidth: true actionText: qsTr("Configure") + colorScheme: root.colorScheme description: qsTr("Using the mailbox details below (re)configure your client.") + showSeparator: splitMode.visible + text: qsTr("Email clients") type: SettingsItem.Button visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1) - showSeparator: splitMode.visible + onClicked: { if (!root.user) return; root.showSetupGuide(root.user, user.addresses[0]); } - - Layout.fillWidth: true } - SettingsItem { id: splitMode - colorScheme: root.colorScheme - text: qsTr("Split addresses") - description: qsTr("Setup multiple email addresses individually.") - type: SettingsItem.Toggle + Layout.fillWidth: true checked: root.user ? root.user.splitMode : false - visible: _connected && root.user.addresses.length > 1 + colorScheme: root.colorScheme + description: qsTr("Setup multiple email addresses individually.") showSeparator: addressSelector.visible + text: qsTr("Split addresses") + type: SettingsItem.Toggle + visible: _connected && root.user.addresses.length > 1 + onClicked: { if (!splitMode.checked) { root.notifications.askEnableSplitMode(user); @@ -158,26 +151,23 @@ Item { root.user.toggleSplitMode(!splitMode.checked); } } - - Layout.fillWidth: true } - RowLayout { - Layout.fillWidth: true Layout.bottomMargin: _spacing + Layout.fillWidth: true visible: _connected && root.user.splitMode ComboBox { id: addressSelector - colorScheme: root.colorScheme Layout.fillWidth: true + colorScheme: root.colorScheme model: root.user ? root.user.addresses : null } - Button { colorScheme: root.colorScheme - text: qsTr("Configure") secondary: true + text: qsTr("Configure") + onClicked: { if (!root.user) return; @@ -185,25 +175,23 @@ Item { } } } - Rectangle { height: 0 } // just for some extra space before separator } } - Rectangle { id: bottomArea Layout.fillWidth: true - implicitHeight: bottomLayout.implicitHeight color: root.colorScheme.background_weak + implicitHeight: bottomLayout.implicitHeight ColumnLayout { id: bottomLayout - width: _contentWidth anchors.horizontalCenter: parent.horizontalCenter spacing: _spacing visible: _connected + width: _contentWidth Label { Layout.topMargin: _detailsMargin @@ -211,35 +199,34 @@ Item { text: qsTr("Mailbox details") type: Label.Body_semibold } - RowLayout { id: configuration - spacing: _spacing - Layout.fillWidth: true - Layout.fillHeight: true property string currentAddress: addressSelector.displayText - Configuration { - Layout.fillWidth: true - colorScheme: root.colorScheme - title: qsTr("IMAP") - hostname: Backend.hostname - port: Backend.imapPort.toString() - username: configuration.currentAddress - password: root.user ? root.user.password : "" - security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" - } + Layout.fillHeight: true + Layout.fillWidth: true + spacing: _spacing Configuration { Layout.fillWidth: true colorScheme: root.colorScheme - title: qsTr("SMTP") hostname: Backend.hostname - port: Backend.smtpPort.toString() - username: configuration.currentAddress password: root.user ? root.user.password : "" + port: Backend.imapPort.toString() + security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" + title: qsTr("IMAP") + username: configuration.currentAddress + } + Configuration { + Layout.fillWidth: true + colorScheme: root.colorScheme + hostname: Backend.hostname + password: root.user ? root.user.password : "" + port: Backend.smtpPort.toString() security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS" + title: qsTr("SMTP") + username: configuration.currentAddress } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Banner.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Banner.qml index 18c5d856..47a80893 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Banner.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Banner.qml @@ -1,25 +1,19 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton import Notifications @@ -27,34 +21,28 @@ Popup { id: root property ColorScheme colorScheme - property Notification notification property var mainWindow - - topMargin: 37 - leftMargin: (mainWindow.width - root.implicitWidth)/2 + property Notification notification implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin - - popupType: ApplicationWindow.PopupType.Banner - - shouldShow: notification ? (notification.active && !notification.dismissed) : false - + leftMargin: (mainWindow.width - root.implicitWidth) / 2 modal: false + popupType: ApplicationWindow.PopupType.Banner + shouldShow: notification ? (notification.active && !notification.dismissed) : false + topMargin: 37 Action { id: defaultDismissAction - text: qsTr("OK") + onTriggered: { if (!root.notification) { - return + return; } - - root.notification.dismissed = true + root.notification.dismissed = true; } } - RowLayout { id: contentLayout anchors.fill: parent @@ -63,170 +51,148 @@ Popup { Item { Layout.fillHeight: true Layout.fillWidth: true - clip: true implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin Rectangle { - anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left - width: parent.width + 10 - radius: ProtonStyle.banner_radius + anchors.top: parent.top color: { if (!root.notification) { - return "transparent" + return "transparent"; } - switch (root.notification.type) { - case Notification.NotificationType.Info: - return root.colorScheme.signal_info - case Notification.NotificationType.Success: - return root.colorScheme.signal_success - case Notification.NotificationType.Warning: - return root.colorScheme.signal_warning - case Notification.NotificationType.Danger: - return root.colorScheme.signal_danger + case Notification.NotificationType.Info: + return root.colorScheme.signal_info; + case Notification.NotificationType.Success: + return root.colorScheme.signal_success; + case Notification.NotificationType.Warning: + return root.colorScheme.signal_warning; + case Notification.NotificationType.Danger: + return root.colorScheme.signal_danger; } } + radius: ProtonStyle.banner_radius + width: parent.width + 10 } - RowLayout { - anchors.fill: parent - - anchors.topMargin: 14 anchors.bottomMargin: 14 + anchors.fill: parent anchors.leftMargin: 16 - + anchors.topMargin: 14 spacing: 8 ColorImage { - color: root.colorScheme.text_invert - width: 24 - height: 24 - - sourceSize.width: 24 - sourceSize.height: 24 - Layout.preferredHeight: 24 Layout.preferredWidth: 24 - + color: root.colorScheme.text_invert + height: 24 source: { if (!root.notification) { - return "" + return ""; } - switch (root.notification.type) { - case Notification.NotificationType.Info: - return "/qml/icons/ic-info-circle-filled.svg" - case Notification.NotificationType.Success: - return "/qml/icons/ic-info-circle-filled.svg" - case Notification.NotificationType.Warning: - return "/qml/icons/ic-exclamation-circle-filled.svg" - case Notification.NotificationType.Danger: - return "/qml/icons/ic-exclamation-circle-filled.svg" + case Notification.NotificationType.Info: + return "/qml/icons/ic-info-circle-filled.svg"; + case Notification.NotificationType.Success: + return "/qml/icons/ic-info-circle-filled.svg"; + case Notification.NotificationType.Warning: + return "/qml/icons/ic-exclamation-circle-filled.svg"; + case Notification.NotificationType.Danger: + return "/qml/icons/ic-exclamation-circle-filled.svg"; } } + sourceSize.height: 24 + sourceSize.width: 24 + width: 24 } - Label { - colorScheme: root.colorScheme - Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true Layout.leftMargin: 16 - color: root.colorScheme.text_invert + colorScheme: root.colorScheme text: root.notification ? root.notification.description : "" - wrapMode: Text.WordWrap } } } - Rectangle { Layout.fillHeight: true - width: 1 color: { if (!root.notification) { - return "transparent" + return "transparent"; } - switch (root.notification.type) { - case Notification.NotificationType.Info: - return root.colorScheme.signal_info_active - case Notification.NotificationType.Success: - return root.colorScheme.signal_success_active - case Notification.NotificationType.Warning: - return root.colorScheme.signal_warning_active - case Notification.NotificationType.Danger: - return root.colorScheme.signal_danger_active + case Notification.NotificationType.Info: + return root.colorScheme.signal_info_active; + case Notification.NotificationType.Success: + return root.colorScheme.signal_success_active; + case Notification.NotificationType.Warning: + return root.colorScheme.signal_warning_active; + case Notification.NotificationType.Danger: + return root.colorScheme.signal_danger_active; } } + width: 1 } - Button { - colorScheme: root.colorScheme - Layout.fillHeight: true - id: actionButton - + Layout.fillHeight: true action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction + colorScheme: root.colorScheme background: Item { clip: true + Rectangle { - anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right - width: parent.width + 10 - radius: ProtonStyle.banner_radius + anchors.top: parent.top color: { if (!root.notification) { - return "transparent" + return "transparent"; } - - var norm - var hover - var active - + let norm; + let hover; + let active; switch (root.notification.type) { - case Notification.NotificationType.Info: - norm = root.colorScheme.signal_info - hover = root.colorScheme.signal_info_hover - active = root.colorScheme.signal_info_active + case Notification.NotificationType.Info: + norm = root.colorScheme.signal_info; + hover = root.colorScheme.signal_info_hover; + active = root.colorScheme.signal_info_active; break; - case Notification.NotificationType.Success: - norm = root.colorScheme.signal_success - hover = root.colorScheme.signal_success_hover - active = root.colorScheme.signal_success_active + case Notification.NotificationType.Success: + norm = root.colorScheme.signal_success; + hover = root.colorScheme.signal_success_hover; + active = root.colorScheme.signal_success_active; break; - case Notification.NotificationType.Warning: - norm = root.colorScheme.signal_warning - hover = root.colorScheme.signal_warning_hover - active = root.colorScheme.signal_warning_active + case Notification.NotificationType.Warning: + norm = root.colorScheme.signal_warning; + hover = root.colorScheme.signal_warning_hover; + active = root.colorScheme.signal_warning_active; break; - case Notification.NotificationType.Danger: - norm = root.colorScheme.signal_danger - hover = root.colorScheme.signal_danger_hover - active = root.colorScheme.signal_danger_active + case Notification.NotificationType.Danger: + norm = root.colorScheme.signal_danger; + hover = root.colorScheme.signal_danger_hover; + active = root.colorScheme.signal_danger_active; break; } - if (actionButton.down) { - return active + return active; } - if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) { - return hover + return hover; } - if (actionButton.loading) { - return hover + return hover; } - - return norm + return norm; } + radius: ProtonStyle.banner_radius + width: parent.width + 10 } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 3e377ad7..1f1a6c4c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -1,129 +1,112 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Window import Qt.labs.platform - import Proton import Notifications QtObject { id: root - function bound(num, lowerLimit, upperLimit) { - return Math.max(lowerLimit, Math.min(upperLimit, num)) + property MainWindow _mainWindow: MainWindow { + id: mainWindow + notifications: root._notifications + title: root.title + visible: false + + onVisibleChanged: { + Backend.dockIconVisible = visible; + } + + Connections { + function onColorSchemeNameChanged(scheme) { + root.setColorScheme(); + } + function onDiskCacheUnavailable() { + mainWindow.showAndRise(); + } + function onHideMainWindow() { + mainWindow.hide(); + } + + target: Backend + } } - - property var title: Backend.appname - property Notifications _notifications: Notifications { id: notifications frontendMain: mainWindow } - property NotificationFilter _trayNotificationFilter: NotificationFilter { - id: trayNotificationFilter - source: root._notifications ? root._notifications.all : undefined - onTopmostChanged: { - if (topmost) { - switch (topmost.type) { - case Notification.NotificationType.Danger: - Backend.setErrorTrayIcon(topmost.brief, topmost.icon) - return - case Notification.NotificationType.Warning: - Backend.setWarnTrayIcon(topmost.brief, topmost.icon) - return - case Notification.NotificationType.Info: - Backend.setUpdateTrayIcon(topmost.brief, topmost.icon) - return + id: trayNotificationFilter + source: root._notifications ? root._notifications.all : undefined + + onTopmostChanged: { + if (topmost) { + switch (topmost.type) { + case Notification.NotificationType.Danger: + Backend.setErrorTrayIcon(topmost.brief, topmost.icon); + return; + case Notification.NotificationType.Warning: + Backend.setWarnTrayIcon(topmost.brief, topmost.icon); + return; + case Notification.NotificationType.Info: + Backend.setUpdateTrayIcon(topmost.brief, topmost.icon); + return; } } - Backend.setNormalTrayIcon() + Backend.setNormalTrayIcon(); } } + property var title: Backend.appname - - property MainWindow _mainWindow: MainWindow { - id: mainWindow - visible: false - - title: root.title - notifications: root._notifications - - onVisibleChanged: { - Backend.dockIconVisible = visible - } - - Connections { - target: Backend - function onDiskCacheUnavailable() { - mainWindow.showAndRise() - } - function onColorSchemeNameChanged(scheme) { root.setColorScheme() } - - function onHideMainWindow() { - mainWindow.hide(); - } - } + function bound(num, lowerLimit, upperLimit) { + return Math.max(lowerLimit, Math.min(upperLimit, num)); + } + function setColorScheme() { + if (Backend.colorSchemeName === "light") + ProtonStyle.currentStyle = ProtonStyle.lightStyle; + if (Backend.colorSchemeName === "dark") + ProtonStyle.currentStyle = ProtonStyle.darkStyle; } - Component.onCompleted: { if (!Backend) { - console.log("Backend not loaded") + console.log("Backend not loaded"); } - - root.setColorScheme() - - + root.setColorScheme(); if (!Backend.users) { - console.log("users not loaded") + console.log("users not loaded"); } - - var c = Backend.users.count - var u = Backend.users.get(0) + const c = Backend.users.count; + const u = Backend.users.get(0); // DEBUG if (c !== 0) { - console.log("users non zero", c) - console.log("first user", u ) + console.log("users non zero", c); + console.log("first user", u); } - if (c === 0) { - mainWindow.showAndRise() + mainWindow.showAndRise(); } - if (u) { if (c === 1 && (u.state === EUserState.SignedOut)) { - mainWindow.showAndRise() + mainWindow.showAndRise(); } } - - Backend.guiReady() - - if (Backend.showOnStartup || Backend.showSplashScreen) { - mainWindow.showAndRise() + Backend.guiReady(); + if (Backend.showOnStartup || Backend.showSplashScreen) { + mainWindow.showAndRise(); } - - } - - function setColorScheme() { - if (Backend.colorSchemeName === "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle - if (Backend.colorSchemeName === "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/BugReportView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/BugReportView.qml index fc566eec..1f7abc73 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/BugReportView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/BugReportView.qml @@ -1,202 +1,175 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton SettingsView { id: root - fillHeight: true - property var selectedAddress - signal bugReportWasSent() + signal bugReportWasSent - Label { - text: qsTr("Report a problem") - colorScheme: root.colorScheme - type: Label.Heading + function isValidEmail(text) { + const reEmail = /^[^@]+@[^@]+\.[A-Za-z]+\s*$/; + return reEmail.test(text); + } + function setDefaultValue() { + description.text = ""; + address.text = root.selectedAddress; + emailClient.text = Backend.currentEmailClient; + includeLogs.checked = true; + } + function setDescription(message) { + description.text = message; + } + function submit() { + sendButton.loading = true; + Backend.reportBug(description.text, address.text, emailClient.text, includeLogs.checked); } + fillHeight: true + onVisibleChanged: { + root.setDefaultValue(); + } + + Label { + colorScheme: root.colorScheme + text: qsTr("Report a problem") + type: Label.Heading + } TextArea { id: description - property int _minLength: 150 + property int _maxLength: 800 - - label: qsTr("Description") - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: heightForLinesVisible(4) - hint: description.text.length + "/" + _maxLength - placeholderText: qsTr("Tell us what went wrong or isn't working (min. %1 characters).").arg(_minLength) - - validator: function(text) { - if (description.text.length < description._minLength) { - return qsTr("Enter a problem description (min. %1 characters).").arg(_minLength) - } - - if (description.text.length > description._maxLength) { - return qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength) - } - - return - } - - onTextChanged: { - // Rise max length error immediately while typing - if (description.text.length > description._maxLength) { - validate() - } - } + property int _minLength: 150 KeyNavigation.priority: KeyNavigation.BeforeItem KeyNavigation.tab: address + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumHeight: heightForLinesVisible(4) + colorScheme: root.colorScheme + hint: description.text.length + "/" + _maxLength // set implicitHeight to explicit height because se don't // want TextArea implicitHeight (which is height of all text) // to be considered in SettingsView internal scroll view implicitHeight: height + label: qsTr("Description") + placeholderText: qsTr("Tell us what went wrong or isn't working (min. %1 characters).").arg(_minLength) + validator: function (text) { + if (description.text.length < description._minLength) { + return qsTr("Enter a problem description (min. %1 characters).").arg(_minLength); + } + if (description.text.length > description._maxLength) { + return qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength); + } + return; + } + + onTextChanged: { + // Rise max length error immediately while typing + if (description.text.length > description._maxLength) { + validate(); + } + } } - - TextField { id: address - - label: qsTr("Your contact email") - colorScheme: root.colorScheme Layout.fillWidth: true + colorScheme: root.colorScheme + label: qsTr("Your contact email") placeholderText: qsTr("e.g. jane.doe@protonmail.com") - - validator: function(str) { + validator: function (str) { if (!isValidEmail(str)) { - return qsTr("Enter valid email address") + return qsTr("Enter valid email address"); } - return + return; } } - TextField { id: emailClient - - label: qsTr("Your email client (including version)") - colorScheme: root.colorScheme Layout.fillWidth: true + colorScheme: root.colorScheme + label: qsTr("Your email client (including version)") placeholderText: qsTr("e.g. Apple Mail 14.0") - - validator: function(str) { + validator: function (str) { if (str.length === 0) { - return qsTr("Enter an email client name and version") + return qsTr("Enter an email client name and version"); } - return + return; } } - - RowLayout { CheckBox { id: includeLogs - text: qsTr("Include my recent logs") - colorScheme: root.colorScheme checked: true + colorScheme: root.colorScheme + text: qsTr("Include my recent logs") } Button { Layout.leftMargin: 12 - text: qsTr("View logs") - secondary: true colorScheme: root.colorScheme + secondary: true + text: qsTr("View logs") + onClicked: Qt.openUrlExternally(Backend.logsPath) } } - TextEdit { - text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.") - - readOnly: true - Layout.fillWidth: true color: root.colorScheme.text_weak font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.caption_font_size font.letterSpacing: ProtonStyle.caption_letter_spacing + font.pixelSize: ProtonStyle.caption_font_size + font.weight: ProtonStyle.fontWeight_400 + readOnly: true + selectByMouse: true + selectedTextColor: root.colorScheme.text_invert // No way to set lineHeight: ProtonStyle.caption_line_height selectionColor: root.colorScheme.interaction_norm - selectedTextColor: root.colorScheme.text_invert + text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.") wrapMode: Text.WordWrap - selectByMouse: true } - Button { id: sendButton - text: qsTr("Send") colorScheme: root.colorScheme enabled: !loading + text: qsTr("Send") + onClicked: { - description.validate() - address.validate() - emailClient.validate() - + description.validate(); + address.validate(); + emailClient.validate(); if (description.error || address.error || emailClient.error) { - return + return; } - - submit() + submit(); } Connections { + function onBugReportSendSuccess() { + root.bugReportWasSent(); + } + function onReportBugFinished() { + sendButton.loading = false; + } + target: Backend - function onReportBugFinished() { sendButton.loading = false } - function onBugReportSendSuccess() { root.bugReportWasSent() } } } - - function setDescription(message) { - description.text = message - } - - function setDefaultValue() { - description.text = "" - address.text = root.selectedAddress - emailClient.text = Backend.currentEmailClient - includeLogs.checked = true - } - - function isValidEmail(text){ - var reEmail = /^[^@]+@[^@]+\.[A-Za-z]+\s*$/ - return reEmail.test(text) - } - - function submit() { - sendButton.loading = true - Backend.reportBug( - description.text, - address.text, - emailClient.text, - includeLogs.checked - ) - } - - onVisibleChanged: { - root.setDefaultValue() - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml index 84121e81..c162dcfc 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml @@ -1,71 +1,80 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton Rectangle { id: root + property int _margin: 24 property ColorScheme colorScheme - property string title property string hostname - property string port - property string username property string password + property string port property string security - - implicitWidth: 304 - implicitHeight: content.height + 2*root._margin + property string title + property string username color: root.colorScheme.background_norm + implicitHeight: content.height + 2 * root._margin + implicitWidth: 304 radius: ProtonStyle.card_radius - property int _margin: 24 - ColumnLayout { id: content - width: root.width - 2*root._margin - anchors{ - top: root.top - left: root.left - leftMargin : root._margin - rightMargin : root._margin - topMargin : root._margin - bottomMargin : root._margin - } - spacing: 12 + width: root.width - 2 * root._margin + anchors { + bottomMargin: root._margin + left: root.left + leftMargin: root._margin + rightMargin: root._margin + top: root.top + topMargin: root._margin + } Label { colorScheme: root.colorScheme text: root.title type: Label.Body_semibold } - - ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname } - ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port } - ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username } - ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Password") ; value: root.password } - ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Security") ; value: root.security } + ConfigurationItem { + colorScheme: root.colorScheme + label: qsTr("Hostname") + value: root.hostname + } + ConfigurationItem { + colorScheme: root.colorScheme + label: qsTr("Port") + value: root.port + } + ConfigurationItem { + colorScheme: root.colorScheme + label: qsTr("Username") + value: root.username + } + ConfigurationItem { + colorScheme: root.colorScheme + label: qsTr("Password") + value: root.password + } + ConfigurationItem { + colorScheme: root.colorScheme + label: qsTr("Security") + value: root.security + } } } - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml index 80732855..55cf2634 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml @@ -1,35 +1,29 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton Item { id: root - Layout.fillWidth: true property var colorScheme property string label property string value + Layout.fillWidth: true implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth @@ -47,45 +41,42 @@ Item { } TextEdit { id: valueText - text: root.value + Layout.fillWidth: true color: root.colorScheme.text_weak readOnly: true - selectByMouse: true selectByKeyboard: true + selectByMouse: true selectionColor: root.colorScheme.text_weak + text: root.value wrapMode: Text.WrapAnywhere - Layout.fillWidth: true } } - Item { Layout.fillWidth: true } - ColorImage { - source: "/qml/icons/ic-copy.svg" color: root.colorScheme.text_norm height: root.colorScheme.body_font_size + source: "/qml/icons/ic-copy.svg" sourceSize.height: root.colorScheme.body_font_size MouseArea { anchors.fill: parent - onClicked : { - valueText.select(0, valueText.length) - valueText.copy() - valueText.deselect() + + onClicked: { + valueText.select(0, valueText.length); + valueText.copy(); + valueText.deselect(); } onPressed: parent.scale = 0.90 onReleased: parent.scale = 1 } - } } - Rectangle { Layout.fillWidth: true - height: 1 color: root.colorScheme.border_norm + height: 1 } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ConnectionModeSettings.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ConnectionModeSettings.qml index 5ab2e8f2..40daf623 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ConnectionModeSettings.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ConnectionModeSettings.qml @@ -1,155 +1,138 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton SettingsView { id: root + function setDefaultValues() { + imapSSLButton.checked = Backend.useSSLForIMAP; + imapSTARTTLSButton.checked = !Backend.useSSLForIMAP; + smtpSSLButton.checked = Backend.useSSLForSMTP; + smtpSTARTTLSButton.checked = !Backend.useSSLForSMTP; + } + function submit() { + submitButton.loading = true; + Backend.setMailServerSettings(Backend.imapPort, Backend.smtpPort, imapSSLButton.checked, smtpSSLButton.checked); + } fillHeight: false + onVisibleChanged: { + root.setDefaultValues(); + } + Label { + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Connection mode") type: Label.Heading - Layout.fillWidth: true } - Label { + Layout.fillWidth: true + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.") type: Label.Body - color: root.colorScheme.text_weak - Layout.fillWidth: true wrapMode: Text.WordWrap } - ColumnLayout { spacing: 16 - ButtonGroup{ id: imapProtocolSelection } - + ButtonGroup { + id: imapProtocolSelection + } Label { colorScheme: root.colorScheme text: qsTr("IMAP connection") } - RadioButton { id: imapSSLButton - colorScheme: root.colorScheme ButtonGroup.group: imapProtocolSelection + colorScheme: root.colorScheme text: qsTr("SSL") } - RadioButton { id: imapSTARTTLSButton - colorScheme: root.colorScheme ButtonGroup.group: imapProtocolSelection + colorScheme: root.colorScheme text: qsTr("STARTTLS") } } - Rectangle { Layout.fillWidth: true - height: 1 color: root.colorScheme.border_weak + height: 1 } - ColumnLayout { spacing: 16 - ButtonGroup{ id: smtpProtocolSelection } - + ButtonGroup { + id: smtpProtocolSelection + } Label { colorScheme: root.colorScheme text: qsTr("SMTP connection") } - RadioButton { id: smtpSSLButton - colorScheme: root.colorScheme ButtonGroup.group: smtpProtocolSelection + colorScheme: root.colorScheme text: qsTr("SSL") } - RadioButton { id: smtpSTARTTLSButton - colorScheme: root.colorScheme ButtonGroup.group: smtpProtocolSelection + colorScheme: root.colorScheme text: qsTr("STARTTLS") } } - Rectangle { Layout.fillWidth: true - height: 1 color: root.colorScheme.border_weak + height: 1 } - RowLayout { spacing: 12 Button { id: submitButton colorScheme: root.colorScheme - text: qsTr("Save") - onClicked: { - submitButton.loading = true - root.submit() - } - enabled: (!loading) && ((imapSSLButton.checked !== Backend.useSSLForIMAP) || (smtpSSLButton.checked !== Backend.useSSLForSMTP)) - } + text: qsTr("Save") + onClicked: { + submitButton.loading = true; + root.submit(); + } + } Button { colorScheme: root.colorScheme - text: qsTr("Cancel") - onClicked: root.back() secondary: true - } + text: qsTr("Cancel") + onClicked: root.back() + } Connections { - target: Backend - function onChangeMailServerSettingsFinished() { - submitButton.loading = false - root.back() + submitButton.loading = false; + root.back(); } + + target: Backend } } - - function submit(){ - submitButton.loading = true - Backend.setMailServerSettings(Backend.imapPort, Backend.smtpPort, imapSSLButton.checked, smtpSSLButton.checked) - } - - function setDefaultValues(){ - imapSSLButton.checked = Backend.useSSLForIMAP - imapSTARTTLSButton.checked = !Backend.useSSLForIMAP - smtpSSLButton.checked = Backend.useSSLForSMTP - smtpSTARTTLSButton.checked = !Backend.useSSLForSMTP - } - - onVisibleChanged: { - root.setDefaultValues() - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 6dc46d79..eca31c53 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -1,36 +1,62 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton import Notifications Item { id: root - property ColorScheme colorScheme + property ColorScheme colorScheme property var notifications + signal closeWindow + signal quitBridge signal showSetupGuide(var user, string address) - signal closeWindow() - signal quitBridge() + + function selectUser(userID) { + const users = Backend.users; + for (let i = 0; i < users.count; i++) { + const user = users.get(i); + if (user.id !== userID) { + continue; + } + accounts.currentIndex = i; + if (user.state === EUserState.SignedOut) + showSignIn(user.primaryEmailOrUsername()); + return; + } + console.error("User with ID ", userID, " was not found in the account list"); + } + function showBugReportAndPrefill(description) { + rightContent.showBugReport(); + bugReport.setDescription(description); + } + function showHelp() { + rightContent.showHelpView(); + } + function showLocalCacheSettings() { + rightContent.showLocalCacheSettings(); + } + function showSettings() { + rightContent.showGeneralSettings(); + } + function showSignIn(username) { + signIn.username = username; + rightContent.showSignIn(); + } RowLayout { anchors.fill: parent @@ -38,13 +64,13 @@ Item { Rectangle { id: leftBar + property ColorScheme colorScheme: root.colorScheme.prominent - Layout.minimumWidth: 264 - Layout.maximumWidth: 320 - Layout.preferredWidth: 320 Layout.fillHeight: true - + Layout.maximumWidth: 320 + Layout.minimumWidth: 264 + Layout.preferredWidth: 320 color: colorScheme.background_norm ColumnLayout { @@ -52,24 +78,21 @@ Item { spacing: 0 RowLayout { - id:topLeftBar - + id: topLeftBar Layout.fillWidth: true - Layout.minimumHeight: 60 Layout.maximumHeight: 60 + Layout.minimumHeight: 60 Layout.preferredHeight: 60 spacing: 0 Status { + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 17 Layout.leftMargin: 16 Layout.topMargin: 24 - Layout.bottomMargin: 17 - Layout.alignment: Qt.AlignHCenter - colorScheme: leftBar.colorScheme - notifications: root.notifications - notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate + notifications: root.notifications } // just a placeholder @@ -77,47 +100,38 @@ Item { Layout.fillHeight: true Layout.fillWidth: true } - Button { - colorScheme: leftBar.colorScheme - Layout.minimumHeight: 36 - Layout.maximumHeight: 36 - Layout.preferredHeight: 36 - Layout.minimumWidth: 36 - Layout.maximumWidth: 36 - Layout.preferredWidth: 36 - - Layout.topMargin: 16 Layout.bottomMargin: 9 + Layout.maximumHeight: 36 + Layout.maximumWidth: 36 + Layout.minimumHeight: 36 + Layout.minimumWidth: 36 + Layout.preferredHeight: 36 + Layout.preferredWidth: 36 Layout.rightMargin: 4 - + Layout.topMargin: 16 + colorScheme: leftBar.colorScheme horizontalPadding: 0 - icon.source: "/qml/icons/ic-question-circle.svg" onClicked: rightContent.showHelpView() } - Button { - colorScheme: leftBar.colorScheme - Layout.minimumHeight: 36 - Layout.maximumHeight: 36 - Layout.preferredHeight: 36 - Layout.minimumWidth: 36 - Layout.maximumWidth: 36 - Layout.preferredWidth: 36 - - Layout.topMargin: 16 Layout.bottomMargin: 9 + Layout.maximumHeight: 36 + Layout.maximumWidth: 36 + Layout.minimumHeight: 36 + Layout.minimumWidth: 36 + Layout.preferredHeight: 36 + Layout.preferredWidth: 36 Layout.rightMargin: 4 - + Layout.topMargin: 16 + colorScheme: leftBar.colorScheme horizontalPadding: 0 - icon.source: "/qml/icons/ic-cog-wheel.svg" onClicked: rightContent.showGeneralSettings() } - Button { id: dotMenuButton Layout.bottomMargin: 9 @@ -134,7 +148,7 @@ Item { icon.source: "/qml/icons/ic-three-dots-vertical.svg" onClicked: { - dotMenu.open() + dotMenu.open(); } Menu { @@ -143,332 +157,319 @@ Item { modal: true y: dotMenuButton.Layout.preferredHeight + dotMenuButton.Layout.bottomMargin + onClosed: { + parent.checked = false; + } + onOpened: { + parent.checked = true; + } + MenuItem { colorScheme: root.colorScheme text: qsTr("Close window") + onClicked: { - root.closeWindow() + root.closeWindow(); } } MenuItem { colorScheme: root.colorScheme text: qsTr("Quit Bridge") - onClicked: { - root.quitBridge() - } - } - onClosed: { - parent.checked = false - } - onOpened: { - parent.checked = true + onClicked: { + root.quitBridge(); + } } } } } - - Item {implicitHeight:10} + Item { + implicitHeight: 10 + } // Separator line Rectangle { Layout.fillWidth: true - Layout.minimumHeight: 1 Layout.maximumHeight: 1 + Layout.minimumHeight: 1 color: leftBar.colorScheme.border_weak } - ListView { id: accounts - property var _topBottomMargins: 24 property var _leftRightMargins: 16 + property var _topBottomMargins: 24 - Layout.fillWidth: true + Layout.bottomMargin: accounts._topBottomMargins Layout.fillHeight: true + Layout.fillWidth: true Layout.leftMargin: accounts._leftRightMargins Layout.rightMargin: accounts._leftRightMargins Layout.topMargin: accounts._topBottomMargins - Layout.bottomMargin: accounts._topBottomMargins - - spacing: 12 - clip: true boundsBehavior: Flickable.StopAtBounds + clip: true + model: Backend.users + spacing: 12 - header: Rectangle { - height: headerLabel.height+16 - // color: ProtonStyle.transparent - Label{ + delegate: Item { + implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin + implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin + width: leftBar.width - 2 * accounts._leftRightMargins + + AccountDelegate { + id: accountDelegate + anchors.bottomMargin: 8 + anchors.fill: parent + anchors.leftMargin: 12 + anchors.rightMargin: 12 + anchors.topMargin: 8 colorScheme: leftBar.colorScheme + user: Backend.users.get(index) + } + MouseArea { + anchors.fill: parent + + onClicked: { + const user = Backend.users.get(index); + accounts.currentIndex = index; + if (!user) + return; + if (user.state !== EUserState.SignedOut) { + rightContent.showAccount(); + } else { + signIn.username = user.primaryEmailOrUsername(); + rightContent.showSignIn(); + } + } + } + } + header: Rectangle { + height: headerLabel.height + 16 + + // color: ProtonStyle.transparent + Label { id: headerLabel + colorScheme: leftBar.colorScheme text: qsTr("Accounts") type: Label.LabelType.Body } } - highlight: Rectangle { color: leftBar.colorScheme.interaction_default_active radius: ProtonStyle.account_row_radius } - - model: Backend.users - delegate: Item { - width: leftBar.width - 2*accounts._leftRightMargins - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin - implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin - - AccountDelegate { - id: accountDelegate - - anchors.fill: parent - anchors.topMargin: 8 - anchors.bottomMargin: 8 - anchors.leftMargin: 12 - anchors.rightMargin: 12 - - colorScheme: leftBar.colorScheme - user: Backend.users.get(index) - } - - MouseArea { - anchors.fill: parent - onClicked: { - var user = Backend.users.get(index) - accounts.currentIndex = index - if (!user) return - if (user.state !== EUserState.SignedOut) { - rightContent.showAccount() - } else { - signIn.username = user.primaryEmailOrUsername() - rightContent.showSignIn() - } - } - } - } } // Separator Rectangle { Layout.fillWidth: true - Layout.minimumHeight: 1 Layout.maximumHeight: 1 + Layout.minimumHeight: 1 color: leftBar.colorScheme.border_weak } - Item { id: bottomLeftBar - Layout.fillWidth: true - Layout.minimumHeight: 52 Layout.maximumHeight: 52 + Layout.minimumHeight: 52 Layout.preferredHeight: 52 Button { - colorScheme: leftBar.colorScheme - width: 36 - height: 36 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 16 + anchors.top: parent.top anchors.topMargin: 7 - + colorScheme: leftBar.colorScheme + height: 36 horizontalPadding: 0 - icon.source: "/qml/icons/ic-plus.svg" + width: 36 onClicked: { - signIn.username = "" - rightContent.showSignIn() + signIn.username = ""; + rightContent.showSignIn(); } } } } } - - Rectangle { // right content background + Rectangle { + Layout.fillHeight: true // right content background Layout.fillWidth: true - Layout.fillHeight: true - color: colorScheme.background_norm StackLayout { id: rightContent + function showAccount(index) { + if (index !== undefined && index >= 0) { + accounts.currentIndex = index; + } + rightContent.currentIndex = 0; + } + function showBugReport() { + rightContent.currentIndex = 8; + } + function showConnectionModeSettings() { + rightContent.currentIndex = 5; + } + function showGeneralSettings() { + rightContent.currentIndex = 2; + } + function showHelpView() { + rightContent.currentIndex = 7; + } + function showKeychainSettings() { + rightContent.currentIndex = 3; + } + function showLocalCacheSettings() { + rightContent.currentIndex = 6; + } + function showPortSettings() { + rightContent.currentIndex = 4; + } + function showSignIn() { + rightContent.currentIndex = 1; + signIn.focus = true; + } + anchors.fill: parent - AccountView { // 0 + AccountView { + // 0 colorScheme: root.colorScheme notifications: root.notifications user: { - if (accounts.currentIndex < 0) return undefined - if (Backend.users.count == 0) return undefined - return Backend.users.get(accounts.currentIndex) + if (accounts.currentIndex < 0) + return undefined; + if (Backend.users.count === 0) + return undefined; + return Backend.users.get(accounts.currentIndex); + } + + onShowSetupGuide: function (user, address) { + root.showSetupGuide(user, address); } onShowSignIn: { - var user = this.user - signIn.username = user ? user.primaryEmailOrUsername() : "" - rightContent.showSignIn() - } - onShowSetupGuide: function(user, address) { - root.showSetupGuide(user,address) + const user = this.user; + signIn.username = user ? user.primaryEmailOrUsername() : ""; + rightContent.showSignIn(); } } - - GridLayout { // 1 Sign In + GridLayout { + // 1 Sign In columns: 2 Button { id: backButton + Layout.alignment: Qt.AlignTop Layout.leftMargin: 18 Layout.topMargin: 10 - Layout.alignment: Qt.AlignTop - colorScheme: root.colorScheme - onClicked: { - signIn.abort() - rightContent.showAccount() - } + horizontalPadding: 8 icon.source: "/qml/icons/ic-arrow-left.svg" secondary: true - horizontalPadding: 8 - } + onClicked: { + signIn.abort(); + rightContent.showAccount(); + } + } SignIn { id: signIn - Layout.topMargin: 68 - Layout.leftMargin: 80 - backButton.width - 18 - Layout.rightMargin: 80 Layout.bottomMargin: 68 - Layout.preferredWidth: 320 - Layout.fillWidth: true Layout.fillHeight: true - + Layout.fillWidth: true + Layout.leftMargin: 80 - backButton.width - 18 + Layout.preferredWidth: 320 + Layout.rightMargin: 80 + Layout.topMargin: 68 colorScheme: root.colorScheme } } - - GeneralSettings { // 2 + GeneralSettings { + // 2 colorScheme: root.colorScheme notifications: root.notifications onBack: { - rightContent.showAccount() + rightContent.showAccount(); } } - - KeychainSettings { // 3 + KeychainSettings { + // 3 colorScheme: root.colorScheme onBack: { - rightContent.showGeneralSettings() + rightContent.showGeneralSettings(); } } - - PortSettings { // 4 - colorScheme: root.colorScheme - notifications: root.notifications - onBack: { - rightContent.showGeneralSettings() - } - } - - ConnectionModeSettings { // 5 - colorScheme: root.colorScheme - - onBack: { - rightContent.showGeneralSettings() - } - } - - LocalCacheSettings { // 6 + PortSettings { + // 4 colorScheme: root.colorScheme notifications: root.notifications onBack: { - rightContent.showGeneralSettings() + rightContent.showGeneralSettings(); } } - - HelpView { // 7 + ConnectionModeSettings { + // 5 colorScheme: root.colorScheme onBack: { - rightContent.showAccount() + rightContent.showGeneralSettings(); } } + LocalCacheSettings { + // 6 + colorScheme: root.colorScheme + notifications: root.notifications - BugReportView { // 8 + onBack: { + rightContent.showGeneralSettings(); + } + } + HelpView { + // 7 + colorScheme: root.colorScheme + + onBack: { + rightContent.showAccount(); + } + } + BugReportView { + // 8 id: bugReport colorScheme: root.colorScheme selectedAddress: { - if (accounts.currentIndex < 0) return "" - if (Backend.users.count == 0) return "" - var user = Backend.users.get(accounts.currentIndex) - if (!user) return "" - return user.addresses[0] + if (accounts.currentIndex < 0) + return ""; + if (Backend.users.count === 0) + return ""; + const user = Backend.users.get(accounts.currentIndex); + if (!user) + return ""; + return user.addresses[0]; } onBack: { - rightContent.showHelpView() + rightContent.showHelpView(); } - onBugReportWasSent: { - rightContent.showAccount() + rightContent.showAccount(); } } - - function showAccount(index) { - if (index !== undefined && index >= 0){ - accounts.currentIndex = index - } - rightContent.currentIndex = 0 - } - - function showSignIn () { rightContent.currentIndex = 1; signIn.focus = true } - function showGeneralSettings () { rightContent.currentIndex = 2 } - function showKeychainSettings () { rightContent.currentIndex = 3 } - function showPortSettings () { rightContent.currentIndex = 4 } - function showConnectionModeSettings() { rightContent.currentIndex = 5 } - function showLocalCacheSettings () { rightContent.currentIndex = 6 } - function showHelpView () { rightContent.currentIndex = 7 } - function showBugReport () { rightContent.currentIndex = 8 } - Connections { - target: Backend + function onLoginAlreadyLoggedIn(index) { + rightContent.showAccount(index); + } + function onLoginFinished(index) { + rightContent.showAccount(index); + } - function onLoginFinished(index) { rightContent.showAccount(index) } - function onLoginAlreadyLoggedIn(index) { rightContent.showAccount(index) } + target: Backend } } } } - - function showLocalCacheSettings(){rightContent.showLocalCacheSettings() } - function showSettings(){rightContent.showGeneralSettings() } - function showHelp(){rightContent.showHelpView() } - function showSignIn(username){ - signIn.username = username - rightContent.showSignIn() - } - - function selectUser(userID) { - var users = Backend.users; - for (var i = 0; i < users.count; i++) { - var user = users.get(i) - if (user.id !== userID) { - continue; - } - accounts.currentIndex = i; - if (user.state === EUserState.SignedOut) - showSignIn(user.primaryEmailOrUsername()) - return; - } - console.error("User with ID ", userID, " was not found in the account list") - } - - function showBugReportAndPrefill(description) { - rightContent.showBugReport() - bugReport.setDescription(description) - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/DebugWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/DebugWrapper.qml index fafb9a5d..eb8bd15c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/DebugWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/DebugWrapper.qml @@ -1,54 +1,45 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls - import "." -import "./Proton" +import "Proton" Rectangle { property var target: parent - x: target.x - y: target.y - width: target.width - height: target.height - - color: "transparent" border.color: "red" border.width: 1 + color: "transparent" + height: target.height + width: target.width + x: target.x + y: target.y //z: parent.z - 1 z: 10000000 Label { - text: parent.width + "x" + parent.height anchors.centerIn: parent color: "black" colorScheme: ProtonStyle.currentStyle + text: parent.width + "x" + parent.height } - Rectangle { - width: target.implicitWidth - height: target.implicitHeight - - color: "transparent" border.color: "green" border.width: 1 + color: "transparent" + height: target.implicitHeight + width: target.implicitWidth //z: parent.z - 1 z: 10000000 } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/GeneralSettings.qml b/internal/frontend/bridge-gui/bridge-gui/qml/GeneralSettings.qml index 63be1c42..c29bc896 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/GeneralSettings.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/GeneralSettings.qml @@ -1,25 +1,19 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton SettingsView { @@ -31,144 +25,138 @@ SettingsView { fillHeight: false Label { + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Settings") type: Label.Heading - Layout.fillWidth: true } - SettingsItem { id: autoUpdate - colorScheme: root.colorScheme - text: qsTr("Automatic updates") - description: qsTr("Bridge will automatically update in the background.") - type: SettingsItem.Toggle - checked: Backend.isAutomaticUpdateOn - onClicked: Backend.toggleAutomaticUpdate(!autoUpdate.checked) - Layout.fillWidth: true - } + checked: Backend.isAutomaticUpdateOn + colorScheme: root.colorScheme + description: qsTr("Bridge will automatically update in the background.") + text: qsTr("Automatic updates") + type: SettingsItem.Toggle + onClicked: Backend.toggleAutomaticUpdate(!autoUpdate.checked) + } SettingsItem { id: autostart - colorScheme: root.colorScheme - text: qsTr("Open on startup") - description: qsTr("Bridge will open upon startup.") - type: SettingsItem.Toggle - checked: Backend.isAutostartOn - onClicked: { - autostart.loading = true - Backend.toggleAutostart(!autostart.checked) - } - Connections{ - target: Backend - function onToggleAutostartFinished() { - autostart.loading = false - } - } - Layout.fillWidth: true - } + checked: Backend.isAutostartOn + colorScheme: root.colorScheme + description: qsTr("Bridge will open upon startup.") + text: qsTr("Open on startup") + type: SettingsItem.Toggle + onClicked: { + autostart.loading = true; + Backend.toggleAutostart(!autostart.checked); + } + + Connections { + function onToggleAutostartFinished() { + autostart.loading = false; + } + + target: Backend + } + } SettingsItem { id: beta - colorScheme: root.colorScheme - text: qsTr("Beta access") - description: qsTr("Be among the first to try new features.") - type: SettingsItem.Toggle + Layout.fillWidth: true checked: Backend.isBetaEnabled + colorScheme: root.colorScheme + description: qsTr("Be among the first to try new features.") + text: qsTr("Beta access") + type: SettingsItem.Toggle + onClicked: { if (!beta.checked) { - root.notifications.askEnableBeta() + root.notifications.askEnableBeta(); } else { - Backend.toggleBeta(false) + Backend.toggleBeta(false); } } - - Layout.fillWidth: true } - RowLayout { ColorImage { Layout.alignment: Qt.AlignCenter - - source: root._isAdvancedShown ? "/qml/icons/ic-chevron-down.svg" : "/qml/icons/ic-chevron-right.svg" color: root.colorScheme.interaction_norm height: root.colorScheme.body_font_size + source: root._isAdvancedShown ? "/qml/icons/ic-chevron-down.svg" : "/qml/icons/ic-chevron-right.svg" sourceSize.height: root.colorScheme.body_font_size + MouseArea { anchors.fill: parent + onClicked: root._isAdvancedShown = !root._isAdvancedShown } } - Label { id: advSettLabel + color: root.colorScheme.interaction_norm colorScheme: root.colorScheme text: qsTr("Advanced settings") - color: root.colorScheme.interaction_norm type: Label.Body MouseArea { anchors.fill: parent + onClicked: root._isAdvancedShown = !root._isAdvancedShown } } } - SettingsItem { id: keychains - visible: root._isAdvancedShown && Backend.availableKeychain.length > 1 - colorScheme: root.colorScheme - text: qsTr("Change keychain") - description: qsTr("Change which keychain Bridge uses as default") - actionText: qsTr("Change") - type: SettingsItem.Button - checked: Backend.isDoHEnabled - onClicked: root.parent.showKeychainSettings() - Layout.fillWidth: true - } + actionText: qsTr("Change") + checked: Backend.isDoHEnabled + colorScheme: root.colorScheme + description: qsTr("Change which keychain Bridge uses as default") + text: qsTr("Change keychain") + type: SettingsItem.Button + visible: root._isAdvancedShown && Backend.availableKeychain.length > 1 + onClicked: root.parent.showKeychainSettings() + } SettingsItem { id: doh - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Alternative routing") - description: qsTr("If Proton’s servers are blocked in your location, alternative network routing will be used to reach Proton.") - type: SettingsItem.Toggle - checked: Backend.isDoHEnabled - onClicked: Backend.toggleDoH(!doh.checked) - Layout.fillWidth: true - } + checked: Backend.isDoHEnabled + colorScheme: root.colorScheme + description: qsTr("If Proton’s servers are blocked in your location, alternative network routing will be used to reach Proton.") + text: qsTr("Alternative routing") + type: SettingsItem.Toggle + visible: root._isAdvancedShown + onClicked: Backend.toggleDoH(!doh.checked) + } SettingsItem { id: darkMode - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Dark mode") - description: qsTr("Choose dark color theme.") - type: SettingsItem.Toggle - checked: Backend.colorSchemeName == "dark" - onClicked: Backend.changeColorScheme( darkMode.checked ? "light" : "dark") - Layout.fillWidth: true - } + checked: Backend.colorSchemeName === "dark" + colorScheme: root.colorScheme + description: qsTr("Choose dark color theme.") + text: qsTr("Dark mode") + type: SettingsItem.Toggle + visible: root._isAdvancedShown + onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark") + } SettingsItem { id: allMail - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Show All Mail") - description: qsTr("Choose to list the All Mail folder in your local client.") - type: SettingsItem.Toggle - checked: Backend.isAllMailVisible - onClicked: root.notifications.askChangeAllMailVisibility(Backend.isAllMailVisible) - Layout.fillWidth: true - } + checked: Backend.isAllMailVisible + colorScheme: root.colorScheme + description: qsTr("Choose to list the All Mail folder in your local client.") + text: qsTr("Show All Mail") + type: SettingsItem.Toggle + visible: root._isAdvancedShown + onClicked: root.notifications.askChangeAllMailVisibility(Backend.isAllMailVisible) + } SettingsItem { id: telemetry Layout.fillWidth: true @@ -181,73 +169,68 @@ SettingsView { onClicked: Backend.toggleIsTelemetryDisabled(telemetry.checked) } - SettingsItem { id: ports - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Default ports") - actionText: qsTr("Change") - description: qsTr("Choose which ports are used by default.") - type: SettingsItem.Button - onClicked: root.parent.showPortSettings() - Layout.fillWidth: true - } + actionText: qsTr("Change") + colorScheme: root.colorScheme + description: qsTr("Choose which ports are used by default.") + text: qsTr("Default ports") + type: SettingsItem.Button + visible: root._isAdvancedShown + onClicked: root.parent.showPortSettings() + } SettingsItem { id: imap - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Connection mode") - actionText: qsTr("Change") - description: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.") - type: SettingsItem.Button - onClicked: root.parent.showConnectionModeSettings() - Layout.fillWidth: true - } + actionText: qsTr("Change") + colorScheme: root.colorScheme + description: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.") + text: qsTr("Connection mode") + type: SettingsItem.Button + visible: root._isAdvancedShown + onClicked: root.parent.showConnectionModeSettings() + } SettingsItem { id: cache - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Local cache") - actionText: qsTr("Configure") - description: qsTr("Configure Bridge's local cache.") - type: SettingsItem.Button - onClicked: root.parent.showLocalCacheSettings() - Layout.fillWidth: true - } + actionText: qsTr("Configure") + colorScheme: root.colorScheme + description: qsTr("Configure Bridge's local cache.") + text: qsTr("Local cache") + type: SettingsItem.Button + visible: root._isAdvancedShown + onClicked: root.parent.showLocalCacheSettings() + } SettingsItem { id: exportTLSCertificates - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Export TLS certificates") - actionText: qsTr("Export") - description: qsTr("Export the TLS private key and certificate used by the IMAP and SMTP servers.") - type: SettingsItem.Button - onClicked: { - Backend.exportTLSCertificates() - } Layout.fillWidth: true + actionText: qsTr("Export") + colorScheme: root.colorScheme + description: qsTr("Export the TLS private key and certificate used by the IMAP and SMTP servers.") + text: qsTr("Export TLS certificates") + type: SettingsItem.Button + visible: root._isAdvancedShown + onClicked: { + Backend.exportTLSCertificates(); + } } - SettingsItem { id: reset - visible: root._isAdvancedShown - colorScheme: root.colorScheme - text: qsTr("Reset Bridge") - actionText: qsTr("Reset") - description: qsTr("Remove all accounts, clear cached data, and restore the original settings.") - type: SettingsItem.Button - onClicked: { - root.notifications.askResetBridge() - } - Layout.fillWidth: true + actionText: qsTr("Reset") + colorScheme: root.colorScheme + description: qsTr("Remove all accounts, clear cached data, and restore the original settings.") + text: qsTr("Reset Bridge") + type: SettingsItem.Button + visible: root._isAdvancedShown + + onClicked: { + root.notifications.askResetBridge(); + } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index beac6533..969567ba 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -1,126 +1,110 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton SettingsView { id: root - fillHeight: true Label { + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Help") type: Label.Heading - Layout.fillWidth: true } - SettingsItem { id: setupPage - colorScheme: root.colorScheme - text: qsTr("Installation and setup") - actionText: qsTr("Go to help topics") + Layout.fillWidth: true actionIcon: "/qml/icons/ic-external-link.svg" + actionText: qsTr("Go to help topics") + colorScheme: root.colorScheme description: qsTr("Get help setting up your client with our instructions and FAQs.") + text: qsTr("Installation and setup") type: SettingsItem.PrimaryButton + onClicked: { Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); - Qt.openUrlExternally("https://proton.me/support/bridge")} - - Layout.fillWidth: true + Qt.openUrlExternally("https://proton.me/support/bridge"); + } } - SettingsItem { id: checkUpdates - colorScheme: root.colorScheme - text: qsTr("Updates") + Layout.fillWidth: true actionText: qsTr("Check now") + colorScheme: root.colorScheme description: qsTr("Check that you're using the latest version of Bridge. To stay up to date, enable auto-updates in settings.") + text: qsTr("Updates") type: SettingsItem.Button + onClicked: { - checkUpdates.loading = true - Backend.checkUpdates() + checkUpdates.loading = true; + Backend.checkUpdates(); } Connections { + function onCheckUpdatesFinished() { + checkUpdates.loading = false; + } + target: Backend - function onCheckUpdatesFinished() { checkUpdates.loading = false } } - - Layout.fillWidth: true } - SettingsItem { id: logs - colorScheme: root.colorScheme - text: qsTr("Logs") - actionText: qsTr("View logs") - description: qsTr("Open and review logs to troubleshoot.") - type: SettingsItem.Button - onClicked: Qt.openUrlExternally(Backend.logsPath) - Layout.fillWidth: true - } + actionText: qsTr("View logs") + colorScheme: root.colorScheme + description: qsTr("Open and review logs to troubleshoot.") + text: qsTr("Logs") + type: SettingsItem.Button + onClicked: Qt.openUrlExternally(Backend.logsPath) + } SettingsItem { id: reportBug - colorScheme: root.colorScheme - text: qsTr("Report a problem") - actionText: qsTr("Report a problem") - description: qsTr("Something not working as expected? Let us know.") - type: SettingsItem.Button - onClicked: { - Backend.updateCurrentMailClient() - Backend.notifyReportBugClicked() - root.parent.showBugReport() - } - Layout.fillWidth: true + actionText: qsTr("Report a problem") + colorScheme: root.colorScheme + description: qsTr("Something not working as expected? Let us know.") + text: qsTr("Report a problem") + type: SettingsItem.Button + + onClicked: { + Backend.updateCurrentMailClient(); + Backend.notifyReportBugClicked(); + root.parent.showBugReport(); + } } - // fill height so the footer label will be always attached to the bottom + // fill height so the footer label will always be attached to the bottom Item { Layout.fillHeight: true Layout.fillWidth: true } - Label { Layout.alignment: Qt.AlignHCenter - colorScheme: root.colorScheme - type: Label.Caption color: root.colorScheme.text_weak - textFormat: Text.StyledText - + colorScheme: root.colorScheme horizontalAlignment: Text.AlignHCenter + text: qsTr("%1 v%2 (%3)
© 2017-%4 %5
%6 %7
%8").arg(Backend.appname).arg(Backend.version).arg(Backend.tag).arg(Backend.buildYear()).arg(Backend.vendor).arg(link(Backend.licensePath, qsTr("License"))).arg(link(Backend.dependencyLicensesLink, qsTr("Dependencies"))).arg(link(Backend.releaseNotesLink, qsTr("Release notes"))) + textFormat: Text.StyledText + type: Label.Caption - text: qsTr("%1 v%2 (%3)
© 2017-%4 %5
%6 %7
%8"). - arg(Backend.appname). - arg(Backend.version). - arg(Backend.tag). - arg(Backend.buildYear()). - arg(Backend.vendor). - arg(link(Backend.licensePath, qsTr("License"))). - arg(link(Backend.dependencyLicensesLink, qsTr("Dependencies"))). - arg(link(Backend.releaseNotesLink, qsTr("Release notes"))) - - onLinkActivated: function(link) { Qt.openUrlExternally(link) } + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/KeychainSettings.qml b/internal/frontend/bridge-gui/bridge-gui/qml/KeychainSettings.qml index 06af8379..fec873cc 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/KeychainSettings.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/KeychainSettings.qml @@ -1,116 +1,105 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton SettingsView { id: root - fillHeight: false + property bool _valuesChanged: keychainSelection.checkedButton && keychainSelection.checkedButton.text !== Backend.currentKeychain - property bool _valuesChanged: keychainSelection.checkedButton && keychainSelection.checkedButton.text != Backend.currentKeychain - - Label { - colorScheme: root.colorScheme - text: qsTr("Default keychain") - type: Label.Heading - Layout.fillWidth: true - } - - Label { - colorScheme: root.colorScheme - text: qsTr("Change which keychain Bridge uses as default") - type: Label.Body - color: root.colorScheme.text_weak - Layout.fillWidth: true - wrapMode: Text.WordWrap - } - - ColumnLayout { - spacing: 16 - - ButtonGroup{ id: keychainSelection } - - Repeater { - model: Backend.availableKeychain - - RadioButton { - colorScheme: root.colorScheme - ButtonGroup.group: keychainSelection - text: modelData + function setDefaultValues() { + for (const bi in keychainSelection.buttons) { + const button = keychainSelection.buttons[bi]; + if (button.text === Backend.currentKeychain) { + button.checked = true; + break; } } } + fillHeight: false - Rectangle { - Layout.fillWidth: true - height: 1 - color: root.colorScheme.border_weak + Component.onCompleted: root.setDefaultValues() + onBack: { + root.setDefaultValues(); } + Label { + Layout.fillWidth: true + colorScheme: root.colorScheme + text: qsTr("Default keychain") + type: Label.Heading + } + Label { + Layout.fillWidth: true + color: root.colorScheme.text_weak + colorScheme: root.colorScheme + text: qsTr("Change which keychain Bridge uses as default") + type: Label.Body + wrapMode: Text.WordWrap + } + ColumnLayout { + spacing: 16 + + ButtonGroup { + id: keychainSelection + } + Repeater { + model: Backend.availableKeychain + + RadioButton { + ButtonGroup.group: keychainSelection + colorScheme: root.colorScheme + text: modelData + } + } + } + Rectangle { + Layout.fillWidth: true + color: root.colorScheme.border_weak + height: 1 + } RowLayout { spacing: 12 Button { id: submitButton colorScheme: root.colorScheme - text: qsTr("Save and restart") enabled: root._valuesChanged + text: qsTr("Save and restart") + onClicked: { - Backend.changeKeychain(keychainSelection.checkedButton.text) + Backend.changeKeychain(keychainSelection.checkedButton.text); } } - Button { colorScheme: root.colorScheme - text: qsTr("Cancel") - onClicked: root.back() secondary: true - } + text: qsTr("Cancel") + onClicked: root.back() + } Connections { - target: Backend - function onChangeKeychainFinished() { - submitButton.loading = false - root.back() + submitButton.loading = false; + root.back(); } + + target: Backend } } - - onBack: { - root.setDefaultValues() - } - - function setDefaultValues(){ - for (var bi in keychainSelection.buttons){ - var button = keychainSelection.buttons[bi] - if (button.text == Backend.currentKeychain) { - button.checked = true - break; - } - } - } - - Component.onCompleted: root.setDefaultValues() } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/LocalCacheSettings.qml b/internal/frontend/bridge-gui/bridge-gui/qml/LocalCacheSettings.qml index 5dee608e..a319fd4d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/LocalCacheSettings.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/LocalCacheSettings.qml @@ -1,81 +1,88 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Dialogs - import Proton SettingsView { id: root - fillHeight: false - - property var notifications property url diskCachePath: pathDialog.shortcuts.home + property var notifications function refresh() { - diskCacheSetting.description = Backend.nativePath(root.diskCachePath) - submitButton.enabled = (!submitButton.loading) && !Backend.areSameFileOrFolder(Backend.diskCachePath, root.diskCachePath) + diskCacheSetting.description = Backend.nativePath(root.diskCachePath); + submitButton.enabled = (!submitButton.loading) && !Backend.areSameFileOrFolder(Backend.diskCachePath, root.diskCachePath); + } + function setDefaultValues() { + root.diskCachePath = Backend.diskCachePath; + root.refresh(); + } + function submit() { + submitButton.loading = true; + Backend.setDiskCachePath(root.diskCachePath); + } + + fillHeight: false + + onBack: { + root.setDefaultValues(); + } + onVisibleChanged: { + root.setDefaultValues(); } Label { + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Local cache") type: Label.Heading - Layout.fillWidth: true } - Label { + Layout.fillWidth: true + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: qsTr("Bridge stores your encrypted messages locally to optimize communication with your client.") type: Label.Body - color: root.colorScheme.text_weak - Layout.fillWidth: true wrapMode: Text.WordWrap } - SettingsItem { id: diskCacheSetting - colorScheme: root.colorScheme - text: qsTr("Current cache location") - actionText: qsTr("Change location") - descriptionWrap: Text.WrapAnywhere - type: SettingsItem.Button - onClicked: { - pathDialog.open() - } - Layout.fillWidth: true + actionText: qsTr("Change location") + colorScheme: root.colorScheme + descriptionWrap: Text.WrapAnywhere + text: qsTr("Current cache location") + type: SettingsItem.Button + + onClicked: { + pathDialog.open(); + } FolderDialog { id: pathDialog - title: qsTr("Select cache location") currentFolder: root.diskCachePath - onAccepted: { - root.diskCachePath = pathDialog.selectedFolder - root.refresh() - } - } - } + title: qsTr("Select cache location") + onAccepted: { + root.diskCachePath = pathDialog.selectedFolder; + root.refresh(); + } + } + } RowLayout { spacing: 12 @@ -83,43 +90,25 @@ SettingsView { id: submitButton colorScheme: root.colorScheme text: qsTr("Save") + onClicked: { - root.submit() + root.submit(); } } - Button { colorScheme: root.colorScheme - text: qsTr("Cancel") - onClicked: root.back() secondary: true - } + text: qsTr("Cancel") + onClicked: root.back() + } Connections { - target: Backend - function onDiskCachePathChangeFinished() { - submitButton.loading = false - root.setDefaultValues() + submitButton.loading = false; + root.setDefaultValues(); } + + target: Backend } } - - onBack: { - root.setDefaultValues() - } - - function submit() { - submitButton.loading = true - Backend.setDiskCachePath(root.diskCachePath) - } - - function setDefaultValues(){ - root.diskCachePath = Backend.diskCachePath - root.refresh(); - } - - onVisibleChanged: { - root.setDefaultValues() - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 3de94f99..a76c1a6a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -1,232 +1,202 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls - import Proton import Notifications ApplicationWindow { id: root - colorScheme: ProtonStyle.currentStyle - visible: true - - property int _defaultWidth: 1080 property int _defaultHeight: 780 - width: _defaultWidth + property int _defaultWidth: 1080 + property var notifications + + function selectUser(userID) { + contentWrapper.selectUser(userID); + } + function showAndRise() { + root.show(); + root.raise(); + if (!root.active) { + root.requestActivate(); + } + } + function showBugReportAndPrefill(message) { + contentWrapper.showBugReportAndPrefill(message); + } + function showHelp() { + contentWrapper.showHelp(); + } + function showLocalCacheSettings() { + contentWrapper.showLocalCacheSettings(); + } + function showSettings() { + contentWrapper.showSettings(); + } + function showSetup(user, address) { + setupGuide.user = user; + setupGuide.address = address; + setupGuide.reset(); + contentLayout._showSetup = !!setupGuide.user; + } + function showSignIn(username) { + if (contentLayout.currentIndex === 1) + return; + contentWrapper.showSignIn(username); + } + + colorScheme: ProtonStyle.currentStyle height: _defaultHeight minimumWidth: _defaultWidth - - property var notifications + visible: true + width: _defaultWidth // show Setup Guide on every new user Connections { - target: Backend.users - - function onRowsInserted(parent, first, last) { - // considering that users are added one-by-one - var user = Backend.users.get(first) - - if (user.state === EUserState.SignedOut) { - return - } - - if (user.setupGuideSeen) { - return - } - - root.showSetup(user,user.addresses[0]) - } - function onRowsAboutToBeRemoved(parent, first, last) { - for (var i = first; i <= last; i++ ) { - var user = Backend.users.get(i) - + for (let i = first; i <= last; i++) { + const user = Backend.users.get(i); if (setupGuide.user === user) { - setupGuide.user = null - contentLayout._showSetup = false - return + setupGuide.user = null; + contentLayout._showSetup = false; + return; } } } - } + function onRowsInserted(parent, first, _) { + // considering that users are added one-by-one + const user = Backend.users.get(first); + if (user.state === EUserState.SignedOut) { + return; + } + if (user.setupGuideSeen) { + return; + } + root.showSetup(user, user.addresses[0]); + } + target: Backend.users + } Connections { - target: Backend - - function onShowMainWindow() { - root.showAndRise() - } - function onLoginFinished(index, wasSignedOut) { - var user = Backend.users.get(index) + const user = Backend.users.get(index); if (user && !wasSignedOut) { - root.showSetup(user, user.addresses[0]) + root.showSetup(user, user.addresses[0]); } - console.debug("Login finished", index) + console.debug("Login finished", index); } - - function onShowHelp() { - root.showHelp() - root.showAndRise() - } - - function onShowSettings() { - root.showSettings() - root.showAndRise() - } - function onSelectUser(userID, forceShowWindow) { - contentWrapper.selectUser(userID) + contentWrapper.selectUser(userID); if (forceShowWindow) { - root.showAndRise() + root.showAndRise(); } } - } + function onShowHelp() { + root.showHelp(); + root.showAndRise(); + } + function onShowMainWindow() { + root.showAndRise(); + } + function onShowSettings() { + root.showSettings(); + root.showAndRise(); + } + target: Backend + } StackLayout { id: contentLayout - anchors.fill: parent - property bool _showSetup: false + + anchors.fill: parent currentIndex: { // show welcome when there are no users if (Backend.users.count === 0) { - return 1 + return 1; } - - var u = Backend.users.get(0) - + const u = Backend.users.get(0); if (!u) { - console.trace() - console.log("empty user") - return 1 + console.trace(); + console.log("empty user"); + return 1; } - if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) { - showSignIn(u.primaryEmailOrUsername()) - return 0 + showSignIn(u.primaryEmailOrUsername()); + return 0; } - if (contentLayout._showSetup) { - return 2 + return 2; } - - return 0 + return 0; } - ContentWrapper { // 0 + ContentWrapper { + // 0 id: contentWrapper + Layout.fillHeight: true + Layout.fillWidth: true colorScheme: root.colorScheme notifications: root.notifications - Layout.fillHeight: true - Layout.fillWidth: true - - onShowSetupGuide: function(user, address) { - root.showSetup(user,address) - } - onCloseWindow: { - root.close() + root.close(); } - onQuitBridge: { // If we ever want to add a confirmation dialog before quitting: //root.notifications.askQuestion("Quit Bridge", "Insert warning message here.", "Quit", "Cancel", Backend.quit, null) - root.close() - Backend.quit() + root.close(); + Backend.quit(); + } + onShowSetupGuide: function (user, address) { + root.showSetup(user, address); } } - - WelcomeGuide { // 1 - colorScheme: root.colorScheme - + WelcomeGuide { Layout.fillHeight: true - Layout.fillWidth: true + Layout.fillWidth: true // 1 + colorScheme: root.colorScheme } - - SetupGuide { // 2 + SetupGuide { + // 2 id: setupGuide - colorScheme: root.colorScheme - Layout.fillHeight: true Layout.fillWidth: true + colorScheme: root.colorScheme onDismissed: { - root.showSetup(null,"") + root.showSetup(null, ""); } - onFinished: { // TODO: Do not close window. Trigger Backend to check that // there is a successfully connected client. Then Backend // should send another signal to close the setup guide. - root.showSetup(null,"") + root.showSetup(null, ""); } } } - NotificationPopups { colorScheme: root.colorScheme - notifications: root.notifications mainWindow: root + notifications: root.notifications } - SplashScreen { id: splashScreen colorScheme: root.colorScheme } - - function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() } - function showSettings() { contentWrapper.showSettings() } - function showHelp() { contentWrapper.showHelp() } - function selectUser(userID) { contentWrapper.selectUser(userID) } - - function showBugReportAndPrefill(message) { - contentWrapper.showBugReportAndPrefill(message) - } - - function showSignIn(username) { - if (contentLayout.currentIndex == 1) return - contentWrapper.showSignIn(username) - } - - function showSetup(user, address) { - setupGuide.user = user - setupGuide.address = address - setupGuide.reset() - if (setupGuide.user) { - contentLayout._showSetup = true - } else { - contentLayout._showSetup = false - } - } - - function showAndRise() { - root.show() - root.raise() - if (!root.active) { - root.requestActivate() - } - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationDialog.qml b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationDialog.qml index bd075a2e..a1031ad2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationDialog.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationDialog.qml @@ -1,118 +1,99 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton import Notifications Dialog { id: root + default property alias data: additionalChildrenContainer.children property var notification - shouldShow: notification && notification.active && !notification.dismissed modal: true - - default property alias data: additionalChildrenContainer.children + shouldShow: notification && notification.active && !notification.dismissed ColumnLayout { spacing: 0 Image { Layout.alignment: Qt.AlignHCenter - - sourceSize.width: 64 - sourceSize.height: 64 - + Layout.bottomMargin: 16 Layout.preferredHeight: 64 Layout.preferredWidth: 64 - - Layout.bottomMargin: 16 - - visible: source != "" - source: { if (!root.notification) { - return "" + return ""; } - switch (root.notification.type) { - case Notification.NotificationType.Info: - return "/qml/icons/ic-info.svg" - case Notification.NotificationType.Success: - return "/qml/icons/ic-success.svg" - case Notification.NotificationType.Warning: - case Notification.NotificationType.Danger: - return "/qml/icons/ic-alert.svg" + case Notification.NotificationType.Info: + return "/qml/icons/ic-info.svg"; + case Notification.NotificationType.Success: + return "/qml/icons/ic-success.svg"; + case Notification.NotificationType.Warning: + case Notification.NotificationType.Danger: + return "/qml/icons/ic-alert.svg"; } } + sourceSize.height: 64 + sourceSize.width: 64 + visible: source != "" } - Label { Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Text.AlignHCenter Layout.bottomMargin: 8 colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter text: root.notification.title type: Label.LabelType.Title } - Label { + Layout.bottomMargin: 16 Layout.fillWidth: true Layout.preferredWidth: 240 - Layout.bottomMargin: 16 - colorScheme: root.colorScheme - text: root.notification.description - wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter + text: root.notification.description type: Label.LabelType.Body - onLinkActivated: function(link) { Qt.openUrlExternally(link) } - } + wrapMode: Text.WordWrap + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + } Item { id: additionalChildrenContainer - - Layout.fillWidth: true Layout.bottomMargin: 16 - - visible: children.length > 0 - + Layout.fillWidth: true implicitHeight: additionalChildrenContainer.childrenRect.height implicitWidth: additionalChildrenContainer.childrenRect.width + visible: children.length > 0 } - ColumnLayout { spacing: 8 + Repeater { model: root.notification.action + delegate: Button { Layout.fillWidth: true - - colorScheme: root.colorScheme action: modelData - - secondary: index > 0 - + colorScheme: root.colorScheme loading: modelData.loading + secondary: index > 0 } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml index bf6d45bb..08b2cc3a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml @@ -1,25 +1,19 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton import Notifications @@ -27,118 +21,98 @@ Item { id: root property ColorScheme colorScheme - property var notifications property var mainWindow - - property int notificationWhitelist: NotificationFilter.FilterConsts.All property int notificationBlacklist: NotificationFilter.FilterConsts.None + property int notificationWhitelist: NotificationFilter.FilterConsts.All + property var notifications NotificationFilter { id: bannerNotificationFilter - - source: root.notifications.all blacklist: Notifications.Group.Dialogs + source: root.notifications.all } - Banner { colorScheme: root.colorScheme - notification: bannerNotificationFilter.topmost mainWindow: root.mainWindow + notification: bannerNotificationFilter.topmost } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.updateManualReady Switch { - id:autoUpdate + id: autoUpdate + checked: Backend.isAutomaticUpdateOn colorScheme: root.colorScheme text: qsTr("Update automatically in the future") - checked: Backend.isAutomaticUpdateOn + onClicked: Backend.toggleAutomaticUpdate(autoUpdate.checked) } } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.updateForce } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.updateForceError } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.enableBeta } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.cacheUnavailable } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.cacheCantMove } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.diskFull } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.enableSplitMode } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.resetBridge } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.changeAllMailVisibility } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.deleteAccount } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.noKeychain } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.rebuildKeychain } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.apiCertIssue } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.noActiveKeyForRecipient } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.userBadEvent } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.genericError } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.genericQuestion diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notification.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notification.qml index d8fd41d9..202a250c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notification.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notification.qml @@ -1,54 +1,45 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick.Controls QtObject { id: root - - default property var children - enum NotificationType { - Info = 0, - Success = 1, - Warning = 2, - Danger = 3 + Info, + Success, + Warning, + Danger } + property list action + property bool active: false + // brief is used in status view only + property string brief + default property var children + property var data + // description is used in banners and in dialogs as description + property string description + property bool dismissed: false + property int group + property string icon + readonly property var occurred: active ? new Date() : undefined + // title is used in dialogs only property string title - // description is used in banners and in dialogs as description - property string description - // brief is used in status view only - property string brief - - property string icon - property list action property int type - property int group - - property bool dismissed: false - property bool active: false - readonly property var occurred: active ? new Date() : undefined - - property var data onActiveChanged: { - dismissed = false + dismissed = false; } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/NotificationFilter.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/NotificationFilter.qml index 91b49996..d74abc8c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/NotificationFilter.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/NotificationFilter.qml @@ -1,114 +1,95 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQml.Models // contains notifications that satisfy black- and whitelist and are sorted in time-occurred order ListModel { id: root - enum FilterConsts { - None = 0, + None, All = 255 } - property int whitelist: NotificationFilter.FilterConsts.All property int blacklist: NotificationFilter.FilterConsts.None - - property Notification topmost - property var source - property bool componentCompleted: false - Component.onCompleted: { - root.componentCompleted = true - root.rebuildList() - } + property var source + property Notification topmost + property int whitelist: NotificationFilter.FilterConsts.All // overriding get method to ignore any role and return directly object itself function get(row) { if (row < 0 || row >= count) { - return undefined + return undefined; } - return data(index(row, 0), Qt.DisplayRole) + return data(index(row, 0), Qt.DisplayRole); } - function rebuildList() { + let i; // avoid evaluation of the list before Component.onCompleted if (!root.componentCompleted) { - return + return; } - - for (var i = 0; i < root.count; i++) { - root.get(i).onActiveChanged.disconnect( root.updateList ) + for (i = 0; i < root.count; i++) { + root.get(i).onActiveChanged.disconnect(root.updateList); } - - root.clear() - + root.clear(); if (!root.source) { - return + return; } - for (i = 0; i < root.source.length; i++) { - var obj = root.source[i] + const obj = root.source[i]; if (obj.group & root.blacklist) { - continue + continue; } - if (!(obj.group & root.whitelist)) { - continue + continue; } - - root.append({obj}) - obj.onActiveChanged.connect( root.updateList ) + root.append({ + "obj": obj + }); + obj.onActiveChanged.connect(root.updateList); } } - function updateList() { - var topmost = null - - for (var i = 0; i < root.count; i++) { - var obj = root.get(i) - + let topmost = null; + for (let i = 0; i < root.count; i++) { + const obj = root.get(i); if (!obj.active) { - continue + continue; } - if (topmost && (topmost.type > obj.type)) { - continue + continue; } - if (topmost && (topmost.type === obj.type) && (topmost.occurred > obj.occurred)) { - continue + continue; } - - topmost = obj + topmost = obj; } - - root.topmost = topmost + root.topmost = topmost; } - onWhitelistChanged: { - root.rebuildList() + Component.onCompleted: { + root.componentCompleted = true; + root.rebuildList(); } onBlacklistChanged: { - root.rebuildList() + root.rebuildList(); } onSourceChanged: { - root.rebuildList() + root.rebuildList(); + } + onWhitelistChanged: { + root.rebuildList(); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml index ac05169d..22589019 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml @@ -1,1069 +1,114 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import Qt.labs.platform import QtQuick.Controls -import ".." +import "../" QtObject { id: root - - property MainWindow frontendMain - - signal askEnableBeta() - signal askEnableSplitMode(var user) - signal askResetBridge() - signal askChangeAllMailVisibility(var isVisibleNow) - signal askDeleteAccount(var user) - signal askQuestion(var title, var description, var option1, var option2, var action1, var action2) enum Group { - Connection = 1, - Update = 2, + Connection = 1, + Update, Configuration = 4, - ForceUpdate = 8, - API = 32, + ForceUpdate = 8, + API = 32, // Special group for notifications that require dialog popup instead of banner Dialogs = 64 } - property var all: [ - root.noInternet, - root.imapPortStartupError, - root.smtpPortStartupError, - root.imapPortChangeError, - root.smtpPortChangeError, - root.imapConnectionModeChangeError, - root.smtpConnectionModeChangeError, - root.updateManualReady, - root.updateManualRestartNeeded, - root.updateManualError, - root.updateForce, - root.updateForceError, - root.updateSilentRestartNeeded, - root.updateSilentError, - root.updateIsLatestVersion, - root.loginConnectionError, - root.onlyPaidUsers, - root.alreadyLoggedIn, - root.enableBeta, - root.bugReportSendSuccess, - root.bugReportSendError, - root.cacheUnavailable, - root.cacheCantMove, - root.accountChanged, - root.diskFull, - root.cacheLocationChangeSuccess, - root.enableSplitMode, - root.resetBridge, - root.changeAllMailVisibility, - root.deleteAccount, - root.noKeychain, - root.rebuildKeychain, - root.addressChanged, - root.apiCertIssue, - root.noActiveKeyForRecipient, - root.userBadEvent, - root.imapLoginWhileSignedOut, - root.genericError, - root.genericQuestion, - ] - - // Connection - property Notification noInternet: Notification { - description: qsTr("Bridge is not able to contact the server, please check your internet connection.") - brief: qsTr("No connection") - icon: "./icons/ic-no-connection.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onInternetOff() { - root.noInternet.active = true - } - function onInternetOn() { - root.noInternet.active = false - } - } - } - - property Notification imapPortStartupError: Notification { - description: qsTr("The IMAP server could not be started. Please check or change the IMAP port.") - brief: qsTr("IMAP port error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onImapPortStartupError() { - root.imapPortStartupError.active = true - } - } - } - - property Notification smtpPortStartupError: Notification { - description: qsTr("The SMTP server could not be started. Please check or change the SMTP port.") - brief: qsTr("SMTP port error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onSmtpPortStartupError() { - root.smtpPortStartupError.active = true - } - } - } - - property Notification imapPortChangeError: Notification { - description: qsTr("The IMAP port could not be changed.") - brief: qsTr("IMAP port error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onImapPortChangeError() { - root.imapPortChangeError.active = true - } - } - } - - property Notification smtpPortChangeError: Notification { - description: qsTr("The SMTP port could not be changed.") - brief: qsTr("SMTP port error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onSmtpPortChangeError() { - root.smtpPortChangeError.active = true - } - } - } - - property Notification imapConnectionModeChangeError: Notification { - description: qsTr("The IMAP connection mode could not be changed.") - brief: qsTr("IMAP Connection mode error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onImapConnectionModeChangeError() { - root.imapConnectionModeChangeError.active = true - } - } - - action: Action { - text: qsTr("OK") - - onTriggered: { - root.imapConnectionModeChangeError.active= false - } - } - } - - property Notification smtpConnectionModeChangeError: Notification { - description: qsTr("The SMTP connection mode could not be changed.") - brief: qsTr("SMTP Connection mode error") - icon: "./icons/ic-alert.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - - Connections { - target: Backend - - function onSmtpConnectionModeChangeError() { - root.smtpConnectionModeChangeError.active = true - } - } - - action: Action { - text: qsTr("OK") - - onTriggered: { - root.smtpConnectionModeChangeError.active= false - } - } - } - - // Updates - property Notification updateManualReady: Notification { - title: qsTr("Update to Bridge %1").arg(data ? data.version : "") - description: { - var descr = qsTr("A new version of Proton Mail Bridge is available.") - var text = qsTr("See what's changed.") - var link = Backend.releaseNotesLink - return `${descr} ${text}` - } - brief: qsTr("Update available") - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Update | Notifications.Group.Dialogs - - Connections { - target: Backend - function onUpdateManualReady(version) { - root.updateManualReady.data = { version: version } - root.updateManualReady.active = true - } - } - - action: [ - Action { - text: qsTr("Install update") - - onTriggered: { - Backend.installUpdate() - root.updateManualReady.active = false - } - }, - Action { - text: qsTr("Update manually") - - onTriggered: { - Qt.openUrlExternally(Backend.landingPageLink) - root.updateManualReady.active = false - } - }, - Action { - text: qsTr("Remind me later") - - onTriggered: { - root.updateManualReady.active = false - } - } - ] - } - - property Notification updateManualRestartNeeded: Notification { - description: qsTr("Bridge update is ready") - brief: description - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Update - - Connections { - target: Backend - function onUpdateManualRestartNeeded() { - root.updateManualRestartNeeded.active = true - } - } - - action: Action { - text: qsTr("Restart Bridge") - - onTriggered: { - Backend.restart() - root.updateManualRestartNeeded.active = false - } - } - } - - property Notification updateManualError: Notification { - title: qsTr("Bridge couldn’t update") - brief: title - description: qsTr("Please follow manual installation in order to update Bridge.") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Update - - Connections { - target: Backend - function onUpdateManualError() { - root.updateManualError.active = true - } - } - - action: [ - Action { - text: qsTr("Update manually") - - onTriggered: { - Qt.openUrlExternally(Backend.landingPageLink) - root.updateManualError.active = false - Backend.quit() - } - }, - Action { - text: qsTr("Remind me later") - - onTriggered: { - root.updateManualError.active = false - } - } - ] - } - - property Notification updateForce: Notification { - title: qsTr("Update to Bridge %1").arg(data ? data.version : "") - description: qsTr("This version of Bridge is no longer supported, please update.") - brief: qsTr("Bridge is outdated") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Update | Notifications.Group.ForceUpdate | Notifications.Group.Dialogs - - Connections { - target: Backend - - function onUpdateForce(version) { - root.updateForce.data = { version: version } - root.updateForce.active = true - } - } - - action: [ - Action { - text: qsTr("Install update") - - onTriggered: { - Backend.installUpdate() - root.updateForce.active = false - } - }, - Action { - text: qsTr("Update manually") - - onTriggered: { - Qt.openUrlExternally(Backend.landingPageLink) - root.updateForce.active = false - } - }, - Action { - text: qsTr("Quit Bridge") - - onTriggered: { - Backend.quit() - root.updateForce.active = false - } - } - ] - } - - property Notification updateForceError: Notification { - title: qsTr("Bridge couldn't update") - description: qsTr("You must update manually. Go to: https://proton.me/mail/bridge#download") - brief: title - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Update | Notifications.Group.Dialogs - - Connections { - target: Backend - - function onUpdateForceError() { - root.updateForceError.active = true - } - } - - action: [ - Action { - text: qsTr("Update manually") - - onTriggered: { - Qt.openUrlExternally(Backend.landingPageLink) - root.updateForceError.active = false - } - }, - Action { - text: qsTr("Quit Bridge") - - onTriggered: { - Backend.quit() - root.updateForceError.active = false - } - } - ] - } - - property Notification updateSilentRestartNeeded: Notification { - description: qsTr("Bridge update is ready") - brief: description - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Update - - Connections { - target: Backend - function onUpdateSilentRestartNeeded() { - root.updateSilentRestartNeeded.active = true - } - } - - action: Action { - text: qsTr("Restart Bridge") - - onTriggered: { - Backend.restart() - root.updateSilentRestartNeeded.active = false - } - } - } - - property Notification updateSilentError: Notification { - description: qsTr("Bridge couldn't update") - brief: description - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Update - - Connections { - target: Backend - function onUpdateSilentError() { - root.updateSilentError.active = true - } - } - - action: Action { - text: qsTr("Update manually") - - onTriggered: { - Qt.openUrlExternally(Backend.landingPageLink) - root.updateSilentError.active = false - } - } - } - - property Notification updateIsLatestVersion: Notification { - description: qsTr("Bridge is up to date") - brief: description - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Update - - Connections { - target: Backend - function onUpdateIsLatestVersion() { - root.updateIsLatestVersion.active = true - } - } - - action: Action { - text: qsTr("OK") - - onTriggered: { - root.updateIsLatestVersion.active = false - } - } - } - - property Notification enableBeta: Notification { - title: qsTr("Enable Beta access") - brief: title - description: qsTr("Be the first to get new updates and use new features. Bridge will update to the latest beta version.") - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Update | Notifications.Group.Dialogs - - Connections { - target: root - function onAskEnableBeta() { - root.enableBeta.active = true - } - } - - action: [ - Action { - text: qsTr("Enable") - onTriggered: { - Backend.toggleBeta(true) - root.enableBeta.active = false - } - }, - Action { - text: qsTr("Cancel") - - onTriggered: { - root.enableBeta.active = false - } - } - ] - } - - // login - property Notification loginConnectionError: Notification { - description: qsTr("Bridge is not able to contact the server, please check your internet connection.") - brief: qsTr("Connection error") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration - - Connections { - target: Backend - function onLoginConnectionError(errorMsg) { - root.loginConnectionError.active = true - } - } - - action: [ - Action { - text: qsTr("OK") - onTriggered: { - root.loginConnectionError.active = false - } - } - ] - } - - property Notification onlyPaidUsers: Notification { - description: qsTr("Bridge is exclusive to our mail paid plans. Upgrade your account to use Bridge.") - brief: qsTr("Upgrade your account") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration - property var pricingLink: "https://proton.me/mail/pricing" - - Connections { - target: Backend - function onLoginFreeUserError() { - root.onlyPaidUsers.active = true - } - } - - action: [ - Action { - text: qsTr("Upgrade") - onTriggered: { - Qt.openUrlExternally(root.onlyPaidUsers.pricingLink) - root.onlyPaidUsers.active = false - } - } - ] - } - - property Notification alreadyLoggedIn: Notification { - description: qsTr("This account is already signed in.") - brief: qsTr("Already signed in") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Info - group: Notifications.Group.Configuration - - Connections { - target: Backend - function onLoginAlreadyLoggedIn(index) { - root.alreadyLoggedIn.active = true - } - } - - action: [ - Action { - text: qsTr("OK") - onTriggered: { - root.alreadyLoggedIn.active = false - } - } - ] - } - - // Bug reports - property Notification bugReportSendSuccess: Notification { - description: qsTr("Thank you for the report. We'll get back to you as soon as we can.") - brief: qsTr("Report sent") - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Success - group: Notifications.Group.Configuration - - Connections { - target: Backend - function onBugReportSendSuccess() { - root.bugReportSendSuccess.active = true - } - } - - action: [ - Action { - text: qsTr("OK") - onTriggered: { - root.bugReportSendSuccess.active = false - } - } - ] - } - - property Notification bugReportSendError: Notification { - description: qsTr("Report could not be sent. Try again or email us directly.") - brief: qsTr("Error sending report") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration - - Connections { - target: Backend - function onBugReportSendError() { - root.bugReportSendError.active = true - } - } - - action: Action { - text: qsTr("OK") - onTriggered: { - root.bugReportSendError.active = false - } - } - } - - // Cache - property Notification cacheUnavailable: Notification { - title: qsTr("Cache location is unavailable") - description: qsTr("The current cache location is unavailable. Check the directory or change it in your settings.") - brief: title - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - Connections { - target: Backend - function onDiskCacheUnavailable() { - root.cacheUnavailable.active = true - } - } - - action: [ - Action { - text: qsTr("Quit Bridge") - onTriggered: { - Backend.quit() - root.cacheUnavailable.active = false - } - }, - Action { - text: qsTr("Change location") - onTriggered: { - root.cacheUnavailable.active = false - root.frontendMain.showLocalCacheSettings() - } - } - ] - } - - property Notification cacheCantMove: Notification { - title: qsTr("Can’t move cache") - brief: title - description: qsTr("The location you have selected is not available. Make sure you have enough free space or choose another location.") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - Connections { - target: Backend - function onCantMoveDiskCache() { - root.cacheCantMove.active = true - } - } - - action: [ - Action { - text: qsTr("Cancel") - onTriggered: { - root.cacheCantMove.active = false - } - }, - Action { - text: qsTr("Change location") - onTriggered: { - root.cacheCantMove.active = false - root.frontendMain.showLocalCacheSettings() - } - } - ] - } - - property Notification cacheLocationChangeSuccess: Notification { - description: qsTr("Cache location successfully changed") - brief: description - icon: "./icons/ic-info-circle-filled.svg" - type: Notification.NotificationType.Success - group: Notifications.Group.Configuration - - Connections { - target: Backend - function onDiskCachePathChanged() { - console.log("notify location changed successfully") - root.cacheLocationChangeSuccess.active = true - } - } - - action: [ - Action { - text: qsTr("OK") - onTriggered: { - root.cacheLocationChangeSuccess.active = false - } - } - ] - } - // Other property Notification accountChanged: Notification { - description: qsTr("The address list for .... account has changed. You need to reconfigure your email client.") brief: qsTr("Address list changed") + description: qsTr("The address list for .... account has changed. You need to reconfigure your email client.") + group: Notifications.Group.Configuration icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration action: Action { text: qsTr("Reconfigure") - onTriggered: { - // TODO: open configuration window here + onTriggered: + // TODO: open configuration window here + { } } } - - property Notification diskFull: Notification { - title: qsTr("Your disk is almost full") - description: qsTr("Quit Bridge and free disk space or disable the local cache (not recommended).") - brief: title - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - Connections { - target: Backend - function onDiskFull() { - root.diskFull.active = true - } - } - - action: [ - Action { - text: qsTr("Quit Bridge") - onTriggered: { - Backend.quit() - root.diskFull.active = false - } - }, - Action { - text: qsTr("Settings") - onTriggered: { - root.diskFull.active = false - root.frontendMain.showLocalCacheSettings() - } - } - ] - } - - property Notification enableSplitMode: Notification { - title: qsTr("Enable split mode?") - brief: title - description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.") - icon: "/qml/icons/ic-question-circle.svg" - type: Notification.NotificationType.Warning - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - property var user - - Connections { - target: root - function onAskEnableSplitMode(user) { - root.enableSplitMode.user = user - root.enableSplitMode.active = true - } - } - - Connections { - target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null - function onToggleSplitModeFinished() { - root.enableSplitMode.active = false - - enableSplitMode_enable.loading = false - enableSplitMode_cancel.enabled = true - } - } - - action: [ - Action { - id: enableSplitMode_cancel - text: qsTr("Cancel") - onTriggered: { - root.enableSplitMode.active = false - } - }, - Action { - id: enableSplitMode_enable - text: qsTr("Enable split mode") - onTriggered: { - enableSplitMode_enable.loading = true - enableSplitMode_cancel.enabled = false - root.enableSplitMode.user.toggleSplitMode(true) - } - } - ] - } - - property Notification resetBridge: Notification { - title: qsTr("Reset Bridge?") - brief: title - icon: "./icons/ic-exclamation-circle-filled.svg" - description: qsTr("This will clear your accounts, preferences, and cached data. You will need to reconfigure your email client. Bridge will automatically restart.") - type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - property var user - - Connections { - target: root - function onAskResetBridge() { - root.resetBridge.active = true - } - } - - Connections { - target: Backend - function onResetFinished() { - root.resetBridge.active = false - - resetBridge_reset.loading = false - resetBridge_cancel.enabled = true - } - } - - action: [ - Action { - id: resetBridge_cancel - text: qsTr("Cancel") - onTriggered: { - root.resetBridge.active = false - } - }, - Action { - id: resetBridge_reset - text: qsTr("Reset and restart") - onTriggered: { - resetBridge_reset.loading = true - resetBridge_cancel.enabled = false - Backend.triggerReset() - } - } - ] - } - - property Notification changeAllMailVisibility: Notification { - title: root.changeAllMailVisibility.isVisibleNow ? - qsTr("Hide All Mail folder?") : - qsTr("Show All Mail folder?") - brief: title - icon: "./icons/ic-info-circle-filled.svg" - description: qsTr("Switching between showing and hiding the All Mail folder will require you to restart your client.") - type: Notification.NotificationType.Info - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - property var isVisibleNow - - Connections { - target: root - - function onAskChangeAllMailVisibility(isVisibleNow) { - root.changeAllMailVisibility.isVisibleNow = isVisibleNow - root.changeAllMailVisibility.active = true - } - } - - action: [ - Action { - id: allMail_change - text: root.changeAllMailVisibility.isVisibleNow ? - qsTr("Hide All Mail folder") : - qsTr("Show All Mail folder") - onTriggered: { - Backend.changeIsAllMailVisible(!root.changeAllMailVisibility.isVisibleNow) - root.changeAllMailVisibility.active = false - } - }, - Action { - id: allMail_cancel - text: qsTr("Cancel") - onTriggered: { - root.changeAllMailVisibility.active = false - } - } - ] - } - - property Notification deleteAccount: Notification { - title: qsTr("Remove this account?") - brief: title - icon: "./icons/ic-exclamation-circle-filled.svg" - description: qsTr("Are you sure you want to remove this account from Bridge and delete locally stored preferences and data?") - type: Notification.NotificationType.Danger - group: Notifications.Group.Configuration | Notifications.Group.Dialogs - - property var user - - Connections { - target: root - function onAskDeleteAccount(user) { - root.deleteAccount.user = user - root.deleteAccount.active = true - } - } - - action: [ - Action { - id: deleteAccount_cancel - text: qsTr("Cancel") - onTriggered: { - root.deleteAccount.active = false - } - }, - Action { - id: deleteAccount_delete - text: qsTr("Remove this account") - onTriggered: { - root.deleteAccount.user.remove() - root.deleteAccount.active = false - } - } - ] - } - - property Notification noKeychain: Notification { - title: qsTr("No keychain available") - brief: title - description: qsTr("Bridge is not able to detect a supported password manager (pass or secret-service). Please install and setup supported password manager and restart the application.") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Dialogs | Notifications.Group.Configuration - - Connections { - target: Backend - - function onNotifyHasNoKeychain() { - root.noKeychain.active = true - } - } - - action: [ - Action { - text: qsTr("Quit Bridge") - - onTriggered: { - Backend.quit() - } - }, - Action { - text: qsTr("Restart Bridge") - - onTriggered: { - Backend.restart() - } - } - ] - } - - property Notification rebuildKeychain: Notification { - title: qsTr("Your macOS keychain might be corrupted") - brief: title - description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Dialogs | Notifications.Group.Configuration - - property var supportLink: "https://proton.me/support/bridge" - - - Connections { - target: Backend - - function onNotifyRebuildKeychain() { - console.log("notifications") - root.rebuildKeychain.active = true - } - } - - action: [ - Action { - text: qsTr("Open the support page") - - onTriggered: { - Backend.notifyKBArticleClicked(root.rebuildKeychain.supportLink); - Qt.openUrlExternally(root.rebuildKeychain.supportLink) - Backend.quit() - } - } - ] - } - property Notification addressChanged: Notification { - title: qsTr("Address list changes") brief: title description: qsTr("The address list for your account has changed. You might need to reconfigure your email client.") - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Warning group: Notifications.Group.Configuration - - Connections { - target: Backend - - function onAddressChanged(address) { - root.addressChanged.description = qsTr("The address list for your account %1 has changed. You might need to reconfigure your email client.").arg(address) - root.addressChanged.active = true - } - - function onAddressChangedLogout(address) { - root.addressChanged.description = qsTr("The address list for your account %1 has changed. You have to reconfigure your email client.").arg(address) - root.addressChanged.active = true - } - } + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Address list changes") + type: Notification.NotificationType.Warning action: [ Action { text: qsTr("OK") onTriggered: { - root.addressChanged.active = false + root.addressChanged.active = false; } } ] - } - - property Notification apiCertIssue: Notification { - title: qsTr("Unable to establish a \nsecure connection to \nProton servers") - brief: qsTr("Cannot establish secure connection") - description: qsTr("Bridge cannot verify the authenticity of Proton servers on your current network due to a TLS certificate error. " + - "Start Bridge again after ensuring your connection is secure and/or connecting to a VPN. Learn more about TLS pinning " + - "here.") - - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Dialogs | Notifications.Group.Connection Connections { - target: Backend - - function onApiCertIssue() { - root.apiCertIssue.active = true + function onAddressChanged(address) { + root.addressChanged.description = qsTr("The address list for your account %1 has changed. You might need to reconfigure your email client.").arg(address); + root.addressChanged.active = true; } + function onAddressChangedLogout(address) { + root.addressChanged.description = qsTr("The address list for your account %1 has changed. You have to reconfigure your email client.").arg(address); + root.addressChanged.active = true; + } + + target: Backend } + } + property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.cacheUnavailable, root.cacheCantMove, root.accountChanged, root.diskFull, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.noActiveKeyForRecipient, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion] + property Notification alreadyLoggedIn: Notification { + brief: qsTr("Already signed in") + description: qsTr("This account is already signed in.") + group: Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Info + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.alreadyLoggedIn.active = false; + } + } + ] + + Connections { + function onLoginAlreadyLoggedIn(_) { + root.alreadyLoggedIn.active = true; + } + + target: Backend + } + } + property Notification apiCertIssue: Notification { + brief: qsTr("Cannot establish secure connection") + description: qsTr("Bridge cannot verify the authenticity of Proton servers on your current network due to a TLS certificate error. " + "Start Bridge again after ensuring your connection is secure and/or connecting to a VPN. Learn more about TLS pinning " + "here.") + group: Notifications.Group.Dialogs | Notifications.Group.Connection + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Unable to establish a \nsecure connection to \nProton servers") + type: Notification.NotificationType.Danger action: [ Action { @@ -1071,180 +116,1092 @@ QtObject { onTriggered: { root.apiCertIssue.active = false; - Backend.quit() + Backend.quit(); } } ] - } - - property Notification noActiveKeyForRecipient: Notification { - title: qsTr("Unable to send \nencrypted message") - brief: title - description: "#PlaceholderText#" - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Dialogs | Notifications.Group.Connection Connections { - target: Backend + function onApiCertIssue() { + root.apiCertIssue.active = true; + } - function onNoActiveKeyForRecipient(email) { - root.noActiveKeyForRecipient.description = qsTr("There are no active keys to encrypt your message to %1. "+ - "Please update the setting for this contact.").arg(email) - root.noActiveKeyForRecipient.active = true + target: Backend + } + } + property Notification bugReportSendError: Notification { + brief: qsTr("Error sending report") + description: qsTr("Report could not be sent. Try again or email us directly.") + group: Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Danger + + action: Action { + text: qsTr("OK") + + onTriggered: { + root.bugReportSendError.active = false; } } + Connections { + function onBugReportSendError() { + root.bugReportSendError.active = true; + } + + target: Backend + } + } + + // Bug reports + property Notification bugReportSendSuccess: Notification { + brief: qsTr("Report sent") + description: qsTr("Thank you for the report. We'll get back to you as soon as we can.") + group: Notifications.Group.Configuration + icon: "./icons/ic-info-circle-filled.svg" + type: Notification.NotificationType.Success + action: [ Action { text: qsTr("OK") onTriggered: { - root.noActiveKeyForRecipient.active = false + root.bugReportSendSuccess.active = false; } } ] - } - - property Notification userBadEvent: Notification { - title: qsTr("Internal error") - brief: title - description: "#PlaceHolderText" - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection | Notifications.Group.Dialogs - - property var userID: "" Connections { - target: Backend - function onUserBadEvent(userID, errorMessage) { - root.userBadEvent.userID = userID - root.userBadEvent.description = errorMessage - root.userBadEvent.active = true + function onBugReportSendSuccess() { + root.bugReportSendSuccess.active = true; } + + target: Backend } + } + property Notification cacheCantMove: Notification { + brief: title + description: qsTr("The location you have selected is not available. Make sure you have enough free space or choose another location.") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Can’t move cache") + type: Notification.NotificationType.Warning action: [ Action { - text: qsTr("Synchronize") + text: qsTr("Cancel") onTriggered: { - root.userBadEvent.active = false - Backend.sendBadEventUserFeedback(root.userBadEvent.userID, true) + root.cacheCantMove.active = false; } }, - Action { - text: qsTr("Logout") + text: qsTr("Change location") onTriggered: { - root.userBadEvent.active = false - Backend.sendBadEventUserFeedback(root.userBadEvent.userID, false) + root.cacheCantMove.active = false; + root.frontendMain.showLocalCacheSettings(); } } ] - } - - property Notification imapLoginWhileSignedOut: Notification { - title: qsTr("IMAP Login failed") - brief: title - description: "#PlaceHolderText" - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger - group: Notifications.Group.Connection - Connections { - target: Backend - function onImapLoginWhileSignedOut(username) { - root.imapLoginWhileSignedOut.description = qsTr("An email client tried to connect to the account %1, but this account is signed " + - "out. Please sign-in to continue.").arg(username) - root.imapLoginWhileSignedOut.active = true + function onCantMoveDiskCache() { + root.cacheCantMove.active = true; } + + target: Backend } + } + property Notification cacheLocationChangeSuccess: Notification { + brief: description + description: qsTr("Cache location successfully changed") + group: Notifications.Group.Configuration + icon: "./icons/ic-info-circle-filled.svg" + type: Notification.NotificationType.Success action: [ Action { text: qsTr("OK") onTriggered: { - root.imapLoginWhileSignedOut.active = false + root.cacheLocationChangeSuccess.active = false; } } ] + + Connections { + function onDiskCachePathChanged() { + console.log("notify location changed successfully"); + root.cacheLocationChangeSuccess.active = true; + } + + target: Backend + } } + // Cache + property Notification cacheUnavailable: Notification { + brief: title + description: qsTr("The current cache location is unavailable. Check the directory or change it in your settings.") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Cache location is unavailable") + type: Notification.NotificationType.Warning + + action: [ + Action { + text: qsTr("Quit Bridge") + + onTriggered: { + Backend.quit(); + root.cacheUnavailable.active = false; + } + }, + Action { + text: qsTr("Change location") + + onTriggered: { + root.cacheUnavailable.active = false; + root.frontendMain.showLocalCacheSettings(); + } + } + ] + + Connections { + function onDiskCacheUnavailable() { + root.cacheUnavailable.active = true; + } + + target: Backend + } + } + property Notification changeAllMailVisibility: Notification { + property var isVisibleNow + + brief: title + description: qsTr("Switching between showing and hiding the All Mail folder will require you to restart your client.") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-info-circle-filled.svg" + title: root.changeAllMailVisibility.isVisibleNow ? qsTr("Hide All Mail folder?") : qsTr("Show All Mail folder?") + type: Notification.NotificationType.Info + + action: [ + Action { + id: allMail_change + text: root.changeAllMailVisibility.isVisibleNow ? qsTr("Hide All Mail folder") : qsTr("Show All Mail folder") + + onTriggered: { + Backend.changeIsAllMailVisible(!root.changeAllMailVisibility.isVisibleNow); + root.changeAllMailVisibility.active = false; + } + }, + Action { + id: allMail_cancel + text: qsTr("Cancel") + + onTriggered: { + root.changeAllMailVisibility.active = false; + } + } + ] + + Connections { + function onAskChangeAllMailVisibility(isVisibleNow) { + root.changeAllMailVisibility.isVisibleNow = isVisibleNow; + root.changeAllMailVisibility.active = true; + } + + target: root + } + } + property Notification deleteAccount: Notification { + property var user + + brief: title + description: qsTr("Are you sure you want to remove this account from Bridge and delete locally stored preferences and data?") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Remove this account?") + type: Notification.NotificationType.Danger + + action: [ + Action { + id: deleteAccount_cancel + text: qsTr("Cancel") + + onTriggered: { + root.deleteAccount.active = false; + } + }, + Action { + id: deleteAccount_delete + text: qsTr("Remove this account") + + onTriggered: { + root.deleteAccount.user.remove(); + root.deleteAccount.active = false; + } + } + ] + + Connections { + function onAskDeleteAccount(user) { + root.deleteAccount.user = user; + root.deleteAccount.active = true; + } + + target: root + } + } + property Notification diskFull: Notification { + brief: title + description: qsTr("Quit Bridge and free disk space or disable the local cache (not recommended).") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Your disk is almost full") + type: Notification.NotificationType.Warning + + action: [ + Action { + text: qsTr("Quit Bridge") + + onTriggered: { + Backend.quit(); + root.diskFull.active = false; + } + }, + Action { + text: qsTr("Settings") + + onTriggered: { + root.diskFull.active = false; + root.frontendMain.showLocalCacheSettings(); + } + } + ] + + Connections { + function onDiskFull() { + root.diskFull.active = true; + } + + target: Backend + } + } + property Notification enableBeta: Notification { + brief: title + description: qsTr("Be the first to get new updates and use new features. Bridge will update to the latest beta version.") + group: Notifications.Group.Update | Notifications.Group.Dialogs + icon: "./icons/ic-info-circle-filled.svg" + title: qsTr("Enable Beta access") + type: Notification.NotificationType.Info + + action: [ + Action { + text: qsTr("Enable") + + onTriggered: { + Backend.toggleBeta(true); + root.enableBeta.active = false; + } + }, + Action { + text: qsTr("Cancel") + + onTriggered: { + root.enableBeta.active = false; + } + } + ] + + Connections { + function onAskEnableBeta() { + root.enableBeta.active = true; + } + + target: root + } + } + property Notification enableSplitMode: Notification { + property var user + + brief: title + description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "/qml/icons/ic-question-circle.svg" + title: qsTr("Enable split mode?") + type: Notification.NotificationType.Warning + + action: [ + Action { + id: enableSplitMode_cancel + text: qsTr("Cancel") + + onTriggered: { + root.enableSplitMode.active = false; + } + }, + Action { + id: enableSplitMode_enable + text: qsTr("Enable split mode") + + onTriggered: { + enableSplitMode_enable.loading = true; + enableSplitMode_cancel.enabled = false; + root.enableSplitMode.user.toggleSplitMode(true); + } + } + ] + + Connections { + function onAskEnableSplitMode(user) { + root.enableSplitMode.user = user; + root.enableSplitMode.active = true; + } + + target: root + } + Connections { + function onToggleSplitModeFinished() { + root.enableSplitMode.active = false; + enableSplitMode_enable.loading = false; + enableSplitMode_cancel.enabled = true; + } + + target: (root && root.enableSplitMode && root.enableSplitMode.user) ? root.enableSplitMode.user : null + } + } + property MainWindow frontendMain property Notification genericError: Notification { - title: "" brief: title description: "" - icon: "./icons/ic-exclamation-circle-filled.svg" - type: Notification.NotificationType.Danger group: Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: "" + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.genericError.active = false; + } + } + ] + Connections { - target: Backend function onGenericError(title, description) { - root.genericError.title = title - root.genericError.description = description + root.genericError.title = title; + root.genericError.description = description; root.genericError.active = true; } + + target: Backend } - - action: [ - Action { - text: qsTr("OK") - - onTriggered: { - root.genericError.active = false - } - } - ] } - property Notification genericQuestion: Notification { - title: "" - brief: title - description: "" - type: Notification.NotificationType.Warning - group: Notifications.Group.Dialogs - property var option1: "" - property var option2: "" property variant action1: null property variant action2: null + property var option1: "" + property var option2: "" - Connections { - target: root - function onAskQuestion(title, description, option1, option2, action1, action2) { - root.genericQuestion.title = title - root.genericQuestion.description = description - root.genericQuestion.option1 = option1 - root.genericQuestion.option2 = option2 - root.genericQuestion.action1 = action1 - root.genericQuestion.action2 = action2 - root.genericQuestion.active = true - } - } + brief: title + description: "" + group: Notifications.Group.Dialogs + title: "" + type: Notification.NotificationType.Warning action: [ Action { text: root.genericQuestion.option1 onTriggered: { - root.genericQuestion.active = false + root.genericQuestion.active = false; if (root.genericQuestion.action1) - root.genericQuestion.action1() + root.genericQuestion.action1(); } }, Action { text: root.genericQuestion.option2 onTriggered: { - root.genericQuestion.active = false + root.genericQuestion.active = false; if (root.genericQuestion.action2) - root.genericQuestion.action2() + root.genericQuestion.action2(); } } ] + + Connections { + function onAskQuestion(title, description, option1, option2, action1, action2) { + root.genericQuestion.title = title; + root.genericQuestion.description = description; + root.genericQuestion.option1 = option1; + root.genericQuestion.option2 = option2; + root.genericQuestion.action1 = action1; + root.genericQuestion.action2 = action2; + root.genericQuestion.active = true; + } + + target: root + } } + property Notification imapConnectionModeChangeError: Notification { + brief: qsTr("IMAP Connection mode error") + description: qsTr("The IMAP connection mode could not be changed.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + action: Action { + text: qsTr("OK") + + onTriggered: { + root.imapConnectionModeChangeError.active = false; + } + } + + Connections { + function onImapConnectionModeChangeError() { + root.imapConnectionModeChangeError.active = true; + } + + target: Backend + } + } + property Notification imapLoginWhileSignedOut: Notification { + brief: title + description: "#PlaceHolderText" + group: Notifications.Group.Connection + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("IMAP Login failed") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.imapLoginWhileSignedOut.active = false; + } + } + ] + + Connections { + function onImapLoginWhileSignedOut(username) { + root.imapLoginWhileSignedOut.description = qsTr("An email client tried to connect to the account %1, but this account is signed " + "out. Please sign-in to continue.").arg(username); + root.imapLoginWhileSignedOut.active = true; + } + + target: Backend + } + } + property Notification imapPortChangeError: Notification { + brief: qsTr("IMAP port error") + description: qsTr("The IMAP port could not be changed.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + Connections { + function onImapPortChangeError() { + root.imapPortChangeError.active = true; + } + + target: Backend + } + } + property Notification imapPortStartupError: Notification { + brief: qsTr("IMAP port error") + description: qsTr("The IMAP server could not be started. Please check or change the IMAP port.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + Connections { + function onImapPortStartupError() { + root.imapPortStartupError.active = true; + } + + target: Backend + } + } + + // login + property Notification loginConnectionError: Notification { + brief: qsTr("Connection error") + description: qsTr("Bridge is not able to contact the server, please check your internet connection.") + group: Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.loginConnectionError.active = false; + } + } + ] + + Connections { + function onLoginConnectionError(_) { + root.loginConnectionError.active = true; + } + + target: Backend + } + } + property Notification noActiveKeyForRecipient: Notification { + brief: title + description: "#PlaceholderText#" + group: Notifications.Group.Dialogs | Notifications.Group.Connection + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Unable to send \nencrypted message") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.noActiveKeyForRecipient.active = false; + } + } + ] + + Connections { + function onNoActiveKeyForRecipient(email) { + root.noActiveKeyForRecipient.description = qsTr("There are no active keys to encrypt your message to %1. " + "Please update the setting for this contact.").arg(email); + root.noActiveKeyForRecipient.active = true; + } + + target: Backend + } + } + + // Connection + property Notification noInternet: Notification { + brief: qsTr("No connection") + description: qsTr("Bridge is not able to contact the server, please check your internet connection.") + group: Notifications.Group.Connection + icon: "./icons/ic-no-connection.svg" + type: Notification.NotificationType.Danger + + Connections { + function onInternetOff() { + root.noInternet.active = true; + } + function onInternetOn() { + root.noInternet.active = false; + } + + target: Backend + } + } + property Notification noKeychain: Notification { + brief: title + description: qsTr("Bridge is not able to detect a supported password manager (pass or secret-service). Please install and setup supported password manager and restart the application.") + group: Notifications.Group.Dialogs | Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("No keychain available") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Quit Bridge") + + onTriggered: { + Backend.quit(); + } + }, + Action { + text: qsTr("Restart Bridge") + + onTriggered: { + Backend.restart(); + } + } + ] + + Connections { + function onNotifyHasNoKeychain() { + root.noKeychain.active = true; + } + + target: Backend + } + } + property Notification onlyPaidUsers: Notification { + property var pricingLink: "https://proton.me/mail/pricing" + + brief: qsTr("Upgrade your account") + description: qsTr("Bridge is exclusive to our mail paid plans. Upgrade your account to use Bridge.") + group: Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Upgrade") + + onTriggered: { + Qt.openUrlExternally(root.onlyPaidUsers.pricingLink); + root.onlyPaidUsers.active = false; + } + } + ] + + Connections { + function onLoginFreeUserError() { + root.onlyPaidUsers.active = true; + } + + target: Backend + } + } + property Notification rebuildKeychain: Notification { + property var supportLink: "https://proton.me/support/bridge" + + brief: title + description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.") + group: Notifications.Group.Dialogs | Notifications.Group.Configuration + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Your macOS keychain might be corrupted") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Open the support page") + + onTriggered: { + Backend.notifyKBArticleClicked(root.rebuildKeychain.supportLink); + Qt.openUrlExternally(root.rebuildKeychain.supportLink); + Backend.quit(); + } + } + ] + + Connections { + function onNotifyRebuildKeychain() { + console.log("notifications"); + root.rebuildKeychain.active = true; + } + + target: Backend + } + } + property Notification resetBridge: Notification { + property var user + + brief: title + description: qsTr("This will clear your accounts, preferences, and cached data. You will need to reconfigure your email client. Bridge will automatically restart.") + group: Notifications.Group.Configuration | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Reset Bridge?") + type: Notification.NotificationType.Danger + + action: [ + Action { + id: resetBridge_cancel + text: qsTr("Cancel") + + onTriggered: { + root.resetBridge.active = false; + } + }, + Action { + id: resetBridge_reset + text: qsTr("Reset and restart") + + onTriggered: { + resetBridge_reset.loading = true; + resetBridge_cancel.enabled = false; + Backend.triggerReset(); + } + } + ] + + Connections { + function onAskResetBridge() { + root.resetBridge.active = true; + } + + target: root + } + Connections { + function onResetFinished() { + root.resetBridge.active = false; + resetBridge_reset.loading = false; + resetBridge_cancel.enabled = true; + } + + target: Backend + } + } + property Notification smtpConnectionModeChangeError: Notification { + brief: qsTr("SMTP Connection mode error") + description: qsTr("The SMTP connection mode could not be changed.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + action: Action { + text: qsTr("OK") + + onTriggered: { + root.smtpConnectionModeChangeError.active = false; + } + } + + Connections { + function onSmtpConnectionModeChangeError() { + root.smtpConnectionModeChangeError.active = true; + } + + target: Backend + } + } + property Notification smtpPortChangeError: Notification { + brief: qsTr("SMTP port error") + description: qsTr("The SMTP port could not be changed.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + Connections { + function onSmtpPortChangeError() { + root.smtpPortChangeError.active = true; + } + + target: Backend + } + } + property Notification smtpPortStartupError: Notification { + brief: qsTr("SMTP port error") + description: qsTr("The SMTP server could not be started. Please check or change the SMTP port.") + group: Notifications.Group.Connection + icon: "./icons/ic-alert.svg" + type: Notification.NotificationType.Danger + + Connections { + function onSmtpPortStartupError() { + root.smtpPortStartupError.active = true; + } + + target: Backend + } + } + property Notification updateForce: Notification { + brief: qsTr("Bridge is outdated") + description: qsTr("This version of Bridge is no longer supported, please update.") + group: Notifications.Group.Update | Notifications.Group.ForceUpdate | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Update to Bridge %1").arg(data ? data.version : "") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Install update") + + onTriggered: { + Backend.installUpdate(); + root.updateForce.active = false; + } + }, + Action { + text: qsTr("Update manually") + + onTriggered: { + Qt.openUrlExternally(Backend.landingPageLink); + root.updateForce.active = false; + } + }, + Action { + text: qsTr("Quit Bridge") + + onTriggered: { + Backend.quit(); + root.updateForce.active = false; + } + } + ] + + Connections { + function onUpdateForce(version) { + root.updateForce.data = { + "version": version + }; + root.updateForce.active = true; + } + + target: Backend + } + } + property Notification updateForceError: Notification { + brief: title + description: qsTr("You must update manually. Go to: https://proton.me/mail/bridge#download") + group: Notifications.Group.Update | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Bridge couldn't update") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Update manually") + + onTriggered: { + Qt.openUrlExternally(Backend.landingPageLink); + root.updateForceError.active = false; + } + }, + Action { + text: qsTr("Quit Bridge") + + onTriggered: { + Backend.quit(); + root.updateForceError.active = false; + } + } + ] + + Connections { + function onUpdateForceError() { + root.updateForceError.active = true; + } + + target: Backend + } + } + property Notification updateIsLatestVersion: Notification { + brief: description + description: qsTr("Bridge is up to date") + group: Notifications.Group.Update + icon: "./icons/ic-info-circle-filled.svg" + type: Notification.NotificationType.Info + + action: Action { + text: qsTr("OK") + + onTriggered: { + root.updateIsLatestVersion.active = false; + } + } + + Connections { + function onUpdateIsLatestVersion() { + root.updateIsLatestVersion.active = true; + } + + target: Backend + } + } + property Notification updateManualError: Notification { + brief: title + description: qsTr("Please follow manual installation in order to update Bridge.") + group: Notifications.Group.Update + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Bridge couldn’t update") + type: Notification.NotificationType.Warning + + action: [ + Action { + text: qsTr("Update manually") + + onTriggered: { + Qt.openUrlExternally(Backend.landingPageLink); + root.updateManualError.active = false; + Backend.quit(); + } + }, + Action { + text: qsTr("Remind me later") + + onTriggered: { + root.updateManualError.active = false; + } + } + ] + + Connections { + function onUpdateManualError() { + root.updateManualError.active = true; + } + + target: Backend + } + } + + // Updates + property Notification updateManualReady: Notification { + brief: qsTr("Update available") + description: { + const descr = qsTr("A new version of Proton Mail Bridge is available."); + const text = qsTr("See what's changed."); + const link = Backend.releaseNotesLink; + return `${descr} ${text}`; + } + group: Notifications.Group.Update | Notifications.Group.Dialogs + icon: "./icons/ic-info-circle-filled.svg" + title: qsTr("Update to Bridge %1").arg(data ? data.version : "") + type: Notification.NotificationType.Info + + action: [ + Action { + text: qsTr("Install update") + + onTriggered: { + Backend.installUpdate(); + root.updateManualReady.active = false; + } + }, + Action { + text: qsTr("Update manually") + + onTriggered: { + Qt.openUrlExternally(Backend.landingPageLink); + root.updateManualReady.active = false; + } + }, + Action { + text: qsTr("Remind me later") + + onTriggered: { + root.updateManualReady.active = false; + } + } + ] + + Connections { + function onUpdateManualReady(version) { + root.updateManualReady.data = { + "version": version + }; + root.updateManualReady.active = true; + } + + target: Backend + } + } + property Notification updateManualRestartNeeded: Notification { + brief: description + description: qsTr("Bridge update is ready") + group: Notifications.Group.Update + icon: "./icons/ic-info-circle-filled.svg" + type: Notification.NotificationType.Info + + action: Action { + text: qsTr("Restart Bridge") + + onTriggered: { + Backend.restart(); + root.updateManualRestartNeeded.active = false; + } + } + + Connections { + function onUpdateManualRestartNeeded() { + root.updateManualRestartNeeded.active = true; + } + + target: Backend + } + } + property Notification updateSilentError: Notification { + brief: description + description: qsTr("Bridge couldn't update") + group: Notifications.Group.Update + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Warning + + action: Action { + text: qsTr("Update manually") + + onTriggered: { + Qt.openUrlExternally(Backend.landingPageLink); + root.updateSilentError.active = false; + } + } + + Connections { + function onUpdateSilentError() { + root.updateSilentError.active = true; + } + + target: Backend + } + } + property Notification updateSilentRestartNeeded: Notification { + brief: description + description: qsTr("Bridge update is ready") + group: Notifications.Group.Update + icon: "./icons/ic-info-circle-filled.svg" + type: Notification.NotificationType.Info + + action: Action { + text: qsTr("Restart Bridge") + + onTriggered: { + Backend.restart(); + root.updateSilentRestartNeeded.active = false; + } + } + + Connections { + function onUpdateSilentRestartNeeded() { + root.updateSilentRestartNeeded.active = true; + } + + target: Backend + } + } + property Notification userBadEvent: Notification { + property var userID: "" + + brief: title + description: "#PlaceHolderText" + group: Notifications.Group.Connection | Notifications.Group.Dialogs + icon: "./icons/ic-exclamation-circle-filled.svg" + title: qsTr("Internal error") + type: Notification.NotificationType.Danger + + action: [ + Action { + text: qsTr("Synchronize") + + onTriggered: { + root.userBadEvent.active = false; + Backend.sendBadEventUserFeedback(root.userBadEvent.userID, true); + } + }, + Action { + text: qsTr("Logout") + + onTriggered: { + root.userBadEvent.active = false; + Backend.sendBadEventUserFeedback(root.userBadEvent.userID, false); + } + } + ] + + Connections { + function onUserBadEvent(userID, errorMessage) { + root.userBadEvent.userID = userID; + root.userBadEvent.description = errorMessage; + root.userBadEvent.active = true; + } + + target: Backend + } + } + + signal askChangeAllMailVisibility(var isVisibleNow) + signal askDeleteAccount(var user) + signal askEnableBeta + signal askEnableSplitMode(var user) + signal askQuestion(var title, var description, var option1, var option2, var action1, var action2) + signal askResetBridge } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/PortSettings.qml b/internal/frontend/bridge-gui/bridge-gui/qml/PortSettings.qml index 6e10b8d8..cd524ecf 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/PortSettings.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/PortSettings.qml @@ -1,178 +1,157 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton SettingsView { id: root - fillHeight: false - + property bool _valuesChanged: (imapField.text * 1 !== Backend.imapPort || smtpField.text * 1 !== Backend.smtpPort) property var notifications - property bool _valuesChanged: ( - imapField.text*1 !== Backend.imapPort || - smtpField.text*1 !== Backend.smtpPort - ) + function isPortFree(field) { + const num = field.text * 1; + if (num === Backend.imapPort) + return true; + if (num === Backend.smtpPort) + return true; + if (!Backend.isPortFree(num)) { + field.error = true; + field.errorString = qsTr("Port occupied"); + return false; + } + return true; + } + function setDefaultValues() { + imapField.text = Backend.imapPort; + smtpField.text = Backend.smtpPort; + imapField.error = false; + smtpField.error = false; + } + function validate(port) { + const num = port * 1; + if (!(num > 1 && num < 65536)) { + return qsTr("Invalid port number"); + } + if (imapField.text === smtpField.text) { + return qsTr("Port numbers must be different"); + } + } + + fillHeight: false + + Component.onCompleted: root.setDefaultValues() + onBack: { + root.setDefaultValues(); + } Label { + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Default ports") type: Label.Heading - Layout.fillWidth: true } - Label { + Layout.fillWidth: true + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: qsTr("Changes require reconfiguration of your email client.") type: Label.Body - color: root.colorScheme.text_weak - Layout.fillWidth: true wrapMode: Text.WordWrap } - RowLayout { spacing: 16 TextField { id: imapField + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.preferredWidth: 160 colorScheme: root.colorScheme label: qsTr("IMAP port") - Layout.preferredWidth: 160 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft validator: root.validate } TextField { id: smtpField + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.preferredWidth: 160 colorScheme: root.colorScheme label: qsTr("SMTP port") - Layout.preferredWidth: 160 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft validator: root.validate } } - Rectangle { Layout.fillWidth: true - height: 1 color: root.colorScheme.border_weak + height: 1 } - RowLayout { spacing: 12 Button { id: submitButton colorScheme: root.colorScheme - text: qsTr("Save") enabled: (!loading) && root._valuesChanged + text: qsTr("Save") + onClicked: { // removing error here because we may have set it manually (port occupied) - imapField.error = false - smtpField.error = false + imapField.error = false; + smtpField.error = false; // checking errors separately because we want to display "same port" error only once - imapField.validate() + imapField.validate(); if (imapField.error) { - return + return; } - smtpField.validate() + smtpField.validate(); if (smtpField.error) { - return + return; } - - submitButton.loading = true + submitButton.loading = true; // check both ports before returning an error - var err = false - err |= !isPortFree(imapField) - err |= !isPortFree(smtpField) + let err = false; + err |= !isPortFree(imapField); + err |= !isPortFree(smtpField); if (err) { - submitButton.loading = false - return + submitButton.loading = false; + return; } - // We turn off all port error notification. They well be restored if problems persist - root.notifications.imapPortStartupError.active = false - root.notifications.smtpPortStartupError.active = false - root.notifications.imapPortChangeError.active = false - root.notifications.smtpPortChangeError.active = false - - Backend.setMailServerSettings(imapField.text, smtpField.text, Backend.useSSLForIMAP, Backend.useSSLForSMTP) + // We turn off all port error notification. They will be restored if problems persist + root.notifications.imapPortStartupError.active = false; + root.notifications.smtpPortStartupError.active = false; + root.notifications.imapPortChangeError.active = false; + root.notifications.smtpPortChangeError.active = false; + Backend.setMailServerSettings(imapField.text, smtpField.text, Backend.useSSLForIMAP, Backend.useSSLForSMTP); } } - Button { colorScheme: root.colorScheme - text: qsTr("Cancel") - onClicked: root.back() secondary: true - } + text: qsTr("Cancel") + onClicked: root.back() + } Connections { - target: Backend - function onChangeMailServerSettingsFinished() { - submitButton.loading = false + submitButton.loading = false; } + + target: Backend } } - - onBack: { - root.setDefaultValues() - } - - function validate(port) { - var num = port*1 - if (! (num > 1 && num < 65536) ) { - return qsTr("Invalid port number") - } - - if (imapField.text == smtpField.text) { - return qsTr("Port numbers must be different") - } - - return - } - - function isPortFree(field) { - var num = field.text*1 - if (num === Backend.imapPort) return true - if (num === Backend.smtpPort) return true - if (!Backend.isPortFree(num)) { - field.error = true - field.errorString = qsTr("Port occupied") - return false - } - - return true - } - - function setDefaultValues(){ - imapField.text = Backend.imapPort - smtpField.text = Backend.smtpPort - imapField.error = false - smtpField.error = false - } - - Component.onCompleted: root.setDefaultValues() } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Action.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Action.qml index 49af2ede..9f982d18 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Action.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Action.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Templates as T diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ApplicationWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ApplicationWindow.qml index 70b56fa0..788aaf89 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ApplicationWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ApplicationWindow.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Window @@ -25,14 +20,14 @@ import QtQuick.Templates as T T.ApplicationWindow { id: root - property ColorScheme colorScheme - // popup priority based on types enum PopupType { - Banner = 0, - Dialog = 1 + Banner, + Dialog } + property ColorScheme colorScheme + // contains currently visible popup property var popupVisible: null @@ -41,85 +36,61 @@ T.ApplicationWindow { // overriding get method to ignore any role and return directly object itself function get(row) { if (row < 0 || row >= count) { - return undefined + return undefined; } - return data(index(row, 0), Qt.DisplayRole) - } - - onRowsInserted: function(parent, first, last) { - for (var i = first; i <= last; i++) { - var obj = popups.get(i) - obj.onShouldShowChanged.connect( root.processPopups ) - } - - processPopups() + return data(index(row, 0), Qt.DisplayRole); } onRowsAboutToBeRemoved: function (parent, first, last) { - for (var i = first; i <= last; i++ ) { - var obj = popups.get(i) - obj.onShouldShowChanged.disconnect( root.processPopups ) + for (let i = first; i <= last; i++) { + const obj = popups.get(i); + obj.onShouldShowChanged.disconnect(root.processPopups); // if currently visible popup was removed if (root.popupVisible === obj) { - root.popupVisible.visible = false - root.popupVisible = null + root.popupVisible.visible = false; + root.popupVisible = null; } } - - processPopups() + processPopups(); + } + onRowsInserted: function (parent, first, last) { + for (let i = first; i <= last; i++) { + const obj = popups.get(i); + obj.onShouldShowChanged.connect(root.processPopups); + } + processPopups(); } } function processPopups() { if ((root.popupVisible) && (!root.popupVisible.shouldShow)) { - root.popupVisible.visible = false + root.popupVisible.visible = false; } - - var topmost = null - for (var i = 0; i < popups.count; i++) { - var obj = popups.get(i) - + let topmost = null; + for (let i = 0; i < popups.count; i++) { + const obj = popups.get(i); if (obj.shouldShow === false) { - continue + continue; } - if (topmost && (topmost.popupType > obj.popupType)) { - continue + continue; } - if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) { - continue + continue; } - - topmost = obj + topmost = obj; } - if (root.popupVisible !== topmost) { if (root.popupVisible) { - root.popupVisible.visible = false + root.popupVisible.visible = false; } - root.popupVisible = topmost + root.popupVisible = topmost; } - if (!root.popupVisible) { - return - } - - root.popupVisible.visible = true - } - - Connections { - target: root.popupVisible - - function onVisibleChanged() { - if (root.popupVisible.visible) { - return - } - - root.popupVisible = null - root.processPopups() + return; } + root.popupVisible.visible = true; } color: root.colorScheme.background_norm @@ -127,8 +98,19 @@ T.ApplicationWindow { Overlay.modal: Rectangle { color: root.colorScheme.backdrop_norm } - Overlay.modeless: Rectangle { color: "transparent" } + + Connections { + function onVisibleChanged() { + if (root.popupVisible.visible) { + return; + } + root.popupVisible = null; + root.processPopups(); + } + + target: root.popupVisible + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index 34f7c2e9..af085093 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl @@ -23,212 +18,169 @@ import QtQuick.Layouts import "." as Proton T.Button { - property ColorScheme colorScheme - - property alias secondary: control.flat - readonly property bool primary: !secondary - readonly property bool isIcon: control.text === "" - readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0) - property bool loading: false - - property bool borderless: false - - property int labelType: Proton.Label.LabelType.Body - - property alias textVerticalAlignment: label.verticalAlignment - property alias textHorizontalAlignment: label.horizontalAlignment - id: control - implicitWidth: Math.max( - implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding - ) - implicitHeight: Math.max( - implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding - ) - - padding: 8 - horizontalPadding: 16 - spacing: 10 + property bool borderless: false + property ColorScheme colorScheme + readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0) + readonly property bool isIcon: control.text === "" + property int labelType: Proton.Label.LabelType.Body + property bool loading: false + readonly property bool primary: !secondary + property alias secondary: control.flat + property alias textHorizontalAlignment: label.horizontalAlignment + property alias textVerticalAlignment: label.verticalAlignment font: label.font - - icon.width: 16 - icon.height: 16 + horizontalPadding: 16 icon.color: { if (primary && !isIcon) { - return "#FFFFFF" + return "#FFFFFF"; } else { - return control.colorScheme.text_norm + return control.colorScheme.text_norm; } } + icon.height: 16 + icon.width: 16 + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) + padding: 8 + spacing: 10 + background: Rectangle { + border.color: { + return control.colorScheme.border_norm; + } + border.width: secondary && !borderless ? 1 : 0 + color: { + if (!isIcon) { + if (primary) { + // Primary colors + if (control.down) { + return control.colorScheme.interaction_norm_active; + } + if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { + return control.colorScheme.interaction_norm_hover; + } + if (control.loading) { + return control.colorScheme.interaction_norm_hover; + } + return control.colorScheme.interaction_norm; + } else { + // Secondary colors + if (control.down) { + return control.colorScheme.interaction_default_active; + } + if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { + return control.colorScheme.interaction_default_hover; + } + if (control.loading) { + return control.colorScheme.interaction_default_hover; + } + return control.colorScheme.interaction_default; + } + } else { + if (primary) { + // Primary icon colors + if (control.down) { + return control.colorScheme.interaction_default_active; + } + if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { + return control.colorScheme.interaction_default_hover; + } + if (control.loading) { + return control.colorScheme.interaction_default_hover; + } + return control.colorScheme.interaction_default; + } else { + // Secondary icon colors + if (control.down) { + return control.colorScheme.interaction_default_active; + } + if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { + return control.colorScheme.interaction_default_hover; + } + if (control.loading) { + return control.colorScheme.interaction_default_hover; + } + return control.colorScheme.interaction_default; + } + } + } + implicitHeight: 36 + implicitWidth: 36 + opacity: control.enabled || control.loading ? 1.0 : 0.5 + radius: ProtonStyle.button_radius + visible: true + } contentItem: RowLayout { id: _contentItem spacing: control.hasTextAndIcon ? control.spacing : 0 Proton.Label { - colorScheme: root.colorScheme id: label - - Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter - - elide: Text.ElideRight - horizontalAlignment: Qt.AlignHCenter - - visible: !control.isIcon - text: control.text + Layout.fillWidth: true color: { if (primary && !isIcon) { - return "#FFFFFF" + return "#FFFFFF"; } else { - return control.colorScheme.text_norm + return control.colorScheme.text_norm; } } + colorScheme: root.colorScheme + elide: Text.ElideRight + horizontalAlignment: Qt.AlignHCenter opacity: control.enabled || control.loading ? 1.0 : 0.5 - + text: control.text type: labelType + visible: !control.isIcon } - ColorImage { id: iconImage - Layout.alignment: Qt.AlignCenter - + color: control.icon.color + height: { + if (control.loading) { + return width; + } + Math.min(control.icon.height, availableHeight); + } + source: control.loading ? "/qml/icons/Loader_16.svg" : control.icon.source + sourceSize.height: control.icon.height + sourceSize.width: control.icon.width + visible: control.loading || control.icon.source width: { // special case for loading since we want icon to be square for rotation animation if (control.loading) { - return Math.min(control.icon.width, availableWidth, control.icon.height, availableHeight) + return Math.min(control.icon.width, availableWidth, control.icon.height, availableHeight); } - - return Math.min(control.icon.width, availableWidth) + return Math.min(control.icon.width, availableWidth); } - height: { - if (control.loading) { - return width - } - - Math.min(control.icon.height, availableHeight) - } - - sourceSize.width: control.icon.width - sourceSize.height: control.icon.height - - color: control.icon.color - source: control.loading ? "/qml/icons/Loader_16.svg" : control.icon.source - visible: control.loading || control.icon.source RotationAnimation { - target: iconImage - loops: Animation.Infinite + direction: RotationAnimation.Clockwise duration: 1000 from: 0 - to: 360 - direction: RotationAnimation.Clockwise + loops: Animation.Infinite running: control.loading + target: iconImage + to: 360 } } } - background: Rectangle { - implicitWidth: 36 - implicitHeight: 36 - radius: ProtonStyle.button_radius - visible: true - color: { - if (!isIcon) { - if (primary) { - // Primary colors - - if (control.down) { - return control.colorScheme.interaction_norm_active - } - - if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { - return control.colorScheme.interaction_norm_hover - } - - if (control.loading) { - return control.colorScheme.interaction_norm_hover - } - - return control.colorScheme.interaction_norm - } else { - // Secondary colors - - if (control.down) { - return control.colorScheme.interaction_default_active - } - - if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { - return control.colorScheme.interaction_default_hover - } - - if (control.loading) { - return control.colorScheme.interaction_default_hover - } - - return control.colorScheme.interaction_default - } - } else { - if (primary) { - // Primary icon colors - - if (control.down) { - return control.colorScheme.interaction_default_active - } - - if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { - return control.colorScheme.interaction_default_hover - } - - if (control.loading) { - return control.colorScheme.interaction_default_hover - } - - return control.colorScheme.interaction_default - } else { - // Secondary icon colors - - if (control.down) { - return control.colorScheme.interaction_default_active - } - - if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) { - return control.colorScheme.interaction_default_hover - } - - if (control.loading) { - return control.colorScheme.interaction_default_hover - } - - return control.colorScheme.interaction_default - } - } - } - - border.color: { - return control.colorScheme.border_norm - } - border.width: secondary && !borderless ? 1 : 0 - - opacity: control.enabled || control.loading ? 1.0 : 0.5 - } - - Component.onCompleted: { if (!control.colorScheme) { - console.trace() - var next = root - for (var i = 0; i<1000; i++) { - console.log(i, next, "colorscheme", next.colorScheme) - next = next.parent - if (!next) break + console.trace(); + let next = root; + for (let i = 0; i < 1000; i++) { + console.log(i, next, "colorscheme", next.colorScheme); + next = next.parent; + if (!next) + break; } - console.error("ColorScheme not defined") + console.error("ColorScheme not defined"); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/CheckBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/CheckBox.qml index 5c6b82ad..2dc7551d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/CheckBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/CheckBox.qml @@ -1,97 +1,96 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Templates as T T.CheckBox { - property ColorScheme colorScheme - - property bool error: false - id: control - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding, - implicitIndicatorHeight + topPadding + bottomPadding) + property ColorScheme colorScheme + property bool error: false + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) padding: 0 spacing: 8 + contentItem: CheckLabel { + color: { + if (!enabled) { + return control.colorScheme.text_disabled; + } + if (error) { + return control.colorScheme.signal_danger; + } + return control.colorScheme.text_norm; + } + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 + lineHeight: ProtonStyle.body_line_height + lineHeightMode: Text.FixedHeight + rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 + text: control.text + } indicator: Rectangle { - implicitWidth: 20 + border.color: { + if (!control.enabled) { + return control.colorScheme.field_disabled; + } + if (control.error) { + return control.colorScheme.signal_danger; + } + if (control.hovered || control.activeFocus) { + return control.colorScheme.interaction_norm_hover; + } + return control.colorScheme.field_norm; + } + border.width: control.checked ? 0 : 1 + color: { + if (!checked) { + return control.colorScheme.background_norm; + } + if (!control.enabled) { + return control.colorScheme.field_disabled; + } + if (control.error) { + return control.colorScheme.signal_danger; + } + if (control.hovered || control.activeFocus) { + return control.colorScheme.interaction_norm_hover; + } + return control.colorScheme.interaction_norm; + } implicitHeight: 20 + implicitWidth: 20 radius: ProtonStyle.checkbox_radius - x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 y: control.topPadding + (control.availableHeight - height) / 2 - color: { - if (!checked) { - return control.colorScheme.background_norm - } - - if (!control.enabled) { - return control.colorScheme.field_disabled - } - - if (control.error) { - return control.colorScheme.signal_danger - } - - if (control.hovered || control.activeFocus) { - return control.colorScheme.interaction_norm_hover - } - - return control.colorScheme.interaction_norm - } - - border.width: control.checked ? 0 : 1 - border.color: { - if (!control.enabled) { - return control.colorScheme.field_disabled - } - - if (control.error) { - return control.colorScheme.signal_danger - } - - if (control.hovered || control.activeFocus) { - return control.colorScheme.interaction_norm_hover - } - - return control.colorScheme.field_norm - } - ColorImage { + color: "#FFFFFF" + height: parent.height - 4 + source: "/qml/icons/ic-check.svg" + sourceSize.height: parent.height - 4 + sourceSize.width: parent.width - 4 + visible: control.checkState === Qt.Checked + width: parent.width - 4 x: (parent.width - width) / 2 y: (parent.height - height) / 2 - - width: parent.width - 4 - height: parent.height - 4 - sourceSize.width: parent.width - 4 - sourceSize.height: parent.height - 4 - color: "#FFFFFF" - source: "/qml/icons/ic-check.svg" - visible: control.checkState === Qt.Checked } // TODO: do we need PartiallyChecked state? @@ -105,30 +104,4 @@ T.CheckBox { // visible: control.checkState === Qt.PartiallyChecked //} } - - contentItem: CheckLabel { - leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 - rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 - - text: control.text - - color: { - if (!enabled) { - return control.colorScheme.text_disabled - } - - if (error) { - return control.colorScheme.signal_danger - } - - return control.colorScheme.text_norm - } - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - lineHeight: ProtonStyle.body_line_height - lineHeightMode: Text.FixedHeight - font.letterSpacing: ProtonStyle.body_letter_spacing - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml index 89585091..d795b7f4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml @@ -1,93 +1,88 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQml QtObject { - // should be a pointer to ColorScheme object - property var prominent - // Primary - property color primary_norm + // Backdrop + property color backdrop_norm + property color background_avatar - // Interaction-norm - property color interaction_norm - property color interaction_norm_hover - property color interaction_norm_active - - // Text - property color text_norm - property color text_weak - property color text_hint - property color text_disabled - property color text_invert - - // Field - property color field_norm - property color field_hover - property color field_disabled + // Background + property color background_norm + property color background_strong + property color background_weak // Border property color border_norm property color border_weak + property color field_disabled + property color field_hover - // Background - property color background_norm - property color background_weak - property color background_strong - property color background_avatar - - // Interaction-weak - property color interaction_weak - property color interaction_weak_hover - property color interaction_weak_active + // Field + property color field_norm // Interaction-default property color interaction_default - property color interaction_default_hover property color interaction_default_active + property color interaction_default_hover + + // Interaction-norm + property color interaction_norm + property color interaction_norm_active + property color interaction_norm_hover + + // Interaction-weak + property color interaction_weak + property color interaction_weak_active + property color interaction_weak_hover + property string logo_img + + // Primary + property color primary_norm + // should be a pointer to ColorScheme object + property var prominent + property color scrollbar_hover // Scrollbar property color scrollbar_norm - property color scrollbar_hover - - // Signal - property color signal_danger - property color signal_danger_hover - property color signal_danger_active - property color signal_warning - property color signal_warning_hover - property color signal_warning_active - property color signal_success - property color signal_success_hover - property color signal_success_active - property color signal_info - property color signal_info_hover - property color signal_info_active + property color shadow_lifted // Shadows property color shadow_norm - property color shadow_lifted - // Backdrop - property color backdrop_norm + // Signal + property color signal_danger + property color signal_danger_active + property color signal_danger_hover + property color signal_info + property color signal_info_active + property color signal_info_hover + property color signal_success + property color signal_success_active + property color signal_success_hover + property color signal_warning + property color signal_warning_active + property color signal_warning_hover + property color text_disabled + property color text_hint + property color text_invert + + // Text + property color text_norm + property color text_weak // Images property string welcome_img - property string logo_img } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ComboBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ComboBox.qml index 988abeb0..0d904f86 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ComboBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ComboBox.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Window import QtQuick.Controls @@ -26,148 +21,124 @@ T.ComboBox { property ColorScheme colorScheme - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding, - implicitIndicatorHeight + topPadding + bottomPadding) - + bottomPadding: 5 + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) leftPadding: 12 + (!root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) rightPadding: 12 + (root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) - - topPadding: 5 - bottomPadding: 5 - spacing: 8 + topPadding: 5 - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - font.letterSpacing: ProtonStyle.body_letter_spacing - + background: Rectangle { + border.color: root.colorScheme.border_norm + border.width: 1 + color: { + if (root.down) { + return root.colorScheme.interaction_default_active; + } + if (root.enabled && root.hovered || root.activeFocus) { + return root.colorScheme.interaction_default_hover; + } + if (!root.enabled) { + return root.colorScheme.interaction_default; + } + return root.colorScheme.background_norm; + } + implicitHeight: 36 + implicitWidth: 140 + radius: ProtonStyle.context_item_radius + } contentItem: T.TextField { - padding: 5 - - text: root.editable ? root.editText : root.displayText - font: root.font - - enabled: root.editable autoScroll: root.editable - readOnly: root.down + color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled + enabled: root.editable + font: root.font inputMethodHints: root.inputMethodHints + padding: 5 + placeholderTextColor: root.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + readOnly: root.down + selectedTextColor: root.colorScheme.text_invert + selectionColor: root.colorScheme.interaction_norm + text: root.editable ? root.editText : root.displayText validator: root.validator verticalAlignment: TextInput.AlignVCenter - color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - selectionColor: root.colorScheme.interaction_norm - selectedTextColor: root.colorScheme.text_invert - placeholderTextColor: root.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled - background: Rectangle { - radius: ProtonStyle.context_item_radius - visible: root.enabled && root.editable && !root.flat border.color: { if (root.activeFocus) { - return root.colorScheme.interaction_norm + return root.colorScheme.interaction_norm; } - if (root.hovered || root.activeFocus) { - return root.colorScheme.field_hover + return root.colorScheme.field_hover; } - - return root.colorScheme.field_norm + return root.colorScheme.field_norm; } border.width: 1 color: root.colorScheme.background_norm + radius: ProtonStyle.context_item_radius + visible: root.enabled && root.editable && !root.flat } } - - background: Rectangle { - implicitWidth: 140 - implicitHeight: 36 - radius: ProtonStyle.context_item_radius - color: { - if (root.down) { - return root.colorScheme.interaction_default_active - } - - if (root.enabled && root.hovered || root.activeFocus) { - return root.colorScheme.interaction_default_hover - } - - if (!root.enabled) { - return root.colorScheme.interaction_default - } - - return root.colorScheme.background_norm - } - - border.color: root.colorScheme.border_norm - border.width: 1 - } - - indicator: ColorImage { - x: root.mirrored ? 12 : root.width - width - 12 - y: root.topPadding + (root.availableHeight - height) / 2 - color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - source: popup.visible ? "/qml/icons/ic-chevron-up.svg" : "/qml/icons/ic-chevron-down.svg" - - sourceSize.width: 16 - sourceSize.height: 16 - } - - delegate: ItemDelegate { - width: parent.width - text: root.textRole ? (Array.isArray(root.model) ? modelData[root.textRole] : model[root.textRole]) : modelData - - palette.text: { - if (!root.enabled) { - return root.colorScheme.text_disabled - } - - if (selected) { - return root.colorScheme.text_invert - } - - return root.colorScheme.text_norm - } - font: root.font - - hoverEnabled: root.hoverEnabled - property bool selected: root.currentIndex === index + font: root.font highlighted: root.highlightedIndex === index + hoverEnabled: root.hoverEnabled palette.highlightedText: selected ? root.colorScheme.text_invert : root.colorScheme.text_norm + palette.text: { + if (!root.enabled) { + return root.colorScheme.text_disabled; + } + if (selected) { + return root.colorScheme.text_invert; + } + return root.colorScheme.text_norm; + } + text: root.textRole ? (Array.isArray(root.model) ? modelData[root.textRole] : model[root.textRole]) : modelData + width: parent.width background: PaddedRectangle { - radius: ProtonStyle.context_item_radius color: { if (parent.down) { - return root.colorScheme.interaction_default_active + return root.colorScheme.interaction_default_active; } - if (parent.selected) { - return root.colorScheme.interaction_norm + return root.colorScheme.interaction_norm; } - if (parent.hovered || parent.highlighted) { - return root.colorScheme.interaction_default_hover + return root.colorScheme.interaction_default_hover; } - - return root.colorScheme.interaction_default + return root.colorScheme.interaction_default; } + radius: ProtonStyle.context_item_radius } } - + indicator: ColorImage { + color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled + source: popup.visible ? "/qml/icons/ic-chevron-up.svg" : "/qml/icons/ic-chevron-down.svg" + sourceSize.height: 16 + sourceSize.width: 16 + x: root.mirrored ? 12 : root.width - width - 12 + y: root.topPadding + (root.availableHeight - height) / 2 + } popup: T.Popup { - y: root.height - width: root.width + bottomMargin: 8 height: Math.min(contentItem.implicitHeight, root.Window.height - topMargin - bottomMargin) topMargin: 8 - bottomMargin: 8 + width: root.width + y: root.height + background: Rectangle { + border.color: root.colorScheme.border_weak + border.width: 1 + color: root.colorScheme.background_norm + radius: ProtonStyle.dialog_radius + } contentItem: Item { implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin @@ -175,21 +146,14 @@ T.ComboBox { ListView { anchors.fill: parent anchors.margins: 8 - + currentIndex: root.highlightedIndex implicitHeight: contentHeight model: root.delegateModel - currentIndex: root.highlightedIndex spacing: 4 - T.ScrollIndicator.vertical: ScrollIndicator { } + T.ScrollIndicator.vertical: ScrollIndicator { + } } } - - background: Rectangle { - color: root.colorScheme.background_norm - radius: ProtonStyle.dialog_radius - border.color: root.colorScheme.border_weak - border.width: 1 - } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Dialog.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Dialog.qml index edbfc8bf..368bcb30 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Dialog.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Dialog.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Templates as T @@ -23,58 +18,46 @@ import QtQuick.Controls.impl T.Dialog { id: root + property ColorScheme colorScheme - - Component.onCompleted: { - if (!ApplicationWindow.window) { - return - } - - if (ApplicationWindow.window.popups === undefined) { - return - } - - var obj = this - ApplicationWindow.window.popups.append( { obj } ) - } - - readonly property int popupType: ApplicationWindow.PopupType.Dialog - - property bool shouldShow: false readonly property var occurred: shouldShow ? new Date() : undefined - function open() { - root.shouldShow = true - } + readonly property int popupType: ApplicationWindow.PopupType.Dialog + property bool shouldShow: false function close() { - root.shouldShow = false + root.shouldShow = false; + } + function open() { + root.shouldShow = true; } anchors.centerIn: Overlay.overlay - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - contentWidth + leftPadding + rightPadding, - implicitHeaderWidth, - implicitFooterWidth) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding - + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) - + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) - + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding, implicitHeaderWidth, implicitFooterWidth) padding: 24 + // TODO: Add DropShadow here + T.Overlay.modal: Rectangle { + color: root.colorScheme.backdrop_norm + } + T.Overlay.modeless: Rectangle { + color: "transparent" + } background: Rectangle { color: root.colorScheme.background_norm radius: ProtonStyle.dialog_radius } - // TODO: Add DropShadow here - - T.Overlay.modal: Rectangle { - color: root.colorScheme.backdrop_norm - } - - T.Overlay.modeless: Rectangle { - color: "transparent" + Component.onCompleted: { + if (!ApplicationWindow.window) { + return; + } + if (ApplicationWindow.window.popups === undefined) { + return; + } + const obj = this; + ApplicationWindow.window.popups.append({ + "obj": obj + }); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Label.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Label.qml index 052d530e..da4de95a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Label.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Label.qml @@ -1,32 +1,23 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Templates as T - import "." as Proton T.Label { id: root - - property ColorScheme colorScheme - enum LabelType { // weight 700, size 28, height 36 Heading, @@ -47,96 +38,92 @@ T.Label { // weight 700, size 12, height 16, spacing 0.4 Caption_bold } + + property ColorScheme colorScheme property int type: Proton.Label.LabelType.Body + function link(url, text) { + return `${text}`; + } + color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - linkColor: root.colorScheme.interaction_norm - palette.link: linkColor - font.family: ProtonStyle.font_family - lineHeightMode: Text.FixedHeight - - font.weight: { - switch (root.type) { - case Proton.Label.LabelType.Heading: - return ProtonStyle.fontWeight_700 - case Proton.Label.LabelType.Title: - return ProtonStyle.fontWeight_700 - case Proton.Label.LabelType.Lead: - return ProtonStyle.fontWeight_400 - case Proton.Label.LabelType.Body: - return ProtonStyle.fontWeight_400 - case Proton.Label.LabelType.Body_semibold: - return ProtonStyle.fontWeight_600 - case Proton.Label.LabelType.Body_bold: - return ProtonStyle.fontWeight_700 - case Proton.Label.LabelType.Caption: - return ProtonStyle.fontWeight_400 - case Proton.Label.LabelType.Caption_semibold: - return ProtonStyle.fontWeight_600 - case Proton.Label.LabelType.Caption_bold: - return ProtonStyle.fontWeight_700 - } - } - - font.pixelSize: { - switch (root.type) { - case Proton.Label.LabelType.Heading: - return ProtonStyle.heading_font_size - case Proton.Label.LabelType.Title: - return ProtonStyle.title_font_size - case Proton.Label.LabelType.Lead: - return ProtonStyle.lead_font_size - case Proton.Label.LabelType.Body: - case Proton.Label.LabelType.Body_semibold: - case Proton.Label.LabelType.Body_bold: - return ProtonStyle.body_font_size - case Proton.Label.LabelType.Caption: - case Proton.Label.LabelType.Caption_semibold: - case Proton.Label.LabelType.Caption_bold: - return ProtonStyle.caption_font_size - } - } - - lineHeight: { - switch (root.type) { - case Proton.Label.LabelType.Heading: - return ProtonStyle.heading_line_height - case Proton.Label.LabelType.Title: - return ProtonStyle.title_line_height - case Proton.Label.LabelType.Lead: - return ProtonStyle.lead_line_height - case Proton.Label.LabelType.Body: - case Proton.Label.LabelType.Body_semibold: - case Proton.Label.LabelType.Body_bold: - return ProtonStyle.body_line_height - case Proton.Label.LabelType.Caption: - case Proton.Label.LabelType.Caption_semibold: - case Proton.Label.LabelType.Caption_bold: - return ProtonStyle.caption_line_height - } - } - font.letterSpacing: { switch (root.type) { case Proton.Label.LabelType.Heading: case Proton.Label.LabelType.Title: case Proton.Label.LabelType.Lead: - return 0 + return 0; case Proton.Label.LabelType.Body: case Proton.Label.LabelType.Body_semibold: case Proton.Label.LabelType.Body_bold: - return ProtonStyle.body_letter_spacing + return ProtonStyle.body_letter_spacing; case Proton.Label.LabelType.Caption: case Proton.Label.LabelType.Caption_semibold: case Proton.Label.LabelType.Caption_bold: - return ProtonStyle.caption_letter_spacing + return ProtonStyle.caption_letter_spacing; } } - - verticalAlignment: Text.AlignBottom - - function link(url, text) { - return `${text}` + font.pixelSize: { + switch (root.type) { + case Proton.Label.LabelType.Heading: + return ProtonStyle.heading_font_size; + case Proton.Label.LabelType.Title: + return ProtonStyle.title_font_size; + case Proton.Label.LabelType.Lead: + return ProtonStyle.lead_font_size; + case Proton.Label.LabelType.Body: + case Proton.Label.LabelType.Body_semibold: + case Proton.Label.LabelType.Body_bold: + return ProtonStyle.body_font_size; + case Proton.Label.LabelType.Caption: + case Proton.Label.LabelType.Caption_semibold: + case Proton.Label.LabelType.Caption_bold: + return ProtonStyle.caption_font_size; + } } + font.weight: { + switch (root.type) { + case Proton.Label.LabelType.Heading: + return ProtonStyle.fontWeight_700; + case Proton.Label.LabelType.Title: + return ProtonStyle.fontWeight_700; + case Proton.Label.LabelType.Lead: + return ProtonStyle.fontWeight_400; + case Proton.Label.LabelType.Body: + return ProtonStyle.fontWeight_400; + case Proton.Label.LabelType.Body_semibold: + return ProtonStyle.fontWeight_600; + case Proton.Label.LabelType.Body_bold: + return ProtonStyle.fontWeight_700; + case Proton.Label.LabelType.Caption: + return ProtonStyle.fontWeight_400; + case Proton.Label.LabelType.Caption_semibold: + return ProtonStyle.fontWeight_600; + case Proton.Label.LabelType.Caption_bold: + return ProtonStyle.fontWeight_700; + } + } + lineHeight: { + switch (root.type) { + case Proton.Label.LabelType.Heading: + return ProtonStyle.heading_line_height; + case Proton.Label.LabelType.Title: + return ProtonStyle.title_line_height; + case Proton.Label.LabelType.Lead: + return ProtonStyle.lead_line_height; + case Proton.Label.LabelType.Body: + case Proton.Label.LabelType.Body_semibold: + case Proton.Label.LabelType.Body_bold: + return ProtonStyle.body_line_height; + case Proton.Label.LabelType.Caption: + case Proton.Label.LabelType.Caption_semibold: + case Proton.Label.LabelType.Caption_bold: + return ProtonStyle.caption_line_height; + } + } + lineHeightMode: Text.FixedHeight + linkColor: root.colorScheme.interaction_norm + palette.link: linkColor + verticalAlignment: Text.AlignBottom } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Menu.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Menu.qml index 02026509..0655f1de 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Menu.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Menu.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl @@ -27,22 +22,19 @@ T.Menu { property ColorScheme colorScheme - implicitWidth: Math.max( - implicitBackgroundWidth + leftInset + rightInset, - contentWidth + leftPadding + rightPadding - ) - implicitHeight: Math.max( - implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding - ) - + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) margins: 0 overlap: 1 - delegate: MenuItem { - colorScheme: control.colorScheme + background: Rectangle { + border.color: colorScheme.border_weak + border.width: 1 + color: colorScheme.background_norm + implicitHeight: 40 + implicitWidth: 200 + radius: ProtonStyle.account_row_radius } - contentItem: Item { implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin @@ -50,23 +42,17 @@ T.Menu { ListView { anchors.fill: parent anchors.margins: 8 - - implicitHeight: contentHeight - model: control.contentModel - interactive: Window.window ? contentHeight > Window.window.height : false clip: true currentIndex: control.currentIndex + implicitHeight: contentHeight + interactive: Window.window ? contentHeight > Window.window.height : false + model: control.contentModel - ScrollIndicator.vertical: ScrollIndicator {} + ScrollIndicator.vertical: ScrollIndicator { + } } } - - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - color: colorScheme.background_norm - border.width: 1 - border.color: colorScheme.border_weak - radius: ProtonStyle.account_row_radius + delegate: MenuItem { + colorScheme: control.colorScheme } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml index 7c30d353..3d19cbf8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl @@ -26,46 +21,39 @@ T.MenuItem { property ColorScheme colorScheme - width: parent.width // required. Other item overflows to the right of the menu and get clipped. - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding, - implicitIndicatorHeight + topPadding + bottomPadding) - + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled + icon.height: 24 + icon.width: 24 + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) padding: 12 spacing: 6 - - icon.width: 24 - icon.height: 24 - icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - font.letterSpacing: ProtonStyle.body_letter_spacing - - contentItem: IconLabel { - id: iconLabel - readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0 - readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0 - leftPadding: !control.mirrored ? indicatorPadding : arrowPadding - rightPadding: control.mirrored ? indicatorPadding : arrowPadding - - spacing: control.spacing - mirrored: control.mirrored - display: control.display - alignment: Qt.AlignLeft - - icon: control.icon - text: control.text - font: control.font - - color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled - } + width: parent.width // required. Other item overflows to the right of the menu and get clipped. background: Rectangle { - implicitWidth: 164 - implicitHeight: 36 - radius: ProtonStyle.button_radius color: control.down ? control.colorScheme.interaction_default_active : control.highlighted ? control.colorScheme.interaction_default_hover : control.colorScheme.interaction_default + implicitHeight: 36 + implicitWidth: 164 + radius: ProtonStyle.button_radius + } + contentItem: IconLabel { + id: iconLabel + + readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0 + readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0 + + alignment: Qt.AlignLeft + color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled + display: control.display + font: control.font + icon: control.icon + leftPadding: !control.mirrored ? indicatorPadding : arrowPadding + mirrored: control.mirrored + rightPadding: control.mirrored ? indicatorPadding : arrowPadding + spacing: control.spacing + text: control.text } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Popup.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Popup.qml index bf980b8d..b2be86c0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Popup.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Popup.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Controls @@ -23,45 +18,40 @@ import QtQuick.Templates as T T.Popup { id: root + property ColorScheme colorScheme - - Component.onCompleted: { - if (!ApplicationWindow.window) { - return - } - - if (ApplicationWindow.window.popups === undefined) { - return - } - - var obj = this - ApplicationWindow.window.popups.append( { obj } ) - } - - property int popupType: ApplicationWindow.PopupType.Banner - - property bool shouldShow: false readonly property var occurred: shouldShow ? new Date() : undefined - function open() { - root.shouldShow = true - } + property int popupType: ApplicationWindow.PopupType.Banner + property bool shouldShow: false function close() { - root.shouldShow = false + root.shouldShow = false; + } + function open() { + root.shouldShow = true; } - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - contentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) // TODO: Add DropShadow here - T.Overlay.modal: Rectangle { color: root.colorScheme.backdrop_norm } - T.Overlay.modeless: Rectangle { color: "transparent" } + + Component.onCompleted: { + if (!ApplicationWindow.window) { + return; + } + if (ApplicationWindow.window.popups === undefined) { + return; + } + const obj = this; + ApplicationWindow.window.popups.append({ + "obj": obj + }); + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/RadioButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/RadioButton.qml index 2c31863e..833d409b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/RadioButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/RadioButton.qml @@ -1,115 +1,91 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Templates as T T.RadioButton { - property ColorScheme colorScheme - - property bool error: false - id: control - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding, - implicitIndicatorHeight + topPadding + bottomPadding) + property ColorScheme colorScheme + property bool error: false + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) padding: 0 spacing: 8 + contentItem: CheckLabel { + color: { + if (!enabled) { + return control.colorScheme.text_disabled; + } + if (error) { + return control.colorScheme.signal_danger; + } + return control.colorScheme.text_norm; + } + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 + lineHeight: ProtonStyle.body_line_height + lineHeightMode: Text.FixedHeight + rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 + text: control.text + } indicator: Rectangle { - implicitWidth: 20 + border.color: { + if (!control.enabled) { + return control.colorScheme.field_disabled; + } + if (control.error) { + return control.colorScheme.signal_danger; + } + if (control.hovered || control.activeFocus) { + return control.colorScheme.interaction_norm_hover; + } + return control.colorScheme.field_norm; + } + border.width: 1 + color: control.colorScheme.background_norm implicitHeight: 20 + implicitWidth: 20 radius: width / 2 - x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 y: control.topPadding + (control.availableHeight - height) / 2 - color: control.colorScheme.background_norm - border.width: 1 - border.color: { - if (!control.enabled) { - return control.colorScheme.field_disabled - } - - if (control.error) { - return control.colorScheme.signal_danger - } - - if (control.hovered || control.activeFocus) { - return control.colorScheme.interaction_norm_hover - } - - return control.colorScheme.field_norm - } - Rectangle { - x: (parent.width - width) / 2 - y: (parent.height - height) / 2 - width: 8 - height: 8 - radius: width / 2 color: { if (!control.enabled) { - return control.colorScheme.field_disabled + return control.colorScheme.field_disabled; } - if (control.error) { - return control.colorScheme.signal_danger + return control.colorScheme.signal_danger; } - if (control.hovered || control.activeFocus) { - return control.colorScheme.interaction_norm_hover + return control.colorScheme.interaction_norm_hover; } - - return control.colorScheme.interaction_norm + return control.colorScheme.interaction_norm; } + height: 8 + radius: width / 2 visible: control.checked + width: 8 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 } } - - contentItem: CheckLabel { - leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 - rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 - - text: control.text - - color: { - if (!enabled) { - return control.colorScheme.text_disabled - } - - if (error) { - return control.colorScheme.signal_danger - } - - return control.colorScheme.text_norm - } - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - lineHeight: ProtonStyle.body_line_height - lineHeightMode: Text.FixedHeight - font.letterSpacing: ProtonStyle.body_letter_spacing - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 4d542822..3e6379a3 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -1,387 +1,188 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - pragma Singleton import QtQml import QtQuick - -import "./" +import "." // https://wiki.qt.io/Qml_Styling // http://imaginativethinking.ca/make-qml-component-singleton/ - QtObject { id: root - // TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows: - // https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components - - // component ColorScheme: QtObject { - // property color primary_norm - // ... - // } - - property ColorScheme lightStyle: ColorScheme { - id: _lightStyle - - prominent: lightProminentStyle - - // Primary - primary_norm: "#6D4AFF" - - // Interaction-norm - interaction_norm: "#6D4AFF" - interaction_norm_hover: "#4D34B3" - interaction_norm_active: "#372580" - - // Text - text_norm: "#0C0C14" - text_weak: "#706D6B" - text_hint: "#8F8D8A" - text_disabled: "#C2BFBC" - text_invert: "#FFFFFF" - - // Field - field_norm: "#ADABA8" - field_hover: "#8F8D8A" - field_disabled: "#D1CFCD" - - // Border - border_norm: "#D1CFCD" - border_weak: "#EAE7E4" - - // Background - background_norm: "#FFFFFF" - background_weak: "#F5F4F2" - background_strong: "#EAE7E4" - background_avatar: "#C2BFBC" - - // Interaction-weak - interaction_weak: "#D1CFCD" - interaction_weak_hover: "#C2BFBC" - interaction_weak_active: "#A8A6A3" - - // Interaction-default - interaction_default: Qt.rgba(0,0,0,0) - interaction_default_hover: Qt.rgba(194./255., 191./255., 188./255., 0.2) - interaction_default_active: Qt.rgba(194./255., 191./255., 188./255., 0.4) - - // Scrollbar - scrollbar_norm: "#D1CFCD" - scrollbar_hover: "#C2BFBC" - - // Signal - signal_danger: "#DC3251" - signal_danger_hover: "#F74F6D" - signal_danger_active: "#B72346" - signal_warning: "#FF9900" - signal_warning_hover: "#FFB800" - signal_warning_active: "#FF851A" - signal_success: "#1EA885" - signal_success_hover: "#23C299" - signal_success_active: "#198F71" - signal_info: "#239ECE" - signal_info_hover: "#27B1E8" - signal_info_active: "#1F83B5" - - // Shadows - shadow_norm: Qt.rgba(0,0,0, 0.1) // #000000 10% x:0 y:1 blur:4 - shadow_lifted: Qt.rgba(0,0,0, 0.16) // #000000 16% x:0 y:8 blur:24 - - // Backdrop - backdrop_norm: Qt.rgba(12./255., 12./255., 20./255., 0.32) - - // Images - welcome_img: "/qml/icons/img-welcome.png" - logo_img: "/qml/icons/product_logos.svg" - } - - property ColorScheme lightProminentStyle: ColorScheme { - id: _lightProminentStyle - - prominent: this - - // Primary - primary_norm: "#8A6EFF" - - // Interaction-norm - interaction_norm: "#6D4AFF" - interaction_norm_hover: "#7C5CFF" - interaction_norm_active: "#8A6EFF" - - // Text - text_norm: "#FFFFFF" - text_weak: "#9282D4" - text_hint: "#544399" - text_disabled: "#4A398F" - text_invert: "#1B1340" - - // Field - field_norm: "#9282D4" - field_hover: "#7C5CFF" - field_disabled: "#38277A" - - // Border - border_norm: "#413085" - border_weak: "#3C2B80" - - // Background - background_norm: "#1B1340" - background_weak: "#271C57" - background_strong: "#38277A" - background_avatar: "#6D4AFF" - - // Interaction-weak - interaction_weak: "#4A398F" - interaction_weak_hover: "#6D4AFF" - interaction_weak_active: "#8A6EFF" - - // Interaction-default - interaction_default: Qt.rgba(0,0,0,0) - interaction_default_hover: Qt.rgba(68./255., 78./255., 114./255., 0.2) - interaction_default_active: Qt.rgba(68./255., 78./255., 114./255., 0.3) - - // Scrollbar - scrollbar_norm: "#413085" - scrollbar_hover: "#4A398F" - - // Signal - signal_danger: "#F5385A" - signal_danger_hover: "#FF5473" - signal_danger_active: "#DC3251" - signal_warning: "#FF9900" - signal_warning_hover: "#FFB800" - signal_warning_active: "#FF8419" - signal_success: "#1EA885" - signal_success_hover: "#23C299" - signal_success_active: "#198F71" - signal_info: "#2C89DB" - signal_info_hover: "#3491E3" - signal_info_active: "#1F83B5" - - // Shadows - shadow_norm: Qt.rgba(0,0,0, 0.32) // #000000 32% x:0 y:1 blur:4 - shadow_lifted: Qt.rgba(0,0,0, 0.40) // #000000 40% x:0 y:8 blur:24 - - // Backdrop - backdrop_norm: Qt.rgba(0,0,0, 0.32) - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" - logo_img: "/qml/icons/product_logos_dark.svg" - } - - property ColorScheme darkStyle: ColorScheme { - id: _darkStyle - - prominent: darkProminentStyle - - // Primary - primary_norm: "#8A6EFF" - - // Interaction-norm - interaction_norm: "#6D4AFF" - interaction_norm_hover: "#7C5CFF" - interaction_norm_active: "#8A6EFF" - - // Text - text_norm: "#FFFFFF" - text_weak: "#A7A4B5" - text_hint: "#6D697D" - text_disabled: "#5B576B" - text_invert: "#1C1B24" - - // Field - field_norm: "#5B576B" - field_hover: "#6D697D" - field_disabled: "#3F3B4C" - - // Border - border_norm: "#4A4658" - border_weak: "#343140" - - // Background - background_norm: "#1C1B24" - background_weak: "#292733" - background_strong: "#3F3B4C" - background_avatar: "#6D4AFF" - - // Interaction-weak - interaction_weak: "#4A4658" - interaction_weak_hover: "#5B576B" - interaction_weak_active: "#6D697D" - - // Interaction-default - interaction_default: "#00000000" - interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2) - interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4) - - // Scrollbar - scrollbar_norm: "#4A4658" - scrollbar_hover: "#5B576B" - - // Signal - signal_danger: "#F5385A" - signal_danger_hover: "#FF5473" - signal_danger_active: "#DC3251" - signal_warning: "#FF9900" - signal_warning_hover: "#FFB800" - signal_warning_active: "#FF8419" - signal_success: "#1EA885" - signal_success_hover: "#23C299" - signal_success_active: "#198F71" - signal_info: "#239ECE" - signal_info_hover: "#27B1E8" - signal_info_active: "#1F83B5" - - // Shadows - shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4 - shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24 - - // Backdrop - backdrop_norm: Qt.rgba(0,0,0,0.32) - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" - logo_img: "/qml/icons/product_logos_dark.svg" - } + property real account_hover_radius: 12 * root.px // px + property real account_row_radius: 12 * root.px // px + property real avatar_radius: 8 * root.px // px + property real banner_radius: 12 * root.px // px + property real big_avatar_radius: 12 * root.px // px + property int body_font_size: 14 + property real body_letter_spacing: 0.2 * root.px + property int body_line_height: 20 + property real button_radius: 8 * root.px // px + property int caption_font_size: 12 + property real caption_letter_spacing: 0.4 * root.px + property int caption_line_height: 16 + property real card_radius: 12 * root.px // px + property real checkbox_radius: 4 * root.px // px + property real context_item_radius: 8 * root.px // px + property ColorScheme currentStyle: lightStyle property ColorScheme darkProminentStyle: ColorScheme { id: _darkProminentStyle - prominent: this + // Backdrop + backdrop_norm: Qt.rgba(0, 0, 0, 0.32) + background_avatar: "#6D4AFF" - // Primary - primary_norm: "#8A6EFF" - - // Interaction-norm - interaction_norm: "#6D4AFF" - interaction_norm_hover: "#7C5CFF" - interaction_norm_active: "#8A6EFF" - - // Text - text_norm: "#FFFFFF" - text_weak: "#A7A4B5" - text_hint: "#6D697D" - text_disabled: "#5B576B" - text_invert: "#1C1B24" - - // Field - field_norm: "#5B576B" - field_hover: "#6D697D" - field_disabled: "#3F3B4C" + // Background + background_norm: "#16141c" + background_strong: "#3F3B4C" + background_weak: "#292733" // Border border_norm: "#4A4658" border_weak: "#343140" + field_disabled: "#3F3B4C" + field_hover: "#6D697D" - // Background - background_norm: "#16141c" - background_weak: "#292733" - background_strong: "#3F3B4C" - background_avatar: "#6D4AFF" - - // Interaction-weak - interaction_weak: "#4A4658" - interaction_weak_hover: "#5B576B" - interaction_weak_active: "#6D697D" + // Field + field_norm: "#5B576B" // Interaction-default interaction_default: "#00000000" - interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2) - interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4) + interaction_default_active: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.4) + interaction_default_hover: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.2) + + // Interaction-norm + interaction_norm: "#6D4AFF" + interaction_norm_active: "#8A6EFF" + interaction_norm_hover: "#7C5CFF" + + // Interaction-weak + interaction_weak: "#4A4658" + interaction_weak_active: "#6D697D" + interaction_weak_hover: "#5B576B" + logo_img: "/qml/icons/product_logos_dark.svg" + + // Primary + primary_norm: "#8A6EFF" + prominent: this + scrollbar_hover: "#5B576B" // Scrollbar scrollbar_norm: "#4A4658" - scrollbar_hover: "#5B576B" + shadow_lifted: Qt.rgba(0, 0, 0, 0.48) // #000000 48% x+0 y+8 blur:24 + + // Shadows + shadow_norm: Qt.rgba(0, 0, 0, 0.4) // #000000 40% x+0 y+1 blur:4 // Signal signal_danger: "#F5385A" - signal_danger_hover: "#FF5473" signal_danger_active: "#DC3251" - signal_warning: "#FF9900" - signal_warning_hover: "#FFB800" - signal_warning_active: "#FF8419" - signal_success: "#1EA885" - signal_success_hover: "#23C299" - signal_success_active: "#198F71" + signal_danger_hover: "#FF5473" signal_info: "#239ECE" - signal_info_hover: "#27B1E8" signal_info_active: "#1F83B5" + signal_info_hover: "#27B1E8" + signal_success: "#1EA885" + signal_success_active: "#198F71" + signal_success_hover: "#23C299" + signal_warning: "#FF9900" + signal_warning_active: "#FF8419" + signal_warning_hover: "#FFB800" + text_disabled: "#5B576B" + text_hint: "#6D697D" + text_invert: "#1C1B24" - // Shadows - shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4 - shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24 - - // Backdrop - backdrop_norm: Qt.rgba(0,0,0,0.32) + // Text + text_norm: "#FFFFFF" + text_weak: "#A7A4B5" // Images welcome_img: "/qml/icons/img-welcome-dark.png" - logo_img: "/qml/icons/product_logos_dark.svg" } + property ColorScheme darkStyle: ColorScheme { + id: _darkStyle - property ColorScheme currentStyle: lightStyle + // Backdrop + backdrop_norm: Qt.rgba(0, 0, 0, 0.32) + background_avatar: "#6D4AFF" - property string font_family: { - switch (Qt.platform.os) { - case "windows": - return "Segoe UI" - case "osx": - return ".AppleSystemUIFont" // should be SF Pro for the foreseeable future. Using "SF Pro Display" directly here is not allowed by the font's license. - case "linux": - return "Ubuntu" - default: - console.error("Unknown platform") - } + // Background + background_norm: "#1C1B24" + background_strong: "#3F3B4C" + background_weak: "#292733" + + // Border + border_norm: "#4A4658" + border_weak: "#343140" + field_disabled: "#3F3B4C" + field_hover: "#6D697D" + + // Field + field_norm: "#5B576B" + + // Interaction-default + interaction_default: "#00000000" + interaction_default_active: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.4) + interaction_default_hover: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.2) + + // Interaction-norm + interaction_norm: "#6D4AFF" + interaction_norm_active: "#8A6EFF" + interaction_norm_hover: "#7C5CFF" + + // Interaction-weak + interaction_weak: "#4A4658" + interaction_weak_active: "#6D697D" + interaction_weak_hover: "#5B576B" + logo_img: "/qml/icons/product_logos_dark.svg" + + // Primary + primary_norm: "#8A6EFF" + prominent: darkProminentStyle + scrollbar_hover: "#5B576B" + + // Scrollbar + scrollbar_norm: "#4A4658" + shadow_lifted: Qt.rgba(0, 0, 0, 0.48) // #000000 48% x+0 y+8 blur:24 + + // Shadows + shadow_norm: Qt.rgba(0, 0, 0, 0.4) // #000000 40% x+0 y+1 blur:4 + + // Signal + signal_danger: "#F5385A" + signal_danger_active: "#DC3251" + signal_danger_hover: "#FF5473" + signal_info: "#239ECE" + signal_info_active: "#1F83B5" + signal_info_hover: "#27B1E8" + signal_success: "#1EA885" + signal_success_active: "#198F71" + signal_success_hover: "#23C299" + signal_warning: "#FF9900" + signal_warning_active: "#FF8419" + signal_warning_hover: "#FFB800" + text_disabled: "#5B576B" + text_hint: "#6D697D" + text_invert: "#1C1B24" + + // Text + text_norm: "#FFFFFF" + text_weak: "#A7A4B5" + + // Images + welcome_img: "/qml/icons/img-welcome-dark.png" } - - property real px : 1.00 // px - - property real input_radius : 8 * root.px // px - property real button_radius : 8 * root.px // px - property real checkbox_radius : 4 * root.px // px - property real avatar_radius : 8 * root.px // px - property real big_avatar_radius : 12 * root.px // px - property real account_hover_radius : 12 * root.px // px - property real account_row_radius : 12 * root.px // px - property real context_item_radius : 8 * root.px // px - property real banner_radius : 12 * root.px // px - property real dialog_radius : 12 * root.px // px - property real card_radius : 12 * root.px // px - property real progress_bar_radius : 3 * root.px // px - property real tooltip_radius : 8 * root.px // px - - property int heading_font_size: 28 - property int heading_line_height: 36 - - property int title_font_size: 20 - property int title_line_height: 24 - - property int lead_font_size: 18 - property int lead_line_height: 26 - - property int body_font_size: 14 - property int body_line_height: 20 - property real body_letter_spacing: 0.2 * root.px - - property int caption_font_size: 12 - property int caption_line_height: 16 - property real caption_letter_spacing: 0.4 * root.px - + property real dialog_radius: 12 * root.px // px property int fontWeight_100: Font.Thin property int fontWeight_200: Font.Light property int fontWeight_300: Font.ExtraLight @@ -391,4 +192,179 @@ QtObject { property int fontWeight_700: Font.Bold property int fontWeight_800: Font.ExtraBold property int fontWeight_900: Font.Black + property string font_family: { + switch (Qt.platform.os) { + case "windows": + return "Segoe UI"; + case "osx": + return ".AppleSystemUIFont"; // should be SF Pro for the foreseeable future. Using "SF Pro Display" directly here is not allowed by the font's license. + case "linux": + return "Ubuntu"; + default: + console.error("Unknown platform"); + } + } + property int heading_font_size: 28 + property int heading_line_height: 36 + property real input_radius: 8 * root.px // px + property int lead_font_size: 18 + property int lead_line_height: 26 + property ColorScheme lightProminentStyle: ColorScheme { + id: _lightProminentStyle + + // Backdrop + backdrop_norm: Qt.rgba(0, 0, 0, 0.32) + background_avatar: "#6D4AFF" + + // Background + background_norm: "#1B1340" + background_strong: "#38277A" + background_weak: "#271C57" + + // Border + border_norm: "#413085" + border_weak: "#3C2B80" + field_disabled: "#38277A" + field_hover: "#7C5CFF" + + // Field + field_norm: "#9282D4" + + // Interaction-default + interaction_default: Qt.rgba(0, 0, 0, 0) + interaction_default_active: Qt.rgba(68. / 255., 78. / 255., 114. / 255., 0.3) + interaction_default_hover: Qt.rgba(68. / 255., 78. / 255., 114. / 255., 0.2) + + // Interaction-norm + interaction_norm: "#6D4AFF" + interaction_norm_active: "#8A6EFF" + interaction_norm_hover: "#7C5CFF" + + // Interaction-weak + interaction_weak: "#4A398F" + interaction_weak_active: "#8A6EFF" + interaction_weak_hover: "#6D4AFF" + logo_img: "/qml/icons/product_logos_dark.svg" + + // Primary + primary_norm: "#8A6EFF" + prominent: this + scrollbar_hover: "#4A398F" + + // Scrollbar + scrollbar_norm: "#413085" + shadow_lifted: Qt.rgba(0, 0, 0, 0.40) // #000000 40% x:0 y:8 blur:24 + + // Shadows + shadow_norm: Qt.rgba(0, 0, 0, 0.32) // #000000 32% x:0 y:1 blur:4 + + // Signal + signal_danger: "#F5385A" + signal_danger_active: "#DC3251" + signal_danger_hover: "#FF5473" + signal_info: "#2C89DB" + signal_info_active: "#1F83B5" + signal_info_hover: "#3491E3" + signal_success: "#1EA885" + signal_success_active: "#198F71" + signal_success_hover: "#23C299" + signal_warning: "#FF9900" + signal_warning_active: "#FF8419" + signal_warning_hover: "#FFB800" + text_disabled: "#4A398F" + text_hint: "#544399" + text_invert: "#1B1340" + + // Text + text_norm: "#FFFFFF" + text_weak: "#9282D4" + + // Images + welcome_img: "/qml/icons/img-welcome-dark.png" + } + // TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows: + // https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components + + // component ColorScheme: QtObject { + // property color primary_norm + // ... + // } + property ColorScheme lightStyle: ColorScheme { + id: _lightStyle + + // Backdrop + backdrop_norm: Qt.rgba(12. / 255., 12. / 255., 20. / 255., 0.32) + background_avatar: "#C2BFBC" + + // Background + background_norm: "#FFFFFF" + background_strong: "#EAE7E4" + background_weak: "#F5F4F2" + + // Border + border_norm: "#D1CFCD" + border_weak: "#EAE7E4" + field_disabled: "#D1CFCD" + field_hover: "#8F8D8A" + + // Field + field_norm: "#ADABA8" + + // Interaction-default + interaction_default: Qt.rgba(0, 0, 0, 0) + interaction_default_active: Qt.rgba(194. / 255., 191. / 255., 188. / 255., 0.4) + interaction_default_hover: Qt.rgba(194. / 255., 191. / 255., 188. / 255., 0.2) + + // Interaction-norm + interaction_norm: "#6D4AFF" + interaction_norm_active: "#372580" + interaction_norm_hover: "#4D34B3" + + // Interaction-weak + interaction_weak: "#D1CFCD" + interaction_weak_active: "#A8A6A3" + interaction_weak_hover: "#C2BFBC" + logo_img: "/qml/icons/product_logos.svg" + + // Primary + primary_norm: "#6D4AFF" + prominent: lightProminentStyle + scrollbar_hover: "#C2BFBC" + + // Scrollbar + scrollbar_norm: "#D1CFCD" + shadow_lifted: Qt.rgba(0, 0, 0, 0.16) // #000000 16% x:0 y:8 blur:24 + + // Shadows + shadow_norm: Qt.rgba(0, 0, 0, 0.1) // #000000 10% x:0 y:1 blur:4 + + // Signal + signal_danger: "#DC3251" + signal_danger_active: "#B72346" + signal_danger_hover: "#F74F6D" + signal_info: "#239ECE" + signal_info_active: "#1F83B5" + signal_info_hover: "#27B1E8" + signal_success: "#1EA885" + signal_success_active: "#198F71" + signal_success_hover: "#23C299" + signal_warning: "#FF9900" + signal_warning_active: "#FF851A" + signal_warning_hover: "#FFB800" + text_disabled: "#C2BFBC" + text_hint: "#8F8D8A" + text_invert: "#FFFFFF" + + // Text + text_norm: "#0C0C14" + text_weak: "#706D6B" + + // Images + welcome_img: "/qml/icons/img-welcome.png" + } + property real progress_bar_radius: 3 * root.px // px + property real px: 1.00 // px + property int title_font_size: 20 + property int title_line_height: 24 + property real tooltip_radius: 8 * root.px // px } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Switch.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Switch.qml index 567f5c7f..4d633b8d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Switch.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Switch.qml @@ -1,150 +1,124 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Templates as T import QtQuick.Controls import QtQuick.Controls.impl T.Switch { - property ColorScheme colorScheme + id: control + property ColorScheme colorScheme property bool loading: false + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) + padding: 0 + spacing: 7 + + contentItem: CheckLabel { + id: label + color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 + lineHeight: ProtonStyle.body_line_height + lineHeightMode: Text.FixedHeight + rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 + text: control.text + } + indicator: Rectangle { + border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm + border.width: control.enabled && !loading ? 1 : 0 + color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong + implicitHeight: 24 + implicitWidth: 40 + radius: height / 2. + x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 + y: control.topPadding + (control.availableHeight - height) / 2 + + Rectangle { + color: { + if (!control.enabled) { + return control.colorScheme.field_disabled; + } + if (control.checked) { + if (control.hovered || control.activeFocus) { + return control.colorScheme.interaction_norm_hover; + } + return control.colorScheme.interaction_norm; + } + if (control.hovered || control.activeFocus) { + return control.colorScheme.field_hover; + } + return control.colorScheme.field_norm; + } + height: 24 + radius: parent.radius + visible: !loading + width: 24 + x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2))) + y: (parent.height - height) / 2 + + Behavior on x { + enabled: !control.down + + SmoothedAnimation { + velocity: 200 + } + } + + ColorImage { + color: "#FFFFFF" + height: 16 + source: "/qml/icons/ic-check.svg" + sourceSize.height: 16 + sourceSize.width: 16 + visible: control.checked + width: 16 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + } + } + ColorImage { + id: loadingImage + color: control.colorScheme.interaction_norm_hover + height: 18 + source: "/qml/icons/Loader_16.svg" + sourceSize.height: 18 + sourceSize.width: 18 + visible: control.loading + width: 18 + x: parent.width - width + y: (parent.height - height) / 2 + + RotationAnimation { + direction: RotationAnimation.Clockwise + duration: 1000 + from: 0 + loops: Animation.Infinite + running: control.loading + target: loadingImage + to: 360 + } + } + } + // TODO: store previous enabled state and restore it? // For now assuming that only enabled buttons could have loading state onLoadingChanged: { - if (loading) { - enabled = false - } else { - enabled = true - } - } - - id: control - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding, - implicitIndicatorHeight + topPadding + bottomPadding) - - padding: 0 - spacing: 7 - - indicator: Rectangle { - implicitWidth: 40 - implicitHeight: 24 - - x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 - y: control.topPadding + (control.availableHeight - height) / 2 - - radius: height / 2. - color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong - border.width: control.enabled && !loading ? 1 : 0 - border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm - - Rectangle { - x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2))) - y: (parent.height - height) / 2 - width: 24 - height: 24 - radius: parent.radius - - visible: !loading - - color: { - if (!control.enabled) { - return control.colorScheme.field_disabled - } - - if (control.checked) { - if (control.hovered || control.activeFocus) { - return control.colorScheme.interaction_norm_hover - } - - return control.colorScheme.interaction_norm - } - - if (control.hovered || control.activeFocus) { - return control.colorScheme.field_hover - } - - return control.colorScheme.field_norm - } - - ColorImage { - x: (parent.width - width) / 2 - y: (parent.height - height) / 2 - - width: 16 - height: 16 - sourceSize.width: 16 - sourceSize.height: 16 - color: "#FFFFFF" - source: "/qml/icons/ic-check.svg" - visible: control.checked - } - - Behavior on x { - enabled: !control.down - SmoothedAnimation { velocity: 200 } - } - } - - ColorImage { - id: loadingImage - x: parent.width - width - y: (parent.height - height) / 2 - - width: 18 - height: 18 - sourceSize.width: 18 - sourceSize.height: 18 - color: control.colorScheme.interaction_norm_hover - source: "/qml/icons/Loader_16.svg" - visible: control.loading - - RotationAnimation { - target: loadingImage - loops: Animation.Infinite - duration: 1000 - from: 0 - to: 360 - direction: RotationAnimation.Clockwise - running: control.loading - } - } - } - - contentItem: CheckLabel { - id: label - leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 - rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 - - text: control.text - - color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - lineHeight: ProtonStyle.body_line_height - lineHeightMode: Text.FixedHeight - font.letterSpacing: ProtonStyle.body_letter_spacing + enabled = !loading; } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml index 3742e5cb..a5422280 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml @@ -1,54 +1,37 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Templates as T import QtQuick.Layouts - import "." as Proton FocusScope { id: root - property ColorScheme colorScheme - property alias background: control.background - property alias bottomInset: control.bottomInset - //property alias flickable: control.flickable - property alias focusReason: control.focusReason - property alias hoverEnabled: control.hoverEnabled - property alias hovered: control.hovered - property alias implicitBackgroundHeight: control.implicitBackgroundHeight - property alias implicitBackgroundWidth: control.implicitBackgroundWidth - property alias leftInset: control.leftInset - property alias palette: control.palette - property alias placeholderText: control.placeholderText - property alias placeholderTextColor: control.placeholderTextColor - property alias rightInset: control.rightInset - property alias topInset: control.topInset property alias activeFocusOnPress: control.activeFocusOnPress + property string assistiveText + property alias background: control.background property alias baseUrl: control.baseUrl + property alias bottomInset: control.bottomInset property alias bottomPadding: control.bottomPadding property alias canPaste: control.canPaste property alias canRedo: control.canRedo property alias canUndo: control.canUndo property alias color: control.color + property ColorScheme colorScheme property alias contentHeight: control.contentHeight property alias contentWidth: control.contentWidth property alias cursorDelegate: control.cursorDelegate @@ -56,21 +39,36 @@ FocusScope { property alias cursorRectangle: control.cursorRectangle property alias cursorVisible: control.cursorVisible property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment + property bool error: false + property string errorString + //property alias flickable: control.flickable + property alias focusReason: control.focusReason property alias font: control.font + property alias hint: hint.text property alias horizontalAlignment: control.horizontalAlignment + property alias hoverEnabled: control.hoverEnabled + property alias hovered: control.hovered property alias hoveredLink: control.hoveredLink + property alias implicitBackgroundHeight: control.implicitBackgroundHeight + property alias implicitBackgroundWidth: control.implicitBackgroundWidth property alias inputMethodComposing: control.inputMethodComposing property alias inputMethodHints: control.inputMethodHints + property alias label: label.text + property alias leftInset: control.leftInset property alias leftPadding: control.leftPadding property alias length: control.length property alias lineCount: control.lineCount property alias mouseSelectionMode: control.mouseSelectionMode property alias overwriteMode: control.overwriteMode property alias padding: control.padding + property alias palette: control.palette property alias persistentSelection: control.persistentSelection + property alias placeholderText: control.placeholderText + property alias placeholderTextColor: control.placeholderTextColor property alias preeditText: control.preeditText property alias readOnly: control.readOnly property alias renderType: control.renderType + property alias rightInset: control.rightInset property alias rightPadding: control.rightPadding property alias selectByKeyboard: control.selectByKeyboard property alias selectByMouse: control.selectByMouse @@ -84,61 +82,119 @@ FocusScope { property alias textDocument: control.textDocument property alias textFormat: control.textFormat property alias textMargin: control.textMargin + property alias topInset: control.topInset property alias topPadding: control.topPadding + property bool validateOnEditingFinished: true // We are using our own type of validators. It should be a function // returning an error string in case of error and undefined if no error property var validator property alias verticalAlignment: control.verticalAlignment property alias wrapMode: control.wrapMode - implicitWidth: children[0].implicitWidth - implicitHeight: children[0].implicitHeight + signal editingFinished - property alias label: label.text - property alias hint: hint.text - property string assistiveText - property string errorString - - property bool error: false - - signal editingFinished() - - function append(text) { return control.append(text) } - function clear() { return control.clear() } - function copy() { return control.copy() } - function cut() { return control.cut() } - function deselect() { return control.deselect() } - function getFormattedText(start, end) { return control.getFormattedText(start, end) } - function getText(start, end) { return control.getText(start, end) } - function insert(position, text) { return control.insert(position, text) } - function isRightToLeft(start, end) { return control.isRightToLeft(start, end) } - function linkAt(x, y) { return control.linkAt(x, y) } - function moveCursorSelection(position, mode) { return control.moveCursorSelection(position, mode) } - function paste() { return control.paste() } - function positionAt(x, y) { return control.positionAt(x, y) } - function positionToRectangle(position) { return control.positionToRectangle(position) } - function redo() { return control.redo() } - function remove(start, end) { return control.remove(start, end) } - function select(start, end) { return control.select(start, end) } - function selectAll() { return control.selectAll() } - function selectWord() { return control.selectWord() } - function undo() { return control.undo() } + function append(text) { + return control.append(text); + } + function clear() { + return control.clear(); + } + function copy() { + return control.copy(); + } + function cut() { + return control.cut(); + } + function deselect() { + return control.deselect(); + } + function getFormattedText(start, end) { + return control.getFormattedText(start, end); + } + function getText(start, end) { + return control.getText(start, end); + } // Calculates the height of the component to make exactly lineNum visible in edit area function heightForLinesVisible(lineNum) { - var totalHeight = 0 - totalHeight += headerLayout.height - totalHeight += footerLayout.height - totalHeight += control.topPadding + control.bottomPadding - totalHeight += lineNum * fontMetrics.height - return totalHeight + let totalHeight = 0; + totalHeight += headerLayout.height; + totalHeight += footerLayout.height; + totalHeight += control.topPadding + control.bottomPadding; + totalHeight += lineNum * fontMetrics.height; + return totalHeight; + } + function insert(position, text) { + return control.insert(position, text); + } + function isRightToLeft(start, end) { + return control.isRightToLeft(start, end); + } + function linkAt(x, y) { + return control.linkAt(x, y); + } + function moveCursorSelection(position, mode) { + return control.moveCursorSelection(position, mode); + } + function paste() { + return control.paste(); + } + function positionAt(x, y) { + return control.positionAt(x, y); + } + function positionToRectangle(position) { + return control.positionToRectangle(position); + } + function redo() { + return control.redo(); + } + function remove(start, end) { + return control.remove(start, end); + } + function select(start, end) { + return control.select(start, end); + } + function selectAll() { + return control.selectAll(); + } + function selectWord() { + return control.selectWord(); + } + function undo() { + return control.undo(); + } + function validate() { + if (validator === undefined) { + return; + } + const error = validator(text); + if (error) { + root.error = true; + root.errorString = error; + } else { + root.error = false; + root.errorString = ""; + } + } + + implicitHeight: children[0].implicitHeight + implicitWidth: children[0].implicitWidth + + onEditingFinished: { + if (!validateOnEditingFinished) { + return; + } + validate(); + } + onTextChanged: { + root.error = false; + root.errorString = ""; } FontMetrics { id: fontMetrics font: control.font } - ColumnLayout { anchors.fill: parent spacing: 0 @@ -149,154 +205,123 @@ FocusScope { spacing: 0 Proton.Label { - colorScheme: root.colorScheme id: label - Layout.fillWidth: true - color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - + colorScheme: root.colorScheme type: Proton.Label.LabelType.Body_semibold } - Proton.Label { - colorScheme: root.colorScheme id: hint - Layout.fillWidth: true - color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled + colorScheme: root.colorScheme horizontalAlignment: Text.AlignRight type: Proton.Label.LabelType.Caption } } - ScrollView { id: controlView - Layout.fillHeight: true Layout.fillWidth: true - clip: true T.TextArea { id: control - - implicitWidth: Math.max( - contentWidth + leftPadding + rightPadding, - implicitBackgroundWidth + leftInset + rightInset, - placeholder.implicitWidth + leftPadding + rightPadding - ) - implicitHeight: Math.max( - contentHeight + topPadding + bottomPadding, - implicitBackgroundHeight + topInset + bottomInset, - placeholder.implicitHeight + topPadding + bottomPadding - ) - - topPadding: 8 + KeyNavigation.backtab: root.KeyNavigation.backtab + KeyNavigation.down: root.KeyNavigation.down + KeyNavigation.left: root.KeyNavigation.left + KeyNavigation.priority: root.KeyNavigation.priority + KeyNavigation.right: root.KeyNavigation.right + KeyNavigation.tab: root.KeyNavigation.tab + KeyNavigation.up: root.KeyNavigation.up bottomPadding: 8 - leftPadding: 12 - rightPadding: 12 - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - font.letterSpacing: ProtonStyle.body_letter_spacing - color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled - selectionColor: control.palette.highlight - selectedTextColor: control.palette.highlightedText - - onEditingFinished: root.editingFinished() - - wrapMode: TextInput.Wrap // enforcing default focus here within component focus: root.focus - - KeyNavigation.priority: root.KeyNavigation.priority - KeyNavigation.backtab: root.KeyNavigation.backtab - KeyNavigation.tab: root.KeyNavigation.tab - KeyNavigation.up: root.KeyNavigation.up - KeyNavigation.down: root.KeyNavigation.down - KeyNavigation.left: root.KeyNavigation.left - KeyNavigation.right: root.KeyNavigation.right - + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + implicitHeight: Math.max(contentHeight + topPadding + bottomPadding, implicitBackgroundHeight + topInset + bottomInset, placeholder.implicitHeight + topPadding + bottomPadding) + implicitWidth: Math.max(contentWidth + leftPadding + rightPadding, implicitBackgroundWidth + leftInset + rightInset, placeholder.implicitWidth + leftPadding + rightPadding) + leftPadding: 12 + placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + rightPadding: 12 selectByMouse: true - - cursorDelegate: Rectangle { - id: cursor - width: 1 - color: root.colorScheme.interaction_norm - visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd - - Connections { - target: control - function onCursorPositionChanged() { - // keep a moving cursor visible - cursor.opacity = 1 - timer.restart() - } - } - - Timer { - id: timer - running: control.activeFocus && !control.readOnly - repeat: true - interval: Qt.styleHints.cursorFlashTime / 2 - onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 - // force the cursor visible when gaining focus - onRunningChanged: cursor.opacity = 1 - } - } - - PlaceholderText { - id: placeholder - x: control.leftPadding - y: control.topPadding - width: control.width - (control.leftPadding + control.rightPadding) - height: control.height - (control.topPadding + control.bottomPadding) - - text: control.placeholderText - font: control.font - color: control.placeholderTextColor - verticalAlignment: control.verticalAlignment - visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) - elide: Text.ElideRight - renderType: control.renderType - } + selectedTextColor: control.palette.highlightedText + selectionColor: control.palette.highlight + topPadding: 8 + wrapMode: TextInput.Wrap background: Rectangle { anchors.fill: parent - - radius: ProtonStyle.input_radius - visible: true - color: root.colorScheme.background_norm border.color: { if (!control.enabled) { - return root.colorScheme.field_disabled + return root.colorScheme.field_disabled; } - if (control.activeFocus) { - return root.colorScheme.interaction_norm + return root.colorScheme.interaction_norm; } - if (root.error) { - return root.colorScheme.signal_danger + return root.colorScheme.signal_danger; } - if (control.hovered) { - return root.colorScheme.field_hover + return root.colorScheme.field_hover; } - - return root.colorScheme.field_norm + return root.colorScheme.field_norm; } border.width: 1 + color: root.colorScheme.background_norm + radius: ProtonStyle.input_radius + visible: true + } + cursorDelegate: Rectangle { + id: cursor + color: root.colorScheme.interaction_norm + visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd + width: 1 + + Connections { + function onCursorPositionChanged() { + // keep a moving cursor visible + cursor.opacity = 1; + timer.restart(); + } + + target: control + } + Timer { + id: timer + interval: Qt.styleHints.cursorFlashTime / 2 + repeat: true + running: control.activeFocus && !control.readOnly + + // force the cursor visible when gaining focus + onRunningChanged: cursor.opacity = 1 + onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 + } + } + + onEditingFinished: root.editingFinished() + + PlaceholderText { + id: placeholder + color: control.placeholderTextColor + elide: Text.ElideRight + font: control.font + height: control.height - (control.topPadding + control.bottomPadding) + renderType: control.renderType + text: control.placeholderText + verticalAlignment: control.verticalAlignment + visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) + width: control.width - (control.leftPadding + control.rightPadding) + x: control.leftPadding + y: control.topPadding } } } - RowLayout { id: footerLayout Layout.fillWidth: true @@ -304,67 +329,29 @@ FocusScope { ColorImage { id: errorIcon - Layout.rightMargin: 4 - - visible: root.error && (assistiveText.text.length > 0) - source: "/qml/icons/ic-exclamation-circle-filled.svg" color: root.colorScheme.signal_danger height: assistiveText.height + source: "/qml/icons/ic-exclamation-circle-filled.svg" sourceSize.height: assistiveText.height + visible: root.error && (assistiveText.text.length > 0) } - Proton.Label { - colorScheme: root.colorScheme id: assistiveText - Layout.fillWidth: true - - text: root.error ? root.errorString : root.assistiveText - color: { if (!root.enabled) { - return root.colorScheme.text_disabled + return root.colorScheme.text_disabled; } - if (root.error) { - return root.colorScheme.signal_danger + return root.colorScheme.signal_danger; } - - return root.colorScheme.text_weak + return root.colorScheme.text_weak; } - + colorScheme: root.colorScheme + text: root.error ? root.errorString : root.assistiveText type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption } } } - - property bool validateOnEditingFinished: true - onEditingFinished: { - if (!validateOnEditingFinished) { - return - } - validate() - } - - function validate() { - if (validator === undefined) { - return - } - - var error = validator(text) - - if (error) { - root.error = true - root.errorString = error - } else { - root.error = false - root.errorString = "" - } - } - - onTextChanged: { - root.error = false - root.errorString = "" - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextField.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextField.qml index ff20326b..1e630ac5 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextField.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextField.qml @@ -1,54 +1,38 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Templates as T import QtQuick.Layouts - import "." as Proton FocusScope { id: root - property ColorScheme colorScheme - property alias background: control.background - property alias bottomInset: control.bottomInset - property alias focusReason: control.focusReason - property alias hoverEnabled: control.hoverEnabled - property alias hovered: control.hovered - property alias implicitBackgroundHeight: control.implicitBackgroundHeight - property alias implicitBackgroundWidth: control.implicitBackgroundWidth - property alias leftInset: control.leftInset - property alias palette: control.palette - property alias placeholderText: control.placeholderText - property alias placeholderTextColor: control.placeholderTextColor - property alias rightInset: control.rightInset - property alias topInset: control.topInset property alias acceptableInput: control.acceptableInput property alias activeFocusOnPress: control.activeFocusOnPress + property string assistiveText property alias autoScroll: control.autoScroll + property alias background: control.background + property alias bottomInset: control.bottomInset property alias bottomPadding: control.bottomPadding property alias canPaste: control.canPaste property alias canRedo: control.canRedo property alias canUndo: control.canUndo property alias color: control.color + property ColorScheme colorScheme //property alias contentHeight: control.contentHeight //property alias contentWidth: control.contentWidth property alias cursorDelegate: control.cursorDelegate @@ -56,24 +40,39 @@ FocusScope { property alias cursorRectangle: control.cursorRectangle property alias cursorVisible: control.cursorVisible property alias displayText: control.displayText + property int echoMode: TextInput.Normal property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment + property bool error: false + property string errorString + property alias focusReason: control.focusReason property alias font: control.font + property alias hint: hint.text property alias horizontalAlignment: control.horizontalAlignment + property alias hoverEnabled: control.hoverEnabled + property alias hovered: control.hovered + property alias implicitBackgroundHeight: control.implicitBackgroundHeight + property alias implicitBackgroundWidth: control.implicitBackgroundWidth property alias inputMask: control.inputMask property alias inputMethodComposing: control.inputMethodComposing property alias inputMethodHints: control.inputMethodHints + property alias label: label.text + property alias leftInset: control.leftInset property alias leftPadding: control.leftPadding property alias length: control.length property alias maximumLength: control.maximumLength property alias mouseSelectionMode: control.mouseSelectionMode property alias overwriteMode: control.overwriteMode property alias padding: control.padding + property alias palette: control.palette property alias passwordCharacter: control.passwordCharacter property alias passwordMaskDelay: control.passwordMaskDelay property alias persistentSelection: control.persistentSelection + property alias placeholderText: control.placeholderText + property alias placeholderTextColor: control.placeholderTextColor property alias preeditText: control.preeditText property alias readOnly: control.readOnly property alias renderType: control.renderType + property alias rightInset: control.rightInset property alias rightPadding: control.rightPadding property alias selectByMouse: control.selectByMouse property alias selectedText: control.selectedText @@ -82,47 +81,102 @@ FocusScope { property alias selectionEnd: control.selectionEnd property alias selectionStart: control.selectionStart property alias text: control.text + property alias topInset: control.topInset + property bool validateOnEditingFinished: true // We are using our own type of validators. It should be a function // returning an error string in case of error and undefined if no error property var validator property alias verticalAlignment: control.verticalAlignment property alias wrapMode: control.wrapMode - implicitWidth: children[0].implicitWidth + signal accepted + signal editingFinished + signal textEdited + + function clear() { + control.clear(); + } + function copy() { + control.copy(); + } + function cut() { + control.cut(); + } + function deselect() { + control.deselect(); + } + function ensureVisible(position) { + control.ensureVisible(position); + } + function forceActiveFocus() { + control.forceActiveFocus(); + } + function getText(start, end) { + control.getText(start, end); + } + function insert(position, text) { + control.insert(position, text); + } + function isRightToLeft(start, end) { + control.isRightToLeft(start, end); + } + function moveCursorSelection(position, mode) { + control.moveCursorSelection(position, mode); + } + function paste() { + control.paste(); + } + function positionAt(x, y, position) { + control.positionAt(x, y, position); + } + function positionToRectangle(pos) { + control.positionToRectangle(pos); + } + function redo() { + control.redo(); + } + function remove(start, end) { + control.remove(start, end); + } + function select(start, end) { + control.select(start, end); + } + function selectAll() { + control.selectAll(); + } + function selectWord() { + control.selectWord(); + } + function undo() { + control.undo(); + } + function validate() { + if (validator === undefined) { + return; + } + const error = validator(text); + if (error) { + root.error = true; + root.errorString = error; + } else { + root.error = false; + root.errorString = ""; + } + } + implicitHeight: children[0].implicitHeight + implicitWidth: children[0].implicitWidth - property alias label: label.text - property alias hint: hint.text - property string assistiveText - property string errorString - - property int echoMode: TextInput.Normal - - property bool error: false - - signal accepted() - signal editingFinished() - signal textEdited() - - function clear() { control.clear() } - function copy() { control.copy() } - function cut() { control.cut() } - function deselect() { control.deselect() } - function ensureVisible(position) { control.ensureVisible(position) } - function getText(start, end) { control.getText(start, end) } - function insert(position, text) { control.insert(position, text) } - function isRightToLeft(start, end) { control.isRightToLeft(start, end) } - function moveCursorSelection(position, mode) { control.moveCursorSelection(position, mode) } - function paste() { control.paste() } - function positionAt(x, y, position) { control.positionAt(x, y, position) } - function positionToRectangle(pos) { control.positionToRectangle(pos) } - function redo() { control.redo() } - function remove(start, end) { control.remove(start, end) } - function select(start, end) { control.select(start, end) } - function selectAll() { control.selectAll() } - function selectWord() { control.selectWord() } - function undo() { control.undo() } - function forceActiveFocus() { control.forceActiveFocus() } + onEditingFinished: { + if (!validateOnEditingFinished) { + return; + } + validate(); + } + onTextChanged: { + root.error = false; + root.errorString = ""; + } ColumnLayout { anchors.fill: parent @@ -133,19 +187,18 @@ FocusScope { spacing: 0 Proton.Label { - colorScheme: root.colorScheme id: label Layout.fillHeight: true Layout.fillWidth: true + colorScheme: root.colorScheme type: Proton.Label.LabelType.Body_semibold } - Proton.Label { - colorScheme: root.colorScheme id: hint Layout.fillHeight: true Layout.fillWidth: true color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled + colorScheme: root.colorScheme horizontalAlignment: Text.AlignRight type: Proton.Label.LabelType.Caption } @@ -156,36 +209,29 @@ FocusScope { // will be adjusted to background's width making text field and eye button overlap Rectangle { id: background - Layout.fillHeight: true Layout.fillWidth: true - - radius: ProtonStyle.input_radius - visible: true - color: root.colorScheme.background_norm border.color: { if (!control.enabled) { - return root.colorScheme.field_disabled + return root.colorScheme.field_disabled; } - if (control.activeFocus) { - return root.colorScheme.interaction_norm + return root.colorScheme.interaction_norm; } - if (root.error) { - return root.colorScheme.signal_danger + return root.colorScheme.signal_danger; } - if (control.hovered) { - return root.colorScheme.field_hover + return root.colorScheme.field_hover; } - - return root.colorScheme.field_norm + return root.colorScheme.field_norm; } border.width: 1 - - implicitWidth: children[0].implicitWidth + color: root.colorScheme.background_norm implicitHeight: children[0].implicitHeight + implicitWidth: children[0].implicitWidth + radius: ProtonStyle.input_radius + visible: true RowLayout { anchors.fill: parent @@ -193,190 +239,135 @@ FocusScope { T.TextField { id: control - + KeyNavigation.backtab: root.KeyNavigation.backtab + KeyNavigation.down: root.KeyNavigation.down + KeyNavigation.left: root.KeyNavigation.left + KeyNavigation.priority: root.KeyNavigation.priority + KeyNavigation.right: root.KeyNavigation.right + KeyNavigation.tab: root.KeyNavigation.tab + KeyNavigation.up: root.KeyNavigation.up Layout.fillHeight: true Layout.fillWidth: true - - implicitWidth: implicitBackgroundWidth + leftInset + rightInset - || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding, - placeholder.implicitHeight + topPadding + bottomPadding) - - topPadding: 8 bottomPadding: 8 - leftPadding: 12 - rightPadding: 12 - - font.family: ProtonStyle.font_family - font.weight: ProtonStyle.fontWeight_400 - font.pixelSize: ProtonStyle.body_font_size - font.letterSpacing: ProtonStyle.body_letter_spacing - color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled - selectionColor: control.palette.highlight - selectedTextColor: control.palette.highlightedText - - verticalAlignment: TextInput.AlignVCenter - echoMode: eyeButton.checked ? TextInput.Normal : root.echoMode // enforcing default focus here within component focus: true - - KeyNavigation.priority: root.KeyNavigation.priority - KeyNavigation.backtab: root.KeyNavigation.backtab - KeyNavigation.tab: root.KeyNavigation.tab - KeyNavigation.up: root.KeyNavigation.up - KeyNavigation.down: root.KeyNavigation.down - KeyNavigation.left: root.KeyNavigation.left - KeyNavigation.right: root.KeyNavigation.right - + font.family: ProtonStyle.font_family + font.letterSpacing: ProtonStyle.body_letter_spacing + font.pixelSize: ProtonStyle.body_font_size + font.weight: ProtonStyle.fontWeight_400 + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding, placeholder.implicitHeight + topPadding + bottomPadding) + implicitWidth: implicitBackgroundWidth + leftInset + rightInset || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding + leftPadding: 12 + placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + rightPadding: 12 selectByMouse: true + selectedTextColor: control.palette.highlightedText + selectionColor: control.palette.highlight + topPadding: 8 + verticalAlignment: TextInput.AlignVCenter + background: Item { + implicitHeight: 36 + implicitWidth: 80 + visible: false + } cursorDelegate: Rectangle { id: cursor - width: 1 color: root.colorScheme.interaction_norm visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd + width: 1 Connections { - target: control function onCursorPositionChanged() { // keep a moving cursor visible - cursor.opacity = 1 - timer.restart() + cursor.opacity = 1; + timer.restart(); } - } + target: control + } Timer { id: timer - running: control.activeFocus && !control.readOnly - repeat: true interval: Qt.styleHints.cursorFlashTime / 2 - onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 + repeat: true + running: control.activeFocus && !control.readOnly + // force the cursor visible when gaining focus onRunningChanged: cursor.opacity = 1 + onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 } } + onAccepted: { + root.accepted(); + } + onEditingFinished: { + root.editingFinished(); + } + onTextEdited: { + root.textEdited(); + } + PlaceholderText { id: placeholder - x: control.leftPadding - y: control.topPadding - width: control.width - (control.leftPadding + control.rightPadding) - height: control.height - (control.topPadding + control.bottomPadding) - - text: control.placeholderText - font: control.font color: control.placeholderTextColor + elide: Text.ElideRight + font: control.font + height: control.height - (control.topPadding + control.bottomPadding) + renderType: control.renderType + text: control.placeholderText verticalAlignment: control.verticalAlignment visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) - elide: Text.ElideRight - renderType: control.renderType - } - - background: Item { - implicitWidth: 80 - implicitHeight: 36 - visible: false - } - - onAccepted: { - root.accepted() - } - onEditingFinished: { - root.editingFinished() - } - onTextEdited: { - root.textEdited() + width: control.width - (control.leftPadding + control.rightPadding) + x: control.leftPadding + y: control.topPadding } } - Proton.Button { - colorScheme: root.colorScheme id: eyeButton - Layout.fillHeight: true - - visible: root.echoMode === TextInput.Password - icon.color: control.color checkable: true + colorScheme: root.colorScheme + icon.color: control.color icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg" + visible: root.echoMode === TextInput.Password } } } - RowLayout { Layout.fillWidth: true spacing: 0 ColorImage { id: errorIcon - Layout.rightMargin: 4 - - visible: root.error && (assistiveText.text.length > 0) - source: "../icons/ic-exclamation-circle-filled.svg" color: root.colorScheme.signal_danger height: assistiveText.lineHeight + source: "../icons/ic-exclamation-circle-filled.svg" sourceSize.height: assistiveText.lineHeight + visible: root.error && (assistiveText.text.length > 0) } - Proton.Label { - colorScheme: root.colorScheme id: assistiveText - Layout.fillHeight: true Layout.fillWidth: true - wrapMode: Text.WordWrap - - text: root.error ? root.errorString : root.assistiveText - color: { if (!root.enabled) { - return root.colorScheme.text_disabled + return root.colorScheme.text_disabled; } - if (root.error) { - return root.colorScheme.signal_danger + return root.colorScheme.signal_danger; } - - return root.colorScheme.text_weak + return root.colorScheme.text_weak; } - + colorScheme: root.colorScheme + text: root.error ? root.errorString : root.assistiveText type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption + wrapMode: Text.WordWrap } } } - - property bool validateOnEditingFinished: true - onEditingFinished: { - if (!validateOnEditingFinished) { - return - } - validate() - } - - function validate() { - if (validator === undefined) { - return - } - - var error = validator(text) - - if (error) { - root.error = true - root.errorString = error - } else { - root.error = false - root.errorString = "" - } - } - - onTextChanged: { - root.error = false - root.errorString = "" - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Toggle.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Toggle.qml index f2c52541..a9d3207b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Toggle.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Toggle.qml @@ -1,20 +1,15 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls @@ -22,92 +17,106 @@ import QtQuick.Controls.impl Item { id: root - property var colorScheme + + property bool _disabled: !enabled property bool checked + property var colorScheme property bool hovered property bool loading signal clicked - property bool _disabled: !enabled - implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth Rectangle { id: indicator - implicitWidth: 40 - implicitHeight: 24 - - radius: width/2 color: { - if (root.loading) return "transparent" - if (root._disabled) return root.colorScheme.background_strong - return root.colorScheme.background_norm - } - border { - width: 1 - color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm + if (root.loading) + return "transparent"; + if (root._disabled) + return root.colorScheme.background_strong; + return root.colorScheme.background_norm; } + implicitHeight: 24 + implicitWidth: 40 + radius: width / 2 + border { + color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm + width: 1 + } Rectangle { - anchors.verticalCenter: indicator.verticalCenter anchors.left: indicator.left anchors.leftMargin: root.checked ? 16 : 0 - width: 24 - height: 24 - radius: width/2 + anchors.verticalCenter: indicator.verticalCenter color: { - if (root.loading) return "transparent" - if (root._disabled) return root.colorScheme.field_disabled - + if (root.loading) + return "transparent"; + if (root._disabled) + return root.colorScheme.field_disabled; if (root.checked) { - if (root.hovered) return root.colorScheme.interaction_norm_hover - return root.colorScheme.interaction_norm + if (root.hovered) + return root.colorScheme.interaction_norm_hover; + return root.colorScheme.interaction_norm; } else { - if (root.hovered) return root.colorScheme.field_hover - return root.colorScheme.field_norm + if (root.hovered) + return root.colorScheme.field_hover; + return root.colorScheme.field_norm; } } + height: 24 + radius: width / 2 + width: 24 ColorImage { anchors.centerIn: parent - source: "/qml/icons/ic-check.svg" color: root.colorScheme.background_norm height: root.colorScheme.body_font_size + source: "/qml/icons/ic-check.svg" sourceSize.height: root.colorScheme.body_font_size visible: root.checked } } - ColorImage { id: loader anchors.centerIn: parent - source: "/qml/icons/Loader_16.svg" color: root.colorScheme.text_norm height: root.colorScheme.body_font_size + source: "/qml/icons/Loader_16.svg" sourceSize.height: root.colorScheme.body_font_size visible: root.loading RotationAnimation { - target: loader - loops: Animation.Infinite + direction: RotationAnimation.Clockwise duration: 1000 from: 0 - to: 360 - direction: RotationAnimation.Clockwise + loops: Animation.Infinite running: root.loading + target: loader + to: 360 } } - MouseArea { anchors.fill: indicator hoverEnabled: true - onEntered: {root.hovered = true } - onExited: {root.hovered = false } - onClicked: { if (root.enabled) root.clicked();} - onPressed: {root.hovered = true } - onReleased: { root.hovered = containsMouse } + + onClicked: { + if (root.enabled) + root.clicked(); + } + onEntered: { + root.hovered = true; + } + onExited: { + root.hovered = false; + } + onPressed: { + root.hovered = true; + } + onReleased: { + root.hovered = containsMouse; + } } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml index 8a3aafaa..ea0e6fc0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml @@ -1,51 +1,44 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton Item { id: root - property var colorScheme - - property string text: "Text" - property string actionText: "Action" - property string actionIcon: "" - property string description: "Lorem ipsum dolor sit amet" - property alias descriptionWrap: descriptionLabel.wrapMode - property var type: SettingsItem.ActionType.Toggle - - property bool checked: true - property bool loading: false - property bool showSeparator: true + enum ActionType { + Toggle = 1, + Button, + PrimaryButton + } property var _bottomMargin: 20 property var _lineWidth: 1 property var _toggleTopMargin: 6 + property string actionIcon: "" + property string actionText: "Action" + property bool checked: true + property var colorScheme + property string description: "Lorem ipsum dolor sit amet" + property alias descriptionWrap: descriptionLabel.wrapMode + property bool loading: false + property bool showSeparator: true + property string text: "Text" + property var type: SettingsItem.ActionType.Toggle signal clicked - enum ActionType { - Toggle = 1, Button = 2, PrimaryButton = 3 - } - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin @@ -54,10 +47,9 @@ Item { spacing: 16 ColumnLayout { + Layout.bottomMargin: root._bottomMargin Layout.fillHeight: true Layout.fillWidth: true - Layout.bottomMargin: root._bottomMargin - spacing: 4 Label { @@ -66,51 +58,51 @@ Item { text: root.text type: Label.Body_semibold } - Label { id: descriptionLabel Layout.fillHeight: true Layout.fillWidth: true - Layout.preferredWidth: parent.width - - wrapMode: Text.WordWrap + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: root.description - color: root.colorScheme.text_weak + wrapMode: Text.WordWrap } } - Toggle { + id: toggle Layout.alignment: Qt.AlignTop Layout.topMargin: root._toggleTopMargin - id: toggle + checked: root.checked colorScheme: root.colorScheme + loading: root.loading visible: root.type === SettingsItem.ActionType.Toggle - checked: root.checked - loading: root.loading - onClicked: { if (!root.loading) root.clicked() } + onClicked: { + if (!root.loading) + root.clicked(); + } } - Button { - Layout.alignment: Qt.AlignTop - id: button + Layout.alignment: Qt.AlignTop colorScheme: root.colorScheme - visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton - text: root.actionText + (root.actionIcon != "" ? " " : "") - loading: root.loading icon.source: root.actionIcon - onClicked: { if (!root.loading) root.clicked() } + loading: root.loading secondary: root.type !== SettingsItem.PrimaryButton + text: root.actionText + (root.actionIcon !== "" ? " " : "") + visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton + + onClicked: { + if (!root.loading) + root.clicked(); + } } } - Rectangle { + anchors.bottom: root.bottom anchors.left: root.left anchors.right: root.right - anchors.bottom: root.bottom color: colorScheme.border_weak height: root._lineWidth visible: root.showSeparator diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsView.qml index e202b49c..e4619052 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsView.qml @@ -1,61 +1,53 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton Item { id: root - property var colorScheme - default property alias items: content.children - - signal back() - + property int _bottomMargin: 32 property int _leftMargin: 64 property int _rightMargin: 64 - property int _topMargin: 32 - property int _bottomMargin: 32 property int _spacing: 20 + property int _topMargin: 32 + property var colorScheme // fillHeight indicates whether the SettingsView should fill all available explicit height set property bool fillHeight: false + default property alias items: content.children + + signal back ScrollView { id: scrollView + anchors.fill: parent clip: true - anchors.fill: parent Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds // Disable the springy effect when scroll reaches top/bottom. Item { - // can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView) - width: scrollView.availableWidth height: scrollView.availableHeight - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin // do not set implicitWidth because implicit width of ColumnLayout will be equal to maximum implicit width of // internal items. And if one of internal items would be a Text or Label - implicit width of those is always // equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed implicitWidth: width + // can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView) + width: scrollView.availableWidth ColumnLayout { anchors.fill: parent @@ -63,16 +55,13 @@ Item { ColumnLayout { id: content - spacing: root._spacing - - Layout.fillWidth: true - - Layout.topMargin: root._topMargin Layout.bottomMargin: root._bottomMargin + Layout.fillWidth: true Layout.leftMargin: root._leftMargin Layout.rightMargin: root._rightMargin + Layout.topMargin: root._topMargin + spacing: root._spacing } - Item { id: filler Layout.fillHeight: true @@ -81,19 +70,20 @@ Item { } } } - Button { id: backButton - anchors { - top: parent.top - left: parent.left - topMargin: root._topMargin - leftMargin: (root._leftMargin-backButton.width) / 2 - } colorScheme: root.colorScheme - onClicked: root.back() + horizontalPadding: 8 icon.source: "/qml/icons/ic-arrow-left.svg" secondary: true - horizontalPadding: 8 + + onClicked: root.back() + + anchors { + left: parent.left + leftMargin: (root._leftMargin - backButton.width) / 2 + top: parent.top + topMargin: root._topMargin + } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml index 972a7f70..4cdfe43f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml @@ -1,110 +1,139 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton Item { - id:root + id: root + property string address property ColorScheme colorScheme property var user - property string address - signal dismissed() - signal finished() + signal dismissed + signal finished + + function reset() { + guidePages.currentIndex = 0; + clientList.currentIndex = -1; + actionList.currentIndex = -1; + } + function setupAction(actionID, clientID) { + if (user) { + user.setupGuideSeen = true; + } + switch (actionID) { + case -1: + root.dismissed(); + break; // dismiss + case 0 // automatic + : + if (user) { + switch (clientID) { + case 0: + root.user.configureAppleMail(root.address); + Backend.notifyAutoconfigClicked("AppleMail"); + break; + } + } + root.finished(); + break; + case 1 // manual + : + let clientObj = clients.get(clientID); + if (clientObj !== undefined && clientObj.link !== "") { + Qt.openUrlExternally(clientObj.link); + Backend.notifyKBArticleClicked(clientObj.link); + } else { + console.log("unexpected client index", actionID, clientID); + } + root.finished(); + break; + default: + console.log("unexpected client setup action", actionID, clientID); + } + } implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth - ListModel { id: clients - property string name : "Apple Mail" - property string iconSource : "/qml/icons/ic-apple-mail.svg" + property bool haveAutoSetup: true + property string iconSource: "/qml/icons/ic-apple-mail.svg" property string link: "https://proton.me/support/protonmail-bridge-clients-apple-mail" + property string name: "Apple Mail" - Component.onCompleted : { - if (Backend.goos == "darwin") { + Component.onCompleted: { + if (Backend.goos === "darwin") { append({ - "name" : "Apple Mail", - "iconSource" : "/qml/icons/ic-apple-mail.svg", - "haveAutoSetup" : true, - "link" : "https://proton.me/support/protonmail-bridge-clients-apple-mail" - }) + "name": "Apple Mail", + "iconSource": "/qml/icons/ic-apple-mail.svg", + "haveAutoSetup": true, + "link": "https://proton.me/support/protonmail-bridge-clients-apple-mail" + }); append({ - "name" : "Microsoft Outlook", - "iconSource" : "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup" : false, - "link" : "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" - }) + "name": "Microsoft Outlook", + "iconSource": "/qml/icons/ic-microsoft-outlook.svg", + "haveAutoSetup": false, + "link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" + }); } - if (Backend.goos == "windows") { + if (Backend.goos === "windows") { append({ - "name" : "Microsoft Outlook", - "iconSource" : "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup" : false, - "link" : "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019" - }) + "name": "Microsoft Outlook", + "iconSource": "/qml/icons/ic-microsoft-outlook.svg", + "haveAutoSetup": false, + "link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019" + }); } - append({ - "name" : "Mozilla Thunderbird", - "iconSource" : "/qml/icons/ic-mozilla-thunderbird.svg", - "haveAutoSetup" : false, - "link" : "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird" - }) - + "name": "Mozilla Thunderbird", + "iconSource": "/qml/icons/ic-mozilla-thunderbird.svg", + "haveAutoSetup": false, + "link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird" + }); append({ - "name" : "Other", - "iconSource" : "/qml/icons/ic-other-mail-clients.svg", - "haveAutoSetup" : false, - "link" : "https://proton.me/support/protonmail-bridge-configure-client" - }) - + "name": "Other", + "iconSource": "/qml/icons/ic-other-mail-clients.svg", + "haveAutoSetup": false, + "link": "https://proton.me/support/protonmail-bridge-configure-client" + }); } } - Rectangle { anchors.fill: root color: root.colorScheme.background_norm } - StackLayout { id: guidePages + anchors.bottomMargin: 70 anchors.fill: parent anchors.leftMargin: 80 anchors.rightMargin: 80 anchors.topMargin: 30 - anchors.bottomMargin: 70 - - ColumnLayout { // 0: Client selection + ColumnLayout { + // 0: Client selection id: clientView - Layout.fillHeight: true property int columnWidth: 268 + Layout.fillHeight: true spacing: 8 Label { @@ -112,16 +141,14 @@ Item { text: qsTr("Setting up email client") type: Label.LabelType.Heading } - Label { + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: address - color: root.colorScheme.text_weak type: Label.LabelType.Lead } - RowLayout { - Layout.topMargin: 32-clientView.spacing + Layout.topMargin: 32 - clientView.spacing spacing: 24 ColumnLayout { @@ -134,185 +161,133 @@ Item { text: qsTr("Choose an email client") type: Label.LabelType.Body_semibold } - ListView { id: clientList Layout.fillHeight: true + model: clients width: clientView.columnWidth - model: clients - - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - delegate: Item { - implicitWidth: clientRow.width implicitHeight: clientRow.height + implicitWidth: clientRow.width ColumnLayout { id: clientRow width: clientList.width RowLayout { - Layout.topMargin: 12 Layout.bottomMargin: 12 Layout.leftMargin: 16 Layout.rightMargin: 16 + Layout.topMargin: 12 ColorImage { - source: model.iconSource height: 36 + source: model.iconSource sourceSize.height: 36 } - Label { - colorScheme: root.colorScheme Layout.leftMargin: 12 + colorScheme: root.colorScheme text: model.name type: Label.LabelType.Body } } - Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 color: root.colorScheme.border_weak } } - MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor + onClicked: { - clientList.currentIndex = index + clientList.currentIndex = index; if (!model.haveAutoSetup) { - root.setupAction(1,index) + root.setupAction(1, index); } } } } + highlight: Rectangle { + color: root.colorScheme.interaction_default_active + radius: ProtonStyle.context_item_radius + } } } - ColumnLayout { id: actionColumn - visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup Layout.alignment: Qt.AlignTop + visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup Label { colorScheme: root.colorScheme text: qsTr("Choose configuration mode") type: Label.LabelType.Body_semibold } - ListView { id: actionList Layout.fillHeight: true + model: [qsTr("Configure automatically"), qsTr("Configure manually")] width: clientView.columnWidth - model: [ - qsTr("Configure automatically"), - qsTr("Configure manually"), - ] - - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - delegate: Item { - implicitWidth: children[0].width implicitHeight: children[0].height + implicitWidth: children[0].width ColumnLayout { width: actionList.width Label { - Layout.topMargin: 20 Layout.bottomMargin: 20 Layout.leftMargin: 16 Layout.rightMargin: 16 + Layout.topMargin: 20 colorScheme: root.colorScheme text: modelData type: Label.LabelType.Body } - Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 color: root.colorScheme.border_weak } } - MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor + onClicked: { - actionList.currentIndex = index - root.setupAction(index,clientList.currentIndex) + actionList.currentIndex = index; + root.setupAction(index, clientList.currentIndex); } } } + highlight: Rectangle { + color: root.colorScheme.interaction_default_active + radius: ProtonStyle.context_item_radius + } } } } - - Item { Layout.fillHeight: true } - + Item { + Layout.fillHeight: true + } Button { colorScheme: root.colorScheme - text: qsTr("Set up later") flat: true + text: qsTr("Set up later") onClicked: { - root.setupAction(-1,-1) + root.setupAction(-1, -1); if (user) { - user.setupGuideSeen = true + user.setupGuideSeen = true; } - root.dismissed() + root.dismissed(); } } } } - - function setupAction(actionID,clientID){ - if (user) { - user.setupGuideSeen = true - } - - switch (actionID) { - case -1: root.dismissed(); break; // dismiss - case 0: // automatic - if (user) { - switch (clientID) { - case 0: - root.user.configureAppleMail(root.address) - Backend.notifyAutoconfigClicked("AppleMail"); - break; - } - } - root.finished() - break; - case 1: // manual - var clientObj = clients.get(clientID) - if (clientObj != undefined && clientObj.link != "" ) { - Qt.openUrlExternally(clientObj.link) - Backend.notifyKBArticleClicked(clientObj.link); - } else { - console.log("unexpected client index", actionID, clientID) - } - root.finished(); - break; - default: - console.log("unexpected client setup action", actionID, clientID) - } - } - - function reset(){ - guidePages.currentIndex = 0 - clientList.currentIndex = -1 - actionList.currentIndex = -1 - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml index 221affab..35278df0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml @@ -1,478 +1,413 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton FocusScope { id: root + property ColorScheme colorScheme - - function reset() { - stackLayout.currentIndex = 0 - loginNormalLayout.reset() - login2FALayout.reset() - login2PasswordLayout.reset() - - } + property alias currentIndex: stackLayout.currentIndex + property alias username: usernameTextField.text function abort() { - root.reset() - Backend.loginAbort(usernameTextField.text) + root.reset(); + Backend.loginAbort(usernameTextField.text); + } + function reset() { + stackLayout.currentIndex = 0; + loginNormalLayout.reset(); + login2FALayout.reset(); + login2PasswordLayout.reset(); } implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth - - property alias username: usernameTextField.text state: "Page 1" - property alias currentIndex: stackLayout.currentIndex + states: [ + State { + name: "Page 1" + + PropertyChanges { + currentIndex: 0 + target: stackLayout + } + }, + State { + name: "Page 2" + + PropertyChanges { + currentIndex: 1 + target: stackLayout + } + }, + State { + name: "Page 3" + + PropertyChanges { + currentIndex: 2 + target: stackLayout + } + } + ] StackLayout { id: stackLayout + function loginFailed() { + signInButton.loading = false; + usernameTextField.enabled = true; + usernameTextField.error = true; + passwordTextField.enabled = true; + passwordTextField.error = true; + } + anchors.fill: parent - function loginFailed() { - signInButton.loading = false - - usernameTextField.enabled = true - usernameTextField.error = true - - passwordTextField.enabled = true - passwordTextField.error = true - } - Connections { - target: Backend - - function onLoginUsernamePasswordError(errorMsg) { - console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError") - console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError") - - stackLayout.loginFailed() - if (errorMsg!="") errorLabel.text = errorMsg - else errorLabel.text = qsTr("Incorrect login credentials") + function onLogin2FAError(_) { + console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError"); + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = true; + twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect"); + twoFactorPasswordTextField.focus = true; } - - function onLoginFreeUserError() { - console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError") - stackLayout.loginFailed() + function onLogin2FAErrorAbort(_) { + console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); } - - function onLoginConnectionError(errorMsg) { - if (stackLayout.currentIndex == 0 ) { - stackLayout.loginFailed() + function onLogin2FARequested(username) { + console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested"); + twoFactorUsernameLabel.text = username; + stackLayout.currentIndex = 1; + twoFactorPasswordTextField.focus = true; + } + function onLogin2PasswordError(_) { + console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError"); + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = true; + secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect"); + secondPasswordTextField.focus = true; + } + function onLogin2PasswordErrorAbort(_) { + console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); + } + function onLogin2PasswordRequested() { + console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested"); + stackLayout.currentIndex = 2; + secondPasswordTextField.focus = true; + } + function onLoginAlreadyLoggedIn(_) { + stackLayout.currentIndex = 0; + root.reset(); + } + function onLoginConnectionError(_) { + if (stackLayout.currentIndex === 0) { + stackLayout.loginFailed(); } } - - function onLogin2FARequested(username) { - console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested") - twoFactorUsernameLabel.text = username - stackLayout.currentIndex = 1 - twoFactorPasswordTextField.focus = true + function onLoginFinished(_) { + stackLayout.currentIndex = 0; + root.reset(); + } + function onLoginFreeUserError() { + console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError"); + stackLayout.loginFailed(); + } + function onLoginUsernamePasswordError(errorMsg) { + console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError"); + stackLayout.loginFailed(); + if (errorMsg !== "") + errorLabel.text = errorMsg; + else + errorLabel.text = qsTr("Incorrect login credentials"); } - function onLogin2FAError(errorMsg) { - console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAError") - - twoFAButton.loading = false - - twoFactorPasswordTextField.enabled = true - twoFactorPasswordTextField.error = true - twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect") - twoFactorPasswordTextField.focus = true - } - - function onLogin2FAErrorAbort(errorMsg) { - console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort") - root.reset() - errorLabel.text = qsTr("Incorrect login credentials. Please try again.") - } - - function onLogin2PasswordRequested() { - console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested") - stackLayout.currentIndex = 2 - secondPasswordTextField.focus = true - } - function onLogin2PasswordError(errorMsg) { - console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordError") - - secondPasswordButton.loading = false - - secondPasswordTextField.enabled = true - secondPasswordTextField.error = true - secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect") - secondPasswordTextField.focus = true - } - function onLogin2PasswordErrorAbort(errorMsg) { - console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort") - root.reset() - errorLabel.text = qsTr("Incorrect login credentials. Please try again.") - } - - function onLoginFinished(index) { - stackLayout.currentIndex = 0 - root.reset() - } - - function onLoginAlreadyLoggedIn(index) { - stackLayout.currentIndex = 0 - root.reset() - } + target: Backend } - ColumnLayout { id: loginNormalLayout - function reset() { - signInButton.loading = false - - errorLabel.text = "" - - usernameTextField.enabled = true - usernameTextField.error = false - usernameTextField.errorString = "" - usernameTextField.focus = true - - passwordTextField.enabled = true - passwordTextField.error = false - passwordTextField.errorString = "" - passwordTextField.text = "" + signInButton.loading = false; + errorLabel.text = ""; + usernameTextField.enabled = true; + usernameTextField.error = false; + usernameTextField.errorString = ""; + usernameTextField.focus = true; + passwordTextField.enabled = true; + passwordTextField.error = false; + passwordTextField.errorString = ""; + passwordTextField.text = ""; } spacing: 0 Label { - colorScheme: root.colorScheme - text: qsTr("Sign in") Layout.alignment: Qt.AlignHCenter Layout.topMargin: 16 + colorScheme: root.colorScheme + text: qsTr("Sign in") type: Label.LabelType.Title } - Label { - colorScheme: root.colorScheme id: subTitle - text: qsTr("Enter your Proton Account details.") Layout.alignment: Qt.AlignHCenter Layout.topMargin: 8 color: root.colorScheme.text_weak + colorScheme: root.colorScheme + text: qsTr("Enter your Proton Account details.") type: Label.LabelType.Body } - RowLayout { Layout.fillWidth: true Layout.topMargin: 36 - spacing: 0 visible: errorLabel.text.length > 0 ColorImage { color: root.colorScheme.signal_danger - source: "/qml/icons/ic-exclamation-circle-filled.svg" height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" sourceSize.height: errorLabel.lineHeight } - Label { - colorScheme: root.colorScheme id: errorLabel - wrapMode: Text.WordWrap - Layout.fillWidth: true; + Layout.fillWidth: true Layout.leftMargin: 4 color: root.colorScheme.signal_danger - + colorScheme: root.colorScheme type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption + wrapMode: Text.WordWrap } } - TextField { - colorScheme: root.colorScheme id: usernameTextField - label: qsTr("Email or username") - focus: true Layout.fillWidth: true Layout.topMargin: 24 + colorScheme: root.colorScheme + focus: true + label: qsTr("Email or username") validateOnEditingFinished: false - - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = "" - usernameTextField.error = false - passwordTextField.error = false - } - } - - validator: function(str) { + validator: function (str) { if (str.length === 0) { - return qsTr("Enter email or username") + return qsTr("Enter email or username"); } - return } onAccepted: passwordTextField.forceActiveFocus() - } - - TextField { - colorScheme: root.colorScheme - id: passwordTextField - label: qsTr("Password") - Layout.fillWidth: true - Layout.topMargin: 8 - echoMode: TextInput.Password - validateOnEditingFinished: false - onTextChanged: { // remove "invalid username / password error" if (error || errorLabel.text.length > 0) { - errorLabel.text = "" - usernameTextField.error = false - passwordTextField.error = false + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; } } - - validator: function(str) { + } + TextField { + id: passwordTextField + Layout.fillWidth: true + Layout.topMargin: 8 + colorScheme: root.colorScheme + echoMode: TextInput.Password + label: qsTr("Password") + validateOnEditingFinished: false + validator: function (str) { if (str.length === 0) { - return qsTr("Enter password") + return qsTr("Enter password"); } - return } onAccepted: signInButton.checkAndSignIn() - } - - Button { - colorScheme: root.colorScheme - id: signInButton - text: loading ? qsTr("Signing in") : qsTr("Sign in") - enabled: !loading - Layout.fillWidth: true - Layout.topMargin: 24 - - - onClicked: checkAndSignIn() - - function checkAndSignIn() { - usernameTextField.validate() - passwordTextField.validate() - - if (usernameTextField.error || passwordTextField.error) { - return + onTextChanged: { + // remove "invalid username / password error" + if (error || errorLabel.text.length > 0) { + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; } - - usernameTextField.enabled = false - passwordTextField.enabled = false - - loading = true - - Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)) } } + Button { + id: signInButton + function checkAndSignIn() { + usernameTextField.validate(); + passwordTextField.validate(); + if (usernameTextField.error || passwordTextField.error) { + return; + } + usernameTextField.enabled = false; + passwordTextField.enabled = false; + loading = true; + Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); + } - Label { + Layout.fillWidth: true + Layout.topMargin: 24 colorScheme: root.colorScheme - textFormat: Text.StyledText - text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + enabled: !loading + text: loading ? qsTr("Signing in") : qsTr("Sign in") + + onClicked: { + checkAndSignIn(); + } + } + Label { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 24 + colorScheme: root.colorScheme + text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + textFormat: Text.StyledText type: Label.LabelType.Body onLinkActivated: { - Qt.openUrlExternally(link) + Qt.openUrlExternally(link); } } } - ColumnLayout { id: login2FALayout - function reset() { - twoFAButton.loading = false - - twoFactorPasswordTextField.enabled = true - twoFactorPasswordTextField.error = false - twoFactorPasswordTextField.errorString = "" - twoFactorPasswordTextField.text = "" + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = false; + twoFactorPasswordTextField.errorString = ""; + twoFactorPasswordTextField.text = ""; } spacing: 0 Label { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 colorScheme: root.colorScheme text: qsTr("Two-factor authentication") - Layout.topMargin: 16 - Layout.alignment: Qt.AlignCenter type: Label.LabelType.Heading } - Label { - colorScheme: root.colorScheme id: twoFactorUsernameLabel - Layout.alignment: Qt.AlignCenter Layout.topMargin: 8 - type: Label.LabelType.Lead color: root.colorScheme.text_weak - } - - TextField { colorScheme: root.colorScheme + type: Label.LabelType.Lead + } + TextField { id: twoFactorPasswordTextField - label: qsTr("Two-factor code") - assistiveText: qsTr("Enter the 6-digit code") - validateOnEditingFinished: false Layout.fillWidth: true Layout.topMargin: 32 - - validator: function(str) { + assistiveText: qsTr("Enter the 6-digit code") + colorScheme: root.colorScheme + label: qsTr("Two-factor code") + validateOnEditingFinished: false + validator: function (str) { if (str.length === 0) { - return qsTr("Enter the 6-digit code") - } - } - - onTextChanged: { - if (text.length >= 6) { - twoFAButton.onClicked() + return qsTr("Enter the 6-digit code"); } } onAccepted: { - twoFAButton.onClicked() + twoFAButton.onClicked(); + } + onTextChanged: { + if (text.length >= 6) { + twoFAButton.onClicked(); + } } - } - Button { - colorScheme: root.colorScheme id: twoFAButton - text: loading ? qsTr("Authenticating") : qsTr("Authenticate") - enabled: !loading Layout.fillWidth: true Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + text: loading ? qsTr("Authenticating") : qsTr("Authenticate") onClicked: { - twoFactorPasswordTextField.validate() - + twoFactorPasswordTextField.validate(); if (twoFactorPasswordTextField.error) { - return + return; } - - twoFactorPasswordTextField.enabled = false - loading = true - Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)) + twoFactorPasswordTextField.enabled = false; + loading = true; + Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); } } } - ColumnLayout { id: login2PasswordLayout - function reset() { - secondPasswordButton.loading = false - - secondPasswordTextField.enabled = true - secondPasswordTextField.error = false - secondPasswordTextField.errorString = "" - secondPasswordTextField.text = "" + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = false; + secondPasswordTextField.errorString = ""; + secondPasswordTextField.text = ""; } spacing: 0 Label { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 colorScheme: root.colorScheme text: qsTr("Unlock your mailbox") - Layout.topMargin: 16 - Layout.alignment: Qt.AlignCenter type: Label.LabelType.Heading } - TextField { - colorScheme: root.colorScheme id: secondPasswordTextField - label: qsTr("Mailbox password") Layout.fillWidth: true Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight + colorScheme: root.colorScheme echoMode: TextInput.Password + label: qsTr("Mailbox password") validateOnEditingFinished: false - - validator: function(str) { + validator: function (str) { if (str.length === 0) { - return qsTr("Enter password") + return qsTr("Enter password"); } - return } onAccepted: { - secondPasswordButton.onClicked() + secondPasswordButton.onClicked(); } } - Button { - colorScheme: root.colorScheme id: secondPasswordButton - text: loading ? qsTr("Unlocking") : qsTr("Unlock") - enabled: !loading - Layout.fillWidth: true Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + text: loading ? qsTr("Unlocking") : qsTr("Unlock") onClicked: { - secondPasswordTextField.validate() - + secondPasswordTextField.validate(); if (secondPasswordTextField.error) { - return + return; } - - secondPasswordTextField.enabled = false - loading = true - Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)) + secondPasswordTextField.enabled = false; + loading = true; + Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); } } } } - - states: [ - State { - name: "Page 1" - PropertyChanges { - target: stackLayout - currentIndex: 0 - } - }, - State { - name: "Page 2" - PropertyChanges { - target: stackLayout - currentIndex: 1 - } - }, - State { - name: "Page 3" - PropertyChanges { - target: stackLayout - currentIndex: 2 - } - } - ] } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SplashScreen.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SplashScreen.qml index 9490b09c..753f5d25 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SplashScreen.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SplashScreen.qml @@ -1,194 +1,169 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton Dialog { id: root - - shouldShow: Backend.showSplashScreen + leftPadding: 0 modal: true - - topPadding : 0 - leftPadding : 0 - rightPadding : 0 + rightPadding: 0 + shouldShow: Backend.showSplashScreen + topPadding: 0 ColumnLayout { spacing: 20 Image { Layout.alignment: Qt.AlignHCenter - - sourceSize.width: 384 - sourceSize.height: 144 - - Layout.preferredWidth: 384 Layout.preferredHeight: 144 - + Layout.preferredWidth: 384 source: "./icons/img-splash.png" + sourceSize.height: 144 + sourceSize.width: 384 } - Label { - colorScheme: root.colorScheme; - - Layout.alignment: Qt.AlignHCenter; + Layout.alignment: Qt.AlignHCenter Layout.leftMargin: 24 - Layout.rightMargin: 24 Layout.preferredWidth: 336 - - type: Label.Title + Layout.rightMargin: 24 + colorScheme: root.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("What's new in Bridge") + type: Label.Title } - RowLayout { width: root.width Item { Layout.fillHeight: true - width: 24 Layout.leftMargin: 32 Layout.rightMargin: 16 + width: 24 + Image { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - sourceSize.width: 24 - sourceSize.height: 24 source: "./icons/ic-splash-check.svg" + sourceSize.height: 24 + sourceSize.width: 24 } } - Label { - colorScheme: root.colorScheme - + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter; - Layout.preferredWidth: 264 Layout.leftMargin: 0 + Layout.preferredWidth: 264 Layout.rightMargin: 24 - wrapMode: Text.WordWrap - - type: Label.Body + colorScheme: root.colorScheme horizontalAlignment: Text.AlignLeft - textFormat: Text.StyledText text: qsTr("New IMAP engine
For improved stability and performance.") + textFormat: Text.StyledText + type: Label.Body + wrapMode: Text.WordWrap } } - RowLayout { width: root.width Item { Layout.fillHeight: true - width: 24 Layout.leftMargin: 32 Layout.rightMargin: 16 + width: 24 + Image { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - sourceSize.width: 24 - sourceSize.height: 24 source: "./icons/ic-splash-check.svg" + sourceSize.height: 24 + sourceSize.width: 24 } } - Label { - colorScheme: root.colorScheme - + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter; - Layout.preferredWidth: 264 Layout.leftMargin: 0 + Layout.preferredWidth: 264 Layout.rightMargin: 24 - wrapMode: Text.WordWrap - - type: Label.Body + colorScheme: root.colorScheme horizontalAlignment: Text.AlignLeft - textFormat: Text.StyledText text: qsTr("Faster than ever
Up to 10x faster syncing and receiving.") + textFormat: Text.StyledText + type: Label.Body + wrapMode: Text.WordWrap } } - RowLayout { width: root.width Item { Layout.fillHeight: true - width: 24 Layout.leftMargin: 32 Layout.rightMargin: 16 + width: 24 + Image { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - sourceSize.width: 24 - sourceSize.height: 24 source: "./icons/ic-splash-check.svg" + sourceSize.height: 24 + sourceSize.width: 24 } } - - Label { - colorScheme: root.colorScheme - + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter; - Layout.preferredWidth: 264 Layout.leftMargin: 0 + Layout.preferredWidth: 264 Layout.rightMargin: 24 - wrapMode: Text.WordWrap - - type: Label.Body + colorScheme: root.colorScheme horizontalAlignment: Text.AlignLeft - textFormat: Text.StyledText text: qsTr("Extra security
New, encrypted local database and keychain improvements.") + textFormat: Text.StyledText + type: Label.Body + wrapMode: Text.WordWrap } } - Button { Layout.fillWidth: true Layout.leftMargin: 24 Layout.rightMargin: 24 colorScheme: root.colorScheme text: "Got it" + onClicked: Backend.showSplashScreen = false } - Label { - colorScheme: root.colorScheme + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter; - Layout.preferredWidth: 336 Layout.leftMargin: 24 + Layout.preferredWidth: 336 Layout.rightMargin: 24 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Note that your client will redownload all the emails.
") + link("https://proton.me/blog/new-proton-mail-bridge", qsTr("Learn more about new Bridge.")) + textFormat: Text.StyledText + type: Label.Body wrapMode: Text.WordWrap - type: Label.Body - horizontalAlignment: Text.AlignHCenter - textFormat: Text.StyledText - text: qsTr("Note that your client will redownload all the emails.
") + link("https://proton.me/blog/new-proton-mail-bridge", qsTr("Learn more about new Bridge.")) - - onLinkActivated: function(link) { Qt.openUrlExternally(link) } + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } } } } - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Status.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Status.qml index dfe53d8d..b64c8027 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Status.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Status.qml @@ -1,111 +1,93 @@ - // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl - import Proton import Notifications Item { id: root - property var notifications - property ColorScheme colorScheme - - property int notificationWhitelist: NotificationFilter.FilterConsts.All - property int notificationBlacklist: NotificationFilter.FilterConsts.None - readonly property Notification activeNotification: notificationFilter.topmost + property ColorScheme colorScheme + property int notificationBlacklist: NotificationFilter.FilterConsts.None + property int notificationWhitelist: NotificationFilter.FilterConsts.All + property var notifications implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin NotificationFilter { id: notificationFilter - + blacklist: root.notificationBlacklist source: root.notifications ? root.notifications.all : undefined whitelist: root.notificationWhitelist - blacklist: root.notificationBlacklist onTopmostChanged: { if (!topmost) { - image.source = "/qml/icons/ic-connected.svg" - image.color = root.colorScheme.signal_success - label.text = qsTr("Connected") - label.color = root.colorScheme.signal_success + image.source = "/qml/icons/ic-connected.svg"; + image.color = root.colorScheme.signal_success; + label.text = qsTr("Connected"); + label.color = root.colorScheme.signal_success; return; } - - image.source = topmost.icon - label.text = topmost.brief - + image.source = topmost.icon; + label.text = topmost.brief; switch (topmost.type) { - case Notification.NotificationType.Danger: - image.color = root.colorScheme.signal_danger - label.color = root.colorScheme.signal_danger + case Notification.NotificationType.Danger: + image.color = root.colorScheme.signal_danger; + label.color = root.colorScheme.signal_danger; break; - case Notification.NotificationType.Warning: - image.color = root.colorScheme.signal_warning - label.color = root.colorScheme.signal_warning + case Notification.NotificationType.Warning: + image.color = root.colorScheme.signal_warning; + label.color = root.colorScheme.signal_warning; break; - case Notification.NotificationType.Success: - image.color = root.colorScheme.signal_success - label.color = root.colorScheme.signal_success + case Notification.NotificationType.Success: + image.color = root.colorScheme.signal_success; + label.color = root.colorScheme.signal_success; break; - case Notification.NotificationType.Info: - image.color = root.colorScheme.signal_info - label.color = root.colorScheme.signal_info + case Notification.NotificationType.Info: + image.color = root.colorScheme.signal_info; + label.color = root.colorScheme.signal_info; break; } } } - RowLayout { anchors.fill: parent spacing: 8 ColorImage { id: image - width: 16 - height: 16 - sourceSize.width: width - sourceSize.height: height - source: "/qml/icons/ic-connected.svg" color: root.colorScheme.signal_success + height: 16 + source: "/qml/icons/ic-connected.svg" + sourceSize.height: height + sourceSize.width: width + width: 16 } - Label { - colorScheme: root.colorScheme id: label - Layout.fillHeight: true Layout.fillWidth: true - - wrapMode: Text.WordWrap - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - text: qsTr("Connected") color: root.colorScheme.signal_success + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignLeft + text: qsTr("Connected") + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml index 88c331bb..8be55925 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml @@ -1,25 +1,19 @@ // Copyright (c) 2023 Proton AG -// // This file is part of Proton Mail Bridge. -// // Proton Mail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// // Proton Mail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . - import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls - import Proton Item { @@ -34,24 +28,46 @@ Item { anchors.fill: parent spacing: 0 - Rectangle { - color: root.colorScheme.background_norm + states: [ + State { + name: "Page 1" + PropertyChanges { + currentIndex: 0 + target: signInItem + } + }, + State { + name: "Page 2" + + PropertyChanges { + currentIndex: 1 + target: signInItem + } + }, + State { + name: "Page 3" + + PropertyChanges { + currentIndex: 2 + target: signInItem + } + } + ] + + Rectangle { Layout.fillHeight: true Layout.fillWidth: true - + color: root.colorScheme.background_norm implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth - - visible: signInItem.currentIndex == 0 + visible: signInItem.currentIndex === 0 GridLayout { anchors.fill: parent - columnSpacing: 0 - rowSpacing: 0 - columns: 3 + rowSpacing: 0 // top margin Item { @@ -59,141 +75,123 @@ Item { Layout.fillWidth: true // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { + Binding on Layout.preferredHeight { value: (parent.height - welcomeContentItem.height) / 4 } } // left margin Item { - Layout.minimumWidth: 48 - Layout.maximumWidth: 80 Layout.fillWidth: true + Layout.maximumWidth: 80 + Layout.minimumWidth: 48 Layout.preferredHeight: welcomeContentItem.height } - ColumnLayout { id: welcomeContentItem Layout.fillWidth: true spacing: 0 Image { - source: colorScheme.welcome_img Layout.alignment: Qt.AlignHCenter Layout.topMargin: 16 + source: colorScheme.welcome_img sourceSize.height: 148 sourceSize.width: 264 } - Label { - colorScheme: root.colorScheme - text: qsTr("Welcome to\nProton Mail Bridge") Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.topMargin: 16 - + colorScheme: root.colorScheme horizontalAlignment: Text.AlignHCenter - + text: qsTr("Welcome to\nProton Mail Bridge") type: Label.LabelType.Heading } - Label { - colorScheme: root.colorScheme id: longTextLabel - text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.") Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.topMargin: 16 Layout.preferredWidth: 320 - - wrapMode: Text.WordWrap - + Layout.topMargin: 16 + colorScheme: root.colorScheme horizontalAlignment: Text.AlignHCenter - + text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.") type: Label.LabelType.Body + wrapMode: Text.WordWrap } } // Right margin Item { - Layout.minimumWidth: 48 - Layout.maximumWidth: 80 Layout.fillWidth: true + Layout.maximumWidth: 80 + Layout.minimumWidth: 48 Layout.preferredHeight: welcomeContentItem.height } // bottom margin Item { Layout.columnSpan: 3 - Layout.fillWidth: true Layout.fillHeight: true - + Layout.fillWidth: true implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin implicitWidth: children[0].implicitWidth Image { id: logoImage - source: colorScheme.logo_img - - anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - anchors.topMargin: 48 anchors.bottomMargin: 48 + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 48 + source: colorScheme.logo_img sourceSize.height: 25 sourceSize.width: 200 } } } } - Rectangle { - color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm Layout.fillHeight: true Layout.fillWidth: true - + color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth RowLayout { anchors.fill: parent spacing: 0 + Item { Layout.fillHeight: true Layout.fillWidth: true Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4 - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin Button { - colorScheme: root.colorScheme - anchors.left: parent.left anchors.bottom: parent.bottom - + anchors.bottomMargin: 80 + anchors.left: parent.left anchors.leftMargin: 80 anchors.rightMargin: 80 anchors.topMargin: 80 - anchors.bottomMargin: 80 - - visible: signInItem.currentIndex != 0 - + colorScheme: root.colorScheme secondary: true text: qsTr("Back") + visible: signInItem.currentIndex != 0 onClicked: { - signInItem.abort() + signInItem.abort(); } } } - GridLayout { Layout.fillHeight: true Layout.fillWidth: true - columnSpacing: 0 - rowSpacing: 0 - columns: 3 + rowSpacing: 0 // top margin Item { @@ -201,76 +199,47 @@ Item { Layout.fillWidth: true // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { + Binding on Layout.preferredHeight { value: (parent.height - signInItem.height) / 4 } } // left margin Item { - Layout.minimumWidth: 48 - Layout.maximumWidth: 80 Layout.fillWidth: true + Layout.maximumWidth: 80 + Layout.minimumWidth: 48 Layout.preferredHeight: signInItem.height } - - SignIn { id: signInItem - colorScheme: root.colorScheme - - Layout.preferredWidth: 320 Layout.fillWidth: true - + Layout.preferredWidth: 320 + colorScheme: root.colorScheme focus: true username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : "" } // Right margin Item { - Layout.minimumWidth: 48 - Layout.maximumWidth: 80 Layout.fillWidth: true + Layout.maximumWidth: 80 + Layout.minimumWidth: 48 Layout.preferredHeight: signInItem.height } // bottom margin Item { Layout.columnSpan: 3 - Layout.fillWidth: true Layout.fillHeight: true + Layout.fillWidth: true } } - Item { Layout.fillHeight: true - Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4 + Layout.preferredWidth: signInItem.currentIndex === 0 ? 0 : parent.width / 4 } } } - - states: [ - State { - name: "Page 1" - PropertyChanges { - target: signInItem - currentIndex: 0 - } - }, - State { - name: "Page 2" - PropertyChanges { - target: signInItem - currentIndex: 1 - } - }, - State { - name: "Page 3" - PropertyChanges { - target: signInItem - currentIndex: 2 - } - } - ] } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Buttons.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/Buttons.qml deleted file mode 100644 index 1b82a3a2..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Buttons.qml +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - // Primary buttons - ButtonsColumn { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.fillHeight: true - - iconLoading: "/qml/icons/Loader_16.svg" - } - - // Secondary buttons - ButtonsColumn { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.fillHeight: true - - secondary: true - iconLoading: "/qml/icons/Loader_16.svg" - } - - // Secondary icons - ButtonsColumn { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.fillHeight: true - - secondary: true - textNormal: "" - iconNormal: "/qml/icons/ic-cross-close.svg" - textDisabled: "" - iconDisabled: "/qml/icons/ic-cross-close.svg" - textLoading: "" - iconLoading: "/qml/icons/Loader_16.svg" - } - - // Icons - ButtonsColumn { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.fillHeight: true - - textNormal: "" - iconNormal: "/qml/icons/ic-cross-close.svg" - textDisabled: "" - iconDisabled: "/qml/icons/ic-cross-close.svg" - textLoading: "" - iconLoading: "/qml/icons/Loader_16.svg" - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/ButtonsColumn.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/ButtonsColumn.qml deleted file mode 100644 index f4079c48..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/ButtonsColumn.qml +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick.Layouts -import QtQuick -import QtQuick.Controls - -import "../Proton" - -ColumnLayout { - id: root - property ColorScheme colorScheme - - property string textNormal: "Button" - property string iconNormal: "" - property string textDisabled: "Disabled" - property string iconDisabled: "" - property string textLoading: "Loading" - property string iconLoading: "" - property bool secondary: false - - Button { - colorScheme: root.colorScheme - Layout.fillWidth: true - - Layout.minimumHeight: implicitHeight - Layout.minimumWidth: implicitWidth - - text: root.textNormal - icon.source: iconNormal - secondary: root.secondary - } - - - Button { - colorScheme: root.colorScheme - Layout.fillWidth: true - - Layout.minimumHeight: implicitHeight - Layout.minimumWidth: implicitWidth - - text: root.textDisabled - icon.source: iconDisabled - secondary: root.secondary - - enabled: false - } - - Button { - colorScheme: root.colorScheme - Layout.fillWidth: true - - Layout.minimumHeight: implicitHeight - Layout.minimumWidth: implicitWidth - - text: root.textLoading - icon.source: iconLoading - secondary: root.secondary - - loading: true - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/CheckBoxes.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/CheckBoxes.qml deleted file mode 100644 index 12f18b66..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/CheckBoxes.qml +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - CheckBox { - text: "Checkbox" - colorScheme: root.colorScheme - } - - CheckBox { - text: "Checkbox" - error: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "Checkbox" - enabled: false - colorScheme: root.colorScheme - } - CheckBox { - text: "" - colorScheme: root.colorScheme - } - - CheckBox { - text: "" - error: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "" - enabled: false - colorScheme: root.colorScheme - } - } - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - CheckBox { - text: "Checkbox" - checked: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "Checkbox" - checked: true - error: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "Checkbox" - checked: true - enabled: false - colorScheme: root.colorScheme - } - CheckBox { - text: "" - checked: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "" - checked: true - error: true - colorScheme: root.colorScheme - } - - CheckBox { - text: "" - checked: true - enabled: false - colorScheme: root.colorScheme - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/ComboBoxes.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/ComboBoxes.qml deleted file mode 100644 index 437622d5..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/ComboBoxes.qml +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - ColumnLayout { - Layout.fillWidth: true - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - colorScheme: root.colorScheme - } - - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - editable: true - colorScheme: root.colorScheme - } - } - - ColumnLayout { - Layout.fillWidth: true - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - colorScheme: root.colorScheme - enabled: false - } - - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - editable: true - colorScheme: root.colorScheme - enabled: false - } - } - - ColumnLayout { - Layout.fillWidth: true - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - colorScheme: root.colorScheme - LayoutMirroring.enabled: true - } - - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - editable: true - colorScheme: root.colorScheme - LayoutMirroring.enabled: true - } - } - - ColumnLayout { - Layout.fillWidth: true - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - colorScheme: root.colorScheme - enabled: false - LayoutMirroring.enabled: true - } - - ComboBox { - Layout.fillWidth: true - model: ["First", "Second", "Third"] - editable: true - colorScheme: root.colorScheme - enabled: false - LayoutMirroring.enabled: true - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/RadioButtons.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/RadioButtons.qml deleted file mode 100644 index 11a704f2..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/RadioButtons.qml +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - } - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - error: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - enabled: false - } - RadioButton { - colorScheme: root.colorScheme - text: "" - } - - RadioButton { - colorScheme: root.colorScheme - text: "" - error: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "" - enabled: false - } - } - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - checked: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - checked: true - error: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "Radio" - checked: true - enabled: false - } - RadioButton { - colorScheme: root.colorScheme - text: "" - checked: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "" - checked: true - error: true - } - - RadioButton { - colorScheme: root.colorScheme - text: "" - checked: true - enabled: false - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Switches.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/Switches.qml deleted file mode 100644 index 4b664e1e..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Switches.qml +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - Switch { - text: "Toggle" - colorScheme: root.colorScheme - } - - Switch { - text: "Toggle" - enabled: false - colorScheme: root.colorScheme - } - - Switch { - text: "Toggle" - loading: true - colorScheme: root.colorScheme - } - - Switch { - text: "" - colorScheme: root.colorScheme - } - - Switch { - text: "" - enabled: false - colorScheme: root.colorScheme - } - - Switch { - text: "" - loading: true - colorScheme: root.colorScheme - } - } - - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - Switch { - text: "Toggle" - checked: true - colorScheme: root.colorScheme - } - - Switch { - text: "Toggle" - checked: true - enabled: false - colorScheme: root.colorScheme - } - - Switch { - text: "Toggle" - checked: true - loading: true - colorScheme: root.colorScheme - } - - Switch { - text: "" - checked: true - colorScheme: root.colorScheme - } - - Switch { - text: "" - checked: true - enabled: false - colorScheme: root.colorScheme - } - - Switch { - text: "" - checked: true - loading: true - colorScheme: root.colorScheme - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Test.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/Test.qml deleted file mode 100644 index c6027148..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/Test.qml +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick.Window - -import "../Proton" - -Window { - width: 800 - height: 600 - visible: true - TestComponents { - anchors.fill: parent - colorScheme: ProtonStyle.currentStyle - } - onClosing: { - Qt.quit() - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TestComponents.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/TestComponents.qml deleted file mode 100644 index a4fd5738..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TestComponents.qml +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -Rectangle { - id: root - property ColorScheme colorScheme - color: colorScheme.background_norm - clip: true - - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin - implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin - - ScrollView { - anchors.fill: parent - - ColumnLayout { - anchors.margins: 20 - - width: root.width - - spacing: 5 - - Buttons { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - CheckBoxes { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - ComboBoxes { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - RadioButtons { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - Switches { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - TextAreas { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - - TextFields { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.margins: 20 - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextAreas.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextAreas.qml deleted file mode 100644 index e6146d9a..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextAreas.qml +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -ColumnLayout { - id: root - property ColorScheme colorScheme - - spacing: 10 - - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 - - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - - wrapMode: TextInput.Wrap - } - - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - - wrapMode: TextInput.Wrap - } - - - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 - - error: true - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - errorString: "Error message" - - wrapMode: TextInput.Wrap - } - - - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 - - enabled: false - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - - wrapMode: TextInput.Wrap - } - - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 - - placeholderText: "Type 42 here" - label: "42 Validator" - hint: "Accepts only \"42\"" - assistiveText: "Type something here, preferably 42" - - wrapMode: TextInput.Wrap - - validator: function(str) { - if (str === "42") { - return - } - - return "Not 42" - } - } -} - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextFields.qml b/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextFields.qml deleted file mode 100644 index d3618660..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/tests/TextFields.qml +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls - -import "../Proton" - -RowLayout { - id: root - property ColorScheme colorScheme - - // Norm - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } - - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - error: true - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - errorString: "Error message" - } - - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - - enabled: false - } - } - - // Masked - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - echoMode: TextInput.Password - placeholderText: "Password" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - text: "Password" - - echoMode: TextInput.Password - placeholderText: "Password" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - text: "Password" - error: true - - echoMode: TextInput.Password - placeholderText: "Password" - label: "Label" - hint: "Hint" - errorString: "Error message" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - text: "Password" - enabled: false - - echoMode: TextInput.Password - placeholderText: "Password" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } - } - - // Varia - ColumnLayout { - Layout.fillWidth: true - - spacing: parent.spacing - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - placeholderText: "Type 42 here" - label: "42 Validator" - hint: "Accepts only \"42\"" - assistiveText: "Type something here, preferably 42" - - validator: function(str) { - if (str === "42") { - return - } - - return "Not 42" - } - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - placeholderText: "Placeholder" - label: "Label" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - placeholderText: "Placeholder" - hint: "Hint" - } - - TextField { - colorScheme: root.colorScheme - Layout.fillWidth: true - - placeholderText: "Placeholder" - assistiveText: "Assistive text" - } - } -}