chore: use qmlformat on qml files, and removed deprecated tests.

This commit is contained in:
Xavier Michelon
2023-07-18 18:12:29 +02:00
parent 3ef526333a
commit eda49483e2
55 changed files with 4268 additions and 6088 deletions

View File

@ -1,251 +1,252 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
Item { Item {
id: root id: root
enum ViewType {
property ColorScheme colorScheme SmallView,
property var user LargeView
}
property var _spacing: 12 * ProtonStyle.px property var _spacing: 12 * ProtonStyle.px
property ColorScheme colorScheme
property color progressColor: { property color progressColor: {
if (!root.enabled) return root.colorScheme.text_weak if (!root.enabled)
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak return root.colorScheme.text_weak;
if (root.user && root.user.isSyncing) return root.colorScheme.text_weak if (root.type === AccountDelegate.SmallView)
if (root.progressRatio < .50) return root.colorScheme.signal_success return root.colorScheme.text_weak;
if (root.progressRatio < .75) return root.colorScheme.signal_warning if (root.user && root.user.isSyncing)
return root.colorScheme.signal_danger 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: { property real progressRatio: {
if (!root.user) if (!root.user)
return 0 return 0;
return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes) 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 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) property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
property var user
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]
}
function primaryEmail() { 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 // width expected to be set by parent object
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
enum ViewType{
SmallView, LargeView
}
property var type : AccountDelegate.SmallView
RowLayout { RowLayout {
spacing: root._spacing spacing: root._spacing
anchors { anchors {
top: root.top
left: root.left left: root.left
right: root.right right: root.right
top: root.top
} }
Rectangle { Rectangle {
id: avatar id: avatar
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: height Layout.preferredWidth: height
color: root.colorScheme.background_avatar
radius: ProtonStyle.avatar_radius radius: ProtonStyle.avatar_radius
color: root.colorScheme.background_avatar
Label { Label {
colorScheme: root.colorScheme
anchors.fill: parent anchors.fill: parent
color: "#FFFFFF"
colorScheme: root.colorScheme
font.weight: Font.Normal
horizontalAlignment: Qt.AlignHCenter
text: root.user ? root.user.avatarText.toUpperCase() : "" text: root.user ? root.user.avatarText.toUpperCase() : ""
type: { type: {
switch (root.type) { switch (root.type) {
case AccountDelegate.SmallView: return Label.Body case AccountDelegate.SmallView:
case AccountDelegate.LargeView: return Label.Title return Label.Body;
case AccountDelegate.LargeView:
return Label.Title;
} }
} }
font.weight: Font.Normal
color: "#FFFFFF"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
} }
} }
ColumnLayout { ColumnLayout {
id: account id: account
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
Label { Label {
id: labelEmail id: labelEmail
Layout.maximumWidth: root.width - (root._spacing + avatar.width) Layout.maximumWidth: root.width - (root._spacing + avatar.width)
colorScheme: root.colorScheme colorScheme: root.colorScheme
elide: Text.ElideMiddle
text: primaryEmail() text: primaryEmail()
type: { type: {
switch (root.type) { switch (root.type) {
case AccountDelegate.SmallView: return Label.Body case AccountDelegate.SmallView:
case AccountDelegate.LargeView: return Label.Title return Label.Body;
case AccountDelegate.LargeView:
return Label.Title;
} }
} }
elide: Text.ElideMiddle
MouseArea { MouseArea {
id: labelArea id: labelArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
} }
ToolTip { ToolTip {
id: toolTipEmail id: toolTipEmail
visible: labelArea.containsMouse && labelEmail.truncated
text: primaryEmail()
delay: 1000 delay: 1000
text: primaryEmail()
visible: labelArea.containsMouse && labelEmail.truncated
background: Rectangle { background: Rectangle {
border.color: root.colorScheme.background_strong border.color: root.colorScheme.background_strong
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
} }
contentItem: Text { contentItem: Text {
color: root.colorScheme.text_norm color: root.colorScheme.text_norm
text: toolTipEmail.text text: toolTipEmail.text
} }
} }
} }
Item {
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 } implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0
}
RowLayout { RowLayout {
spacing: 0 spacing: 0
Label { Label {
color: root.progressColor
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: { text: {
if (!root.user) if (!root.user)
return qsTr("Signed out") return qsTr("Signed out");
switch (root.user.state) { switch (root.user.state) {
case EUserState.SignedOut: case EUserState.SignedOut:
default: default:
return qsTr("Signed out") return qsTr("Signed out");
case EUserState.Locked: case EUserState.Locked:
return qsTr("Connecting") + dotsTimer.dots return qsTr("Connecting") + dotsTimer.dots;
case EUserState.Connected: case EUserState.Connected:
if (root.user.isSyncing) 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 else
return root.usedSpace return root.usedSpace;
}
}
type: {
switch (root.type) {
case AccountDelegate.SmallView:
return Label.Caption;
case AccountDelegate.LargeView:
return Label.Body;
} }
} }
Timer { // dots animation while connecting & syncing. Timer {
// dots animation while connecting & syncing.
id: dotsTimer id: dotsTimer
property string dots: "" property string dots: ""
interval: 500;
repeat: true; interval: 500
repeat: true
running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing)) running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
onTriggered: {
dots += "."
if (dots.length > 3)
dots = ""
}
onRunningChanged: { onRunningChanged: {
dots = "" dots = "";
} }
} onTriggered: {
dots += ".";
color: root.progressColor if (dots.length > 3)
type: { dots = "";
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
case AccountDelegate.LargeView: return Label.Body
} }
} }
} }
Label { Label {
colorScheme: root.colorScheme
text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
colorScheme: root.colorScheme
text: root.user && root.user.state === EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
type: { type: {
switch (root.type) { switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption case AccountDelegate.SmallView:
case AccountDelegate.LargeView: return Label.Body return Label.Caption;
case AccountDelegate.LargeView:
return Label.Body;
} }
} }
} }
} }
Item {
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 } implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0
}
Rectangle { Rectangle {
id: progress_bar id: progress_bar
visible: root.user ? root.type == AccountDelegate.LargeView : false color: root.colorScheme.border_weak
width: 140 * ProtonStyle.px
height: 4 * ProtonStyle.px height: 4 * ProtonStyle.px
radius: ProtonStyle.progress_bar_radius radius: ProtonStyle.progress_bar_radius
color: root.colorScheme.border_weak visible: root.user ? root.type === AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
Rectangle { Rectangle {
id: progress_bar_filled id: progress_bar_filled
radius: ProtonStyle.progress_bar_radius
color: root.progressColor 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 { anchors {
top : parent.top
bottom: parent.bottom bottom: parent.bottom
left: parent.left left: parent.left
} top: parent.top
width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width }
} }
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }

View File

@ -1,42 +1,35 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
Item { Item {
id: root 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 ColorScheme colorScheme
property var notifications property var notifications
property var user property var user
signal showSignIn
signal showSetupGuide(var user, string address) signal showSetupGuide(var user, string address)
signal showSignIn
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
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@ -45,6 +38,7 @@ Item {
ScrollView { ScrollView {
id: scrollView id: scrollView
anchors.fill: parent anchors.fill: parent
Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds
ColumnLayout { ColumnLayout {
@ -54,16 +48,16 @@ Item {
Rectangle { Rectangle {
id: topArea id: topArea
color: root.colorScheme.background_norm
clip: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true
color: root.colorScheme.background_norm
implicitHeight: childrenRect.height implicitHeight: childrenRect.height
ColumnLayout { ColumnLayout {
id: topLayout id: topLayout
width: _contentWidth
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: _spacing spacing: _spacing
width: _contentWidth
RowLayout { RowLayout {
// account delegate with action buttons // account delegate with action buttons
@ -73,83 +67,82 @@ Item {
AccountDelegate { AccountDelegate {
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
user: root.user
type: AccountDelegate.LargeView
enabled: _connected enabled: _connected
type: AccountDelegate.LargeView
user: root.user
} }
Button { Button {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Sign out")
secondary: true secondary: true
text: qsTr("Sign out")
visible: _connected visible: _connected
onClicked: { onClicked: {
if (!root.user) if (!root.user)
return; return;
root.user.logout(); root.user.logout();
} }
} }
Button { Button {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Sign in")
secondary: true secondary: true
text: qsTr("Sign in")
visible: root.user ? (root.user.state === EUserState.SignedOut) : false visible: root.user ? (root.user.state === EUserState.SignedOut) : false
onClicked: { onClicked: {
if (!root.user) if (!root.user)
return; return;
root.showSignIn(); root.showSignIn();
} }
} }
Button { Button {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme colorScheme: root.colorScheme
icon.source: "/qml/icons/ic-trash.svg" icon.source: "/qml/icons/ic-trash.svg"
secondary: true secondary: true
visible: root.user ? root.user.state !== EUserState.Locked : false
onClicked: { onClicked: {
if (!root.user) if (!root.user)
return; return;
root.notifications.askDeleteAccount(root.user); root.notifications.askDeleteAccount(root.user);
} }
visible: root.user ? root.user.state !== EUserState.Locked : false
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: root._lineThickness
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
height: root._lineThickness
} }
SettingsItem { SettingsItem {
colorScheme: root.colorScheme Layout.fillWidth: true
text: qsTr("Email clients")
actionText: qsTr("Configure") actionText: qsTr("Configure")
colorScheme: root.colorScheme
description: qsTr("Using the mailbox details below (re)configure your client.") description: qsTr("Using the mailbox details below (re)configure your client.")
showSeparator: splitMode.visible
text: qsTr("Email clients")
type: SettingsItem.Button type: SettingsItem.Button
visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1) visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1)
showSeparator: splitMode.visible
onClicked: { onClicked: {
if (!root.user) if (!root.user)
return; return;
root.showSetupGuide(root.user, user.addresses[0]); root.showSetupGuide(root.user, user.addresses[0]);
} }
Layout.fillWidth: true
} }
SettingsItem { SettingsItem {
id: splitMode id: splitMode
colorScheme: root.colorScheme Layout.fillWidth: true
text: qsTr("Split addresses")
description: qsTr("Setup multiple email addresses individually.")
type: SettingsItem.Toggle
checked: root.user ? root.user.splitMode : false 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 showSeparator: addressSelector.visible
text: qsTr("Split addresses")
type: SettingsItem.Toggle
visible: _connected && root.user.addresses.length > 1
onClicked: { onClicked: {
if (!splitMode.checked) { if (!splitMode.checked) {
root.notifications.askEnableSplitMode(user); root.notifications.askEnableSplitMode(user);
@ -158,26 +151,23 @@ Item {
root.user.toggleSplitMode(!splitMode.checked); root.user.toggleSplitMode(!splitMode.checked);
} }
} }
Layout.fillWidth: true
} }
RowLayout { RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: _spacing Layout.bottomMargin: _spacing
Layout.fillWidth: true
visible: _connected && root.user.splitMode visible: _connected && root.user.splitMode
ComboBox { ComboBox {
id: addressSelector id: addressSelector
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
model: root.user ? root.user.addresses : null model: root.user ? root.user.addresses : null
} }
Button { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Configure")
secondary: true secondary: true
text: qsTr("Configure")
onClicked: { onClicked: {
if (!root.user) if (!root.user)
return; return;
@ -185,25 +175,23 @@ Item {
} }
} }
} }
Rectangle { Rectangle {
height: 0 height: 0
} // just for some extra space before separator } // just for some extra space before separator
} }
} }
Rectangle { Rectangle {
id: bottomArea id: bottomArea
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: bottomLayout.implicitHeight
color: root.colorScheme.background_weak color: root.colorScheme.background_weak
implicitHeight: bottomLayout.implicitHeight
ColumnLayout { ColumnLayout {
id: bottomLayout id: bottomLayout
width: _contentWidth
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: _spacing spacing: _spacing
visible: _connected visible: _connected
width: _contentWidth
Label { Label {
Layout.topMargin: _detailsMargin Layout.topMargin: _detailsMargin
@ -211,35 +199,34 @@ Item {
text: qsTr("Mailbox details") text: qsTr("Mailbox details")
type: Label.Body_semibold type: Label.Body_semibold
} }
RowLayout { RowLayout {
id: configuration id: configuration
spacing: _spacing
Layout.fillWidth: true
Layout.fillHeight: true
property string currentAddress: addressSelector.displayText property string currentAddress: addressSelector.displayText
Configuration { Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme spacing: _spacing
title: qsTr("IMAP")
hostname: Backend.hostname
port: Backend.imapPort.toString()
username: configuration.currentAddress
password: root.user ? root.user.password : ""
security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS"
}
Configuration { Configuration {
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
title: qsTr("SMTP")
hostname: Backend.hostname hostname: Backend.hostname
port: Backend.smtpPort.toString()
username: configuration.currentAddress
password: root.user ? root.user.password : "" 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" security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS"
title: qsTr("SMTP")
username: configuration.currentAddress
} }
} }
} }

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
import Notifications import Notifications
@ -27,34 +21,28 @@ Popup {
id: root id: root
property ColorScheme colorScheme property ColorScheme colorScheme
property Notification notification
property var mainWindow property var mainWindow
property Notification notification
topMargin: 37
leftMargin: (mainWindow.width - root.implicitWidth)/2
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
leftMargin: (mainWindow.width - root.implicitWidth) / 2
popupType: ApplicationWindow.PopupType.Banner
shouldShow: notification ? (notification.active && !notification.dismissed) : false
modal: false modal: false
popupType: ApplicationWindow.PopupType.Banner
shouldShow: notification ? (notification.active && !notification.dismissed) : false
topMargin: 37
Action { Action {
id: defaultDismissAction id: defaultDismissAction
text: qsTr("OK") text: qsTr("OK")
onTriggered: { onTriggered: {
if (!root.notification) { if (!root.notification) {
return return;
} }
root.notification.dismissed = true;
root.notification.dismissed = true
} }
} }
RowLayout { RowLayout {
id: contentLayout id: contentLayout
anchors.fill: parent anchors.fill: parent
@ -63,170 +51,148 @@ Popup {
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true clip: true
implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin
implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin
Rectangle { Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
width: parent.width + 10 anchors.top: parent.top
radius: ProtonStyle.banner_radius
color: { color: {
if (!root.notification) { if (!root.notification) {
return "transparent" return "transparent";
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return root.colorScheme.signal_info return root.colorScheme.signal_info;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return root.colorScheme.signal_success return root.colorScheme.signal_success;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning return root.colorScheme.signal_warning;
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger return root.colorScheme.signal_danger;
} }
} }
radius: ProtonStyle.banner_radius
width: parent.width + 10
} }
RowLayout { RowLayout {
anchors.fill: parent
anchors.topMargin: 14
anchors.bottomMargin: 14 anchors.bottomMargin: 14
anchors.fill: parent
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.topMargin: 14
spacing: 8 spacing: 8
ColorImage { ColorImage {
color: root.colorScheme.text_invert
width: 24
height: 24
sourceSize.width: 24
sourceSize.height: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
color: root.colorScheme.text_invert
height: 24
source: { source: {
if (!root.notification) { if (!root.notification) {
return "" return "";
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return "/qml/icons/ic-info-circle-filled.svg" return "/qml/icons/ic-info-circle-filled.svg";
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return "/qml/icons/ic-info-circle-filled.svg" return "/qml/icons/ic-info-circle-filled.svg";
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return "/qml/icons/ic-exclamation-circle-filled.svg" return "/qml/icons/ic-exclamation-circle-filled.svg";
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return "/qml/icons/ic-exclamation-circle-filled.svg" return "/qml/icons/ic-exclamation-circle-filled.svg";
} }
} }
sourceSize.height: 24
sourceSize.width: 24
width: 24
} }
Label { Label {
colorScheme: root.colorScheme
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
color: root.colorScheme.text_invert color: root.colorScheme.text_invert
colorScheme: root.colorScheme
text: root.notification ? root.notification.description : "" text: root.notification ? root.notification.description : ""
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
} }
Rectangle { Rectangle {
Layout.fillHeight: true Layout.fillHeight: true
width: 1
color: { color: {
if (!root.notification) { if (!root.notification) {
return "transparent" return "transparent";
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return root.colorScheme.signal_info_active return root.colorScheme.signal_info_active;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return root.colorScheme.signal_success_active return root.colorScheme.signal_success_active;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning_active return root.colorScheme.signal_warning_active;
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger_active return root.colorScheme.signal_danger_active;
} }
} }
width: 1
} }
Button { Button {
colorScheme: root.colorScheme
Layout.fillHeight: true
id: actionButton id: actionButton
Layout.fillHeight: true
action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction
colorScheme: root.colorScheme
background: Item { background: Item {
clip: true clip: true
Rectangle { Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
width: parent.width + 10 anchors.top: parent.top
radius: ProtonStyle.banner_radius
color: { color: {
if (!root.notification) { if (!root.notification) {
return "transparent" return "transparent";
} }
let norm;
var norm let hover;
var hover let active;
var active
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
norm = root.colorScheme.signal_info norm = root.colorScheme.signal_info;
hover = root.colorScheme.signal_info_hover hover = root.colorScheme.signal_info_hover;
active = root.colorScheme.signal_info_active active = root.colorScheme.signal_info_active;
break; break;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
norm = root.colorScheme.signal_success norm = root.colorScheme.signal_success;
hover = root.colorScheme.signal_success_hover hover = root.colorScheme.signal_success_hover;
active = root.colorScheme.signal_success_active active = root.colorScheme.signal_success_active;
break; break;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
norm = root.colorScheme.signal_warning norm = root.colorScheme.signal_warning;
hover = root.colorScheme.signal_warning_hover hover = root.colorScheme.signal_warning_hover;
active = root.colorScheme.signal_warning_active active = root.colorScheme.signal_warning_active;
break; break;
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
norm = root.colorScheme.signal_danger norm = root.colorScheme.signal_danger;
hover = root.colorScheme.signal_danger_hover hover = root.colorScheme.signal_danger_hover;
active = root.colorScheme.signal_danger_active active = root.colorScheme.signal_danger_active;
break; break;
} }
if (actionButton.down) { if (actionButton.down) {
return active return active;
} }
if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) { if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) {
return hover return hover;
} }
if (actionButton.loading) { if (actionButton.loading) {
return hover return hover;
}
return norm
} }
return norm;
}
radius: ProtonStyle.banner_radius
width: parent.width + 10
} }
} }
} }

View File

@ -1,129 +1,112 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
import Qt.labs.platform import Qt.labs.platform
import Proton import Proton
import Notifications import Notifications
QtObject { QtObject {
id: root id: root
function bound(num, lowerLimit, upperLimit) { property MainWindow _mainWindow: MainWindow {
return Math.max(lowerLimit, Math.min(upperLimit, num)) id: mainWindow
notifications: root._notifications
title: root.title
visible: false
onVisibleChanged: {
Backend.dockIconVisible = visible;
} }
property var title: Backend.appname Connections {
function onColorSchemeNameChanged(scheme) {
root.setColorScheme();
}
function onDiskCacheUnavailable() {
mainWindow.showAndRise();
}
function onHideMainWindow() {
mainWindow.hide();
}
target: Backend
}
}
property Notifications _notifications: Notifications { property Notifications _notifications: Notifications {
id: notifications id: notifications
frontendMain: mainWindow frontendMain: mainWindow
} }
property NotificationFilter _trayNotificationFilter: NotificationFilter { property NotificationFilter _trayNotificationFilter: NotificationFilter {
id: trayNotificationFilter id: trayNotificationFilter
source: root._notifications ? root._notifications.all : undefined source: root._notifications ? root._notifications.all : undefined
onTopmostChanged: { onTopmostChanged: {
if (topmost) { if (topmost) {
switch (topmost.type) { switch (topmost.type) {
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
Backend.setErrorTrayIcon(topmost.brief, topmost.icon) Backend.setErrorTrayIcon(topmost.brief, topmost.icon);
return return;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
Backend.setWarnTrayIcon(topmost.brief, topmost.icon) Backend.setWarnTrayIcon(topmost.brief, topmost.icon);
return return;
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
Backend.setUpdateTrayIcon(topmost.brief, topmost.icon) Backend.setUpdateTrayIcon(topmost.brief, topmost.icon);
return return;
} }
} }
Backend.setNormalTrayIcon() Backend.setNormalTrayIcon();
} }
} }
property var title: Backend.appname
function bound(num, lowerLimit, upperLimit) {
property MainWindow _mainWindow: MainWindow { return Math.max(lowerLimit, Math.min(upperLimit, num));
id: mainWindow
visible: false
title: root.title
notifications: root._notifications
onVisibleChanged: {
Backend.dockIconVisible = visible
} }
function setColorScheme() {
Connections { if (Backend.colorSchemeName === "light")
target: Backend ProtonStyle.currentStyle = ProtonStyle.lightStyle;
function onDiskCacheUnavailable() { if (Backend.colorSchemeName === "dark")
mainWindow.showAndRise() ProtonStyle.currentStyle = ProtonStyle.darkStyle;
} }
function onColorSchemeNameChanged(scheme) { root.setColorScheme() }
function onHideMainWindow() {
mainWindow.hide();
}
}
}
Component.onCompleted: { Component.onCompleted: {
if (!Backend) { if (!Backend) {
console.log("Backend not loaded") console.log("Backend not loaded");
} }
root.setColorScheme();
root.setColorScheme()
if (!Backend.users) { if (!Backend.users) {
console.log("users not loaded") console.log("users not loaded");
} }
const c = Backend.users.count;
var c = Backend.users.count const u = Backend.users.get(0);
var u = Backend.users.get(0)
// DEBUG // DEBUG
if (c !== 0) { if (c !== 0) {
console.log("users non zero", c) console.log("users non zero", c);
console.log("first user", u ) console.log("first user", u);
} }
if (c === 0) { if (c === 0) {
mainWindow.showAndRise() mainWindow.showAndRise();
} }
if (u) { if (u) {
if (c === 1 && (u.state === EUserState.SignedOut)) { if (c === 1 && (u.state === EUserState.SignedOut)) {
mainWindow.showAndRise() mainWindow.showAndRise();
} }
} }
Backend.guiReady();
Backend.guiReady()
if (Backend.showOnStartup || Backend.showSplashScreen) { if (Backend.showOnStartup || Backend.showSplashScreen) {
mainWindow.showAndRise() mainWindow.showAndRise();
} }
}
function setColorScheme() {
if (Backend.colorSchemeName === "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
if (Backend.colorSchemeName === "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
} }
} }

View File

@ -1,202 +1,175 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
SettingsView { SettingsView {
id: root id: root
fillHeight: true
property var selectedAddress property var selectedAddress
signal bugReportWasSent() signal bugReportWasSent
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 { Label {
text: qsTr("Report a problem")
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Report a problem")
type: Label.Heading type: Label.Heading
} }
TextArea { TextArea {
id: description id: description
property int _minLength: 150
property int _maxLength: 800 property int _maxLength: 800
property int _minLength: 150
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()
}
}
KeyNavigation.priority: KeyNavigation.BeforeItem KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: address 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 // set implicitHeight to explicit height because se don't
// want TextArea implicitHeight (which is height of all text) // want TextArea implicitHeight (which is height of all text)
// to be considered in SettingsView internal scroll view // to be considered in SettingsView internal scroll view
implicitHeight: height 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 { TextField {
id: address id: address
label: qsTr("Your contact email")
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your contact email")
placeholderText: qsTr("e.g. jane.doe@protonmail.com") placeholderText: qsTr("e.g. jane.doe@protonmail.com")
validator: function (str) { validator: function (str) {
if (!isValidEmail(str)) { if (!isValidEmail(str)) {
return qsTr("Enter valid email address") return qsTr("Enter valid email address");
} }
return return;
} }
} }
TextField { TextField {
id: emailClient id: emailClient
label: qsTr("Your email client (including version)")
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your email client (including version)")
placeholderText: qsTr("e.g. Apple Mail 14.0") placeholderText: qsTr("e.g. Apple Mail 14.0")
validator: function (str) { validator: function (str) {
if (str.length === 0) { 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 { RowLayout {
CheckBox { CheckBox {
id: includeLogs id: includeLogs
text: qsTr("Include my recent logs")
colorScheme: root.colorScheme
checked: true checked: true
colorScheme: root.colorScheme
text: qsTr("Include my recent logs")
} }
Button { Button {
Layout.leftMargin: 12 Layout.leftMargin: 12
text: qsTr("View logs")
secondary: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
secondary: true
text: qsTr("View logs")
onClicked: Qt.openUrlExternally(Backend.logsPath) onClicked: Qt.openUrlExternally(Backend.logsPath)
} }
} }
TextEdit { TextEdit {
text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
readOnly: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
font.family: ProtonStyle.font_family font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWeight_400
font.pixelSize: ProtonStyle.caption_font_size
font.letterSpacing: ProtonStyle.caption_letter_spacing 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 // No way to set lineHeight: ProtonStyle.caption_line_height
selectionColor: root.colorScheme.interaction_norm 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 wrapMode: Text.WordWrap
selectByMouse: true
} }
Button { Button {
id: sendButton id: sendButton
text: qsTr("Send")
colorScheme: root.colorScheme colorScheme: root.colorScheme
enabled: !loading enabled: !loading
text: qsTr("Send")
onClicked: { onClicked: {
description.validate() description.validate();
address.validate() address.validate();
emailClient.validate() emailClient.validate();
if (description.error || address.error || emailClient.error) { if (description.error || address.error || emailClient.error) {
return return;
} }
submit();
submit()
} }
Connections { Connections {
function onBugReportSendSuccess() {
root.bugReportWasSent();
}
function onReportBugFinished() {
sendButton.loading = false;
}
target: Backend 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()
}
} }

View File

@ -1,71 +1,80 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
Rectangle { Rectangle {
id: root id: root
property int _margin: 24
property ColorScheme colorScheme property ColorScheme colorScheme
property string title
property string hostname property string hostname
property string port
property string username
property string password property string password
property string port
property string security property string security
property string title
implicitWidth: 304 property string username
implicitHeight: content.height + 2*root._margin
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
implicitHeight: content.height + 2 * root._margin
implicitWidth: 304
radius: ProtonStyle.card_radius radius: ProtonStyle.card_radius
property int _margin: 24
ColumnLayout { ColumnLayout {
id: content id: content
spacing: 12
width: root.width - 2 * root._margin width: root.width - 2 * root._margin
anchors { anchors {
top: root.top bottomMargin: root._margin
left: root.left left: root.left
leftMargin: root._margin leftMargin: root._margin
rightMargin: root._margin rightMargin: root._margin
top: root.top
topMargin: root._margin topMargin: root._margin
bottomMargin : root._margin
} }
spacing: 12
Label { Label {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: root.title text: root.title
type: Label.Body_semibold type: Label.Body_semibold
} }
ConfigurationItem {
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname } colorScheme: root.colorScheme
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port } label: qsTr("Hostname")
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username } value: root.hostname
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("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
}
} }
} }

View File

@ -1,35 +1,29 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
Item { Item {
id: root id: root
Layout.fillWidth: true
property var colorScheme property var colorScheme
property string label property string label
property string value property string value
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
@ -47,45 +41,42 @@ Item {
} }
TextEdit { TextEdit {
id: valueText id: valueText
text: root.value Layout.fillWidth: true
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
readOnly: true readOnly: true
selectByMouse: true
selectByKeyboard: true selectByKeyboard: true
selectByMouse: true
selectionColor: root.colorScheme.text_weak selectionColor: root.colorScheme.text_weak
text: root.value
wrapMode: Text.WrapAnywhere wrapMode: Text.WrapAnywhere
Layout.fillWidth: true
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ColorImage { ColorImage {
source: "/qml/icons/ic-copy.svg"
color: root.colorScheme.text_norm color: root.colorScheme.text_norm
height: root.colorScheme.body_font_size height: root.colorScheme.body_font_size
source: "/qml/icons/ic-copy.svg"
sourceSize.height: root.colorScheme.body_font_size sourceSize.height: root.colorScheme.body_font_size
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
valueText.select(0, valueText.length) valueText.select(0, valueText.length);
valueText.copy() valueText.copy();
valueText.deselect() valueText.deselect();
} }
onPressed: parent.scale = 0.90 onPressed: parent.scale = 0.90
onReleased: parent.scale = 1 onReleased: parent.scale = 1
} }
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1
color: root.colorScheme.border_norm color: root.colorScheme.border_norm
height: 1
} }
} }
} }

View File

@ -1,155 +1,138 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
SettingsView { SettingsView {
id: root 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 fillHeight: false
onVisibleChanged: {
root.setDefaultValues();
}
Label { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Connection mode") text: qsTr("Connection mode")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
Label { Label {
Layout.fillWidth: true
color: root.colorScheme.text_weak
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.") text: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.")
type: Label.Body type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
ButtonGroup{ id: imapProtocolSelection } ButtonGroup {
id: imapProtocolSelection
}
Label { Label {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("IMAP connection") text: qsTr("IMAP connection")
} }
RadioButton { RadioButton {
id: imapSSLButton id: imapSSLButton
colorScheme: root.colorScheme
ButtonGroup.group: imapProtocolSelection ButtonGroup.group: imapProtocolSelection
colorScheme: root.colorScheme
text: qsTr("SSL") text: qsTr("SSL")
} }
RadioButton { RadioButton {
id: imapSTARTTLSButton id: imapSTARTTLSButton
colorScheme: root.colorScheme
ButtonGroup.group: imapProtocolSelection ButtonGroup.group: imapProtocolSelection
colorScheme: root.colorScheme
text: qsTr("STARTTLS") text: qsTr("STARTTLS")
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
height: 1
} }
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
ButtonGroup{ id: smtpProtocolSelection } ButtonGroup {
id: smtpProtocolSelection
}
Label { Label {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("SMTP connection") text: qsTr("SMTP connection")
} }
RadioButton { RadioButton {
id: smtpSSLButton id: smtpSSLButton
colorScheme: root.colorScheme
ButtonGroup.group: smtpProtocolSelection ButtonGroup.group: smtpProtocolSelection
colorScheme: root.colorScheme
text: qsTr("SSL") text: qsTr("SSL")
} }
RadioButton { RadioButton {
id: smtpSTARTTLSButton id: smtpSTARTTLSButton
colorScheme: root.colorScheme
ButtonGroup.group: smtpProtocolSelection ButtonGroup.group: smtpProtocolSelection
colorScheme: root.colorScheme
text: qsTr("STARTTLS") text: qsTr("STARTTLS")
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
height: 1
} }
RowLayout { RowLayout {
spacing: 12 spacing: 12
Button { Button {
id: submitButton id: submitButton
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Save")
onClicked: {
submitButton.loading = true
root.submit()
}
enabled: (!loading) && ((imapSSLButton.checked !== Backend.useSSLForIMAP) || (smtpSSLButton.checked !== Backend.useSSLForSMTP)) enabled: (!loading) && ((imapSSLButton.checked !== Backend.useSSLForIMAP) || (smtpSSLButton.checked !== Backend.useSSLForSMTP))
} text: qsTr("Save")
onClicked: {
submitButton.loading = true;
root.submit();
}
}
Button { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Cancel")
onClicked: root.back()
secondary: true secondary: true
} text: qsTr("Cancel")
onClicked: root.back()
}
Connections { Connections {
target: Backend
function onChangeMailServerSettingsFinished() { function onChangeMailServerSettingsFinished() {
submitButton.loading = false submitButton.loading = false;
root.back() root.back();
}
}
} }
function submit(){ target: Backend
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()
} }
} }

View File

@ -1,36 +1,62 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
Item { Item {
id: root id: root
property ColorScheme colorScheme
property ColorScheme colorScheme
property var notifications property var notifications
signal closeWindow
signal quitBridge
signal showSetupGuide(var user, string address) 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 { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -38,13 +64,13 @@ Item {
Rectangle { Rectangle {
id: leftBar id: leftBar
property ColorScheme colorScheme: root.colorScheme.prominent property ColorScheme colorScheme: root.colorScheme.prominent
Layout.minimumWidth: 264
Layout.maximumWidth: 320
Layout.preferredWidth: 320
Layout.fillHeight: true Layout.fillHeight: true
Layout.maximumWidth: 320
Layout.minimumWidth: 264
Layout.preferredWidth: 320
color: colorScheme.background_norm color: colorScheme.background_norm
ColumnLayout { ColumnLayout {
@ -53,23 +79,20 @@ Item {
RowLayout { RowLayout {
id: topLeftBar id: topLeftBar
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 60
Layout.maximumHeight: 60 Layout.maximumHeight: 60
Layout.minimumHeight: 60
Layout.preferredHeight: 60 Layout.preferredHeight: 60
spacing: 0 spacing: 0
Status { Status {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 17
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.topMargin: 24 Layout.topMargin: 24
Layout.bottomMargin: 17
Layout.alignment: Qt.AlignHCenter
colorScheme: leftBar.colorScheme colorScheme: leftBar.colorScheme
notifications: root.notifications
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate
notifications: root.notifications
} }
// just a placeholder // just a placeholder
@ -77,47 +100,38 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { 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.bottomMargin: 9
Layout.maximumHeight: 36
Layout.maximumWidth: 36
Layout.minimumHeight: 36
Layout.minimumWidth: 36
Layout.preferredHeight: 36
Layout.preferredWidth: 36
Layout.rightMargin: 4 Layout.rightMargin: 4
Layout.topMargin: 16
colorScheme: leftBar.colorScheme
horizontalPadding: 0 horizontalPadding: 0
icon.source: "/qml/icons/ic-question-circle.svg" icon.source: "/qml/icons/ic-question-circle.svg"
onClicked: rightContent.showHelpView() onClicked: rightContent.showHelpView()
} }
Button { 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.bottomMargin: 9
Layout.maximumHeight: 36
Layout.maximumWidth: 36
Layout.minimumHeight: 36
Layout.minimumWidth: 36
Layout.preferredHeight: 36
Layout.preferredWidth: 36
Layout.rightMargin: 4 Layout.rightMargin: 4
Layout.topMargin: 16
colorScheme: leftBar.colorScheme
horizontalPadding: 0 horizontalPadding: 0
icon.source: "/qml/icons/ic-cog-wheel.svg" icon.source: "/qml/icons/ic-cog-wheel.svg"
onClicked: rightContent.showGeneralSettings() onClicked: rightContent.showGeneralSettings()
} }
Button { Button {
id: dotMenuButton id: dotMenuButton
Layout.bottomMargin: 9 Layout.bottomMargin: 9
@ -134,7 +148,7 @@ Item {
icon.source: "/qml/icons/ic-three-dots-vertical.svg" icon.source: "/qml/icons/ic-three-dots-vertical.svg"
onClicked: { onClicked: {
dotMenu.open() dotMenu.open();
} }
Menu { Menu {
@ -143,332 +157,319 @@ Item {
modal: true modal: true
y: dotMenuButton.Layout.preferredHeight + dotMenuButton.Layout.bottomMargin y: dotMenuButton.Layout.preferredHeight + dotMenuButton.Layout.bottomMargin
onClosed: {
parent.checked = false;
}
onOpened: {
parent.checked = true;
}
MenuItem { MenuItem {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Close window") text: qsTr("Close window")
onClicked: { onClicked: {
root.closeWindow() root.closeWindow();
} }
} }
MenuItem { MenuItem {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Quit Bridge") text: qsTr("Quit Bridge")
onClicked: { onClicked: {
root.quitBridge() root.quitBridge();
}
}
onClosed: {
parent.checked = false
}
onOpened: {
parent.checked = true
} }
} }
} }
} }
}
Item {implicitHeight:10} Item {
implicitHeight: 10
}
// Separator line // Separator line
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 1
Layout.maximumHeight: 1 Layout.maximumHeight: 1
Layout.minimumHeight: 1
color: leftBar.colorScheme.border_weak color: leftBar.colorScheme.border_weak
} }
ListView { ListView {
id: accounts id: accounts
property var _topBottomMargins: 24
property var _leftRightMargins: 16 property var _leftRightMargins: 16
property var _topBottomMargins: 24
Layout.fillWidth: true Layout.bottomMargin: accounts._topBottomMargins
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: accounts._leftRightMargins Layout.leftMargin: accounts._leftRightMargins
Layout.rightMargin: accounts._leftRightMargins Layout.rightMargin: accounts._leftRightMargins
Layout.topMargin: accounts._topBottomMargins Layout.topMargin: accounts._topBottomMargins
Layout.bottomMargin: accounts._topBottomMargins
spacing: 12
clip: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true
model: Backend.users
spacing: 12
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 { header: Rectangle {
height: headerLabel.height + 16 height: headerLabel.height + 16
// color: ProtonStyle.transparent // color: ProtonStyle.transparent
Label { Label {
colorScheme: leftBar.colorScheme
id: headerLabel id: headerLabel
colorScheme: leftBar.colorScheme
text: qsTr("Accounts") text: qsTr("Accounts")
type: Label.LabelType.Body type: Label.LabelType.Body
} }
} }
highlight: Rectangle { highlight: Rectangle {
color: leftBar.colorScheme.interaction_default_active color: leftBar.colorScheme.interaction_default_active
radius: ProtonStyle.account_row_radius 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 // Separator
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 1
Layout.maximumHeight: 1 Layout.maximumHeight: 1
Layout.minimumHeight: 1
color: leftBar.colorScheme.border_weak color: leftBar.colorScheme.border_weak
} }
Item { Item {
id: bottomLeftBar id: bottomLeftBar
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 52
Layout.maximumHeight: 52 Layout.maximumHeight: 52
Layout.minimumHeight: 52
Layout.preferredHeight: 52 Layout.preferredHeight: 52
Button { Button {
colorScheme: leftBar.colorScheme
width: 36
height: 36
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.top: parent.top
anchors.topMargin: 7 anchors.topMargin: 7
colorScheme: leftBar.colorScheme
height: 36
horizontalPadding: 0 horizontalPadding: 0
icon.source: "/qml/icons/ic-plus.svg" icon.source: "/qml/icons/ic-plus.svg"
width: 36
onClicked: { onClicked: {
signIn.username = "" signIn.username = "";
rightContent.showSignIn() rightContent.showSignIn();
} }
} }
} }
} }
} }
Rectangle {
Rectangle { // right content background Layout.fillHeight: true // right content background
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
color: colorScheme.background_norm color: colorScheme.background_norm
StackLayout { StackLayout {
id: rightContent 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 anchors.fill: parent
AccountView { // 0 AccountView {
// 0
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications notifications: root.notifications
user: { user: {
if (accounts.currentIndex < 0) return undefined if (accounts.currentIndex < 0)
if (Backend.users.count == 0) return undefined return undefined;
return Backend.users.get(accounts.currentIndex) if (Backend.users.count === 0)
} return undefined;
onShowSignIn: { return Backend.users.get(accounts.currentIndex);
var user = this.user
signIn.username = user ? user.primaryEmailOrUsername() : ""
rightContent.showSignIn()
}
onShowSetupGuide: function(user, address) {
root.showSetupGuide(user,address)
}
} }
GridLayout { // 1 Sign In onShowSetupGuide: function (user, address) {
root.showSetupGuide(user, address);
}
onShowSignIn: {
const user = this.user;
signIn.username = user ? user.primaryEmailOrUsername() : "";
rightContent.showSignIn();
}
}
GridLayout {
// 1 Sign In
columns: 2 columns: 2
Button { Button {
id: backButton id: backButton
Layout.alignment: Qt.AlignTop
Layout.leftMargin: 18 Layout.leftMargin: 18
Layout.topMargin: 10 Layout.topMargin: 10
Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme colorScheme: root.colorScheme
onClicked: { horizontalPadding: 8
signIn.abort()
rightContent.showAccount()
}
icon.source: "/qml/icons/ic-arrow-left.svg" icon.source: "/qml/icons/ic-arrow-left.svg"
secondary: true secondary: true
horizontalPadding: 8
}
onClicked: {
signIn.abort();
rightContent.showAccount();
}
}
SignIn { SignIn {
id: signIn id: signIn
Layout.topMargin: 68
Layout.leftMargin: 80 - backButton.width - 18
Layout.rightMargin: 80
Layout.bottomMargin: 68 Layout.bottomMargin: 68
Layout.preferredWidth: 320
Layout.fillWidth: true
Layout.fillHeight: 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 colorScheme: root.colorScheme
} }
} }
GeneralSettings {
GeneralSettings { // 2 // 2
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications notifications: root.notifications
onBack: { onBack: {
rightContent.showAccount() rightContent.showAccount();
} }
} }
KeychainSettings {
KeychainSettings { // 3 // 3
colorScheme: root.colorScheme colorScheme: root.colorScheme
onBack: { onBack: {
rightContent.showGeneralSettings() rightContent.showGeneralSettings();
} }
} }
PortSettings {
PortSettings { // 4 // 4
colorScheme: root.colorScheme
notifications: root.notifications
onBack: {
rightContent.showGeneralSettings()
}
}
ConnectionModeSettings { // 5
colorScheme: root.colorScheme
onBack: {
rightContent.showGeneralSettings()
}
}
LocalCacheSettings { // 6
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications notifications: root.notifications
onBack: { onBack: {
rightContent.showGeneralSettings() rightContent.showGeneralSettings();
} }
} }
ConnectionModeSettings {
HelpView { // 7 // 5
colorScheme: root.colorScheme colorScheme: root.colorScheme
onBack: { 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 id: bugReport
colorScheme: root.colorScheme colorScheme: root.colorScheme
selectedAddress: { selectedAddress: {
if (accounts.currentIndex < 0) return "" if (accounts.currentIndex < 0)
if (Backend.users.count == 0) return "" return "";
var user = Backend.users.get(accounts.currentIndex) if (Backend.users.count === 0)
if (!user) return "" return "";
return user.addresses[0] const user = Backend.users.get(accounts.currentIndex);
if (!user)
return "";
return user.addresses[0];
} }
onBack: { onBack: {
rightContent.showHelpView() rightContent.showHelpView();
} }
onBugReportWasSent: { 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 { Connections {
function onLoginAlreadyLoggedIn(index) {
rightContent.showAccount(index);
}
function onLoginFinished(index) {
rightContent.showAccount(index);
}
target: Backend target: Backend
function onLoginFinished(index) { rightContent.showAccount(index) }
function onLoginAlreadyLoggedIn(index) { rightContent.showAccount(index) }
} }
} }
} }
} }
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)
}
} }

View File

@ -1,54 +1,45 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import "." import "."
import "./Proton" import "Proton"
Rectangle { Rectangle {
property var target: parent property var target: parent
x: target.x
y: target.y
width: target.width
height: target.height
color: "transparent"
border.color: "red" border.color: "red"
border.width: 1 border.width: 1
color: "transparent"
height: target.height
width: target.width
x: target.x
y: target.y
//z: parent.z - 1 //z: parent.z - 1
z: 10000000 z: 10000000
Label { Label {
text: parent.width + "x" + parent.height
anchors.centerIn: parent anchors.centerIn: parent
color: "black" color: "black"
colorScheme: ProtonStyle.currentStyle colorScheme: ProtonStyle.currentStyle
text: parent.width + "x" + parent.height
} }
Rectangle { Rectangle {
width: target.implicitWidth
height: target.implicitHeight
color: "transparent"
border.color: "green" border.color: "green"
border.width: 1 border.width: 1
color: "transparent"
height: target.implicitHeight
width: target.implicitWidth
//z: parent.z - 1 //z: parent.z - 1
z: 10000000 z: 10000000
} }

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
SettingsView { SettingsView {
@ -31,144 +25,138 @@ SettingsView {
fillHeight: false fillHeight: false
Label { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Settings") text: qsTr("Settings")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
SettingsItem { SettingsItem {
id: autoUpdate 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 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 { SettingsItem {
id: autostart 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 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 { SettingsItem {
id: beta id: beta
colorScheme: root.colorScheme Layout.fillWidth: true
text: qsTr("Beta access")
description: qsTr("Be among the first to try new features.")
type: SettingsItem.Toggle
checked: Backend.isBetaEnabled checked: Backend.isBetaEnabled
colorScheme: root.colorScheme
description: qsTr("Be among the first to try new features.")
text: qsTr("Beta access")
type: SettingsItem.Toggle
onClicked: { onClicked: {
if (!beta.checked) { if (!beta.checked) {
root.notifications.askEnableBeta() root.notifications.askEnableBeta();
} else { } else {
Backend.toggleBeta(false) Backend.toggleBeta(false);
} }
} }
Layout.fillWidth: true
} }
RowLayout { RowLayout {
ColorImage { ColorImage {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
source: root._isAdvancedShown ? "/qml/icons/ic-chevron-down.svg" : "/qml/icons/ic-chevron-right.svg"
color: root.colorScheme.interaction_norm color: root.colorScheme.interaction_norm
height: root.colorScheme.body_font_size 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 sourceSize.height: root.colorScheme.body_font_size
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: root._isAdvancedShown = !root._isAdvancedShown onClicked: root._isAdvancedShown = !root._isAdvancedShown
} }
} }
Label { Label {
id: advSettLabel id: advSettLabel
color: root.colorScheme.interaction_norm
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Advanced settings") text: qsTr("Advanced settings")
color: root.colorScheme.interaction_norm
type: Label.Body type: Label.Body
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: root._isAdvancedShown = !root._isAdvancedShown onClicked: root._isAdvancedShown = !root._isAdvancedShown
} }
} }
} }
SettingsItem { SettingsItem {
id: keychains 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 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 { SettingsItem {
id: doh id: doh
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Alternative routing")
description: qsTr("If Protons 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 Layout.fillWidth: true
} checked: Backend.isDoHEnabled
colorScheme: root.colorScheme
description: qsTr("If Protons 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 { SettingsItem {
id: darkMode 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 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 { SettingsItem {
id: allMail 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 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 { SettingsItem {
id: telemetry id: telemetry
Layout.fillWidth: true Layout.fillWidth: true
@ -181,73 +169,68 @@ SettingsView {
onClicked: Backend.toggleIsTelemetryDisabled(telemetry.checked) onClicked: Backend.toggleIsTelemetryDisabled(telemetry.checked)
} }
SettingsItem { SettingsItem {
id: ports 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 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 { SettingsItem {
id: imap 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 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 { SettingsItem {
id: cache 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 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 { SettingsItem {
id: exportTLSCertificates 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 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 { SettingsItem {
id: reset 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 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();
}
} }
} }

View File

@ -1,126 +1,110 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
SettingsView { SettingsView {
id: root id: root
fillHeight: true fillHeight: true
Label { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Help") text: qsTr("Help")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
SettingsItem { SettingsItem {
id: setupPage id: setupPage
colorScheme: root.colorScheme Layout.fillWidth: true
text: qsTr("Installation and setup")
actionText: qsTr("Go to help topics")
actionIcon: "/qml/icons/ic-external-link.svg" 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.") description: qsTr("Get help setting up your client with our instructions and FAQs.")
text: qsTr("Installation and setup")
type: SettingsItem.PrimaryButton type: SettingsItem.PrimaryButton
onClicked: { onClicked: {
Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); Backend.notifyKBArticleClicked("https://proton.me/support/bridge");
Qt.openUrlExternally("https://proton.me/support/bridge")} Qt.openUrlExternally("https://proton.me/support/bridge");
}
Layout.fillWidth: true
} }
SettingsItem { SettingsItem {
id: checkUpdates id: checkUpdates
colorScheme: root.colorScheme Layout.fillWidth: true
text: qsTr("Updates")
actionText: qsTr("Check now") 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.") 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 type: SettingsItem.Button
onClicked: { onClicked: {
checkUpdates.loading = true checkUpdates.loading = true;
Backend.checkUpdates() Backend.checkUpdates();
} }
Connections { Connections {
function onCheckUpdatesFinished() {
checkUpdates.loading = false;
}
target: Backend target: Backend
function onCheckUpdatesFinished() { checkUpdates.loading = false }
} }
Layout.fillWidth: true
} }
SettingsItem { SettingsItem {
id: logs 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 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 { SettingsItem {
id: reportBug 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 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 { Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
colorScheme: root.colorScheme
type: Label.Caption
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
textFormat: Text.StyledText colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("%1 v%2 (%3)<br>© 2017-%4 %5<br>%6 %7<br>%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)<br>© 2017-%4 %5<br>%6 %7<br>%8"). onLinkActivated: function (link) {
arg(Backend.appname). Qt.openUrlExternally(link);
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) }
} }
} }

View File

@ -1,116 +1,105 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
SettingsView { SettingsView {
id: root id: root
property bool _valuesChanged: keychainSelection.checkedButton && keychainSelection.checkedButton.text !== Backend.currentKeychain
function setDefaultValues() {
for (const bi in keychainSelection.buttons) {
const button = keychainSelection.buttons[bi];
if (button.text === Backend.currentKeychain) {
button.checked = true;
break;
}
}
}
fillHeight: false fillHeight: false
property bool _valuesChanged: keychainSelection.checkedButton && keychainSelection.checkedButton.text != Backend.currentKeychain Component.onCompleted: root.setDefaultValues()
onBack: {
root.setDefaultValues();
}
Label { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Default keychain") text: qsTr("Default keychain")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
Label { Label {
Layout.fillWidth: true
color: root.colorScheme.text_weak
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Change which keychain Bridge uses as default") text: qsTr("Change which keychain Bridge uses as default")
type: Label.Body type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
ButtonGroup{ id: keychainSelection } ButtonGroup {
id: keychainSelection
}
Repeater { Repeater {
model: Backend.availableKeychain model: Backend.availableKeychain
RadioButton { RadioButton {
colorScheme: root.colorScheme
ButtonGroup.group: keychainSelection ButtonGroup.group: keychainSelection
colorScheme: root.colorScheme
text: modelData text: modelData
} }
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
height: 1
} }
RowLayout { RowLayout {
spacing: 12 spacing: 12
Button { Button {
id: submitButton id: submitButton
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Save and restart")
enabled: root._valuesChanged enabled: root._valuesChanged
onClicked: { text: qsTr("Save and restart")
Backend.changeKeychain(keychainSelection.checkedButton.text)
}
}
onClicked: {
Backend.changeKeychain(keychainSelection.checkedButton.text);
}
}
Button { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Cancel")
onClicked: root.back()
secondary: true secondary: true
} text: qsTr("Cancel")
onClicked: root.back()
}
Connections { Connections {
target: Backend
function onChangeKeychainFinished() { function onChangeKeychainFinished() {
submitButton.loading = false submitButton.loading = false;
root.back() root.back();
}
}
} }
onBack: { target: Backend
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()
}

View File

@ -1,81 +1,88 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Dialogs import QtQuick.Dialogs
import Proton import Proton
SettingsView { SettingsView {
id: root id: root
fillHeight: false
property var notifications
property url diskCachePath: pathDialog.shortcuts.home property url diskCachePath: pathDialog.shortcuts.home
property var notifications
function refresh() { function refresh() {
diskCacheSetting.description = Backend.nativePath(root.diskCachePath) diskCacheSetting.description = Backend.nativePath(root.diskCachePath);
submitButton.enabled = (!submitButton.loading) && !Backend.areSameFileOrFolder(Backend.diskCachePath, 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 { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Local cache") text: qsTr("Local cache")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
Label { Label {
Layout.fillWidth: true
color: root.colorScheme.text_weak
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Bridge stores your encrypted messages locally to optimize communication with your client.") text: qsTr("Bridge stores your encrypted messages locally to optimize communication with your client.")
type: Label.Body type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
SettingsItem { SettingsItem {
id: diskCacheSetting 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 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 { FolderDialog {
id: pathDialog id: pathDialog
title: qsTr("Select cache location")
currentFolder: root.diskCachePath currentFolder: root.diskCachePath
onAccepted: { title: qsTr("Select cache location")
root.diskCachePath = pathDialog.selectedFolder
root.refresh()
}
}
}
onAccepted: {
root.diskCachePath = pathDialog.selectedFolder;
root.refresh();
}
}
}
RowLayout { RowLayout {
spacing: 12 spacing: 12
@ -83,43 +90,25 @@ SettingsView {
id: submitButton id: submitButton
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Save") text: qsTr("Save")
onClicked: {
root.submit()
}
}
onClicked: {
root.submit();
}
}
Button { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Cancel")
onClicked: root.back()
secondary: true secondary: true
} text: qsTr("Cancel")
onClicked: root.back()
}
Connections { Connections {
target: Backend
function onDiskCachePathChangeFinished() { function onDiskCachePathChangeFinished() {
submitButton.loading = false submitButton.loading = false;
root.setDefaultValues() root.setDefaultValues();
}
}
} }
onBack: { target: Backend
root.setDefaultValues() }
}
function submit() {
submitButton.loading = true
Backend.setDiskCachePath(root.diskCachePath)
}
function setDefaultValues(){
root.diskCachePath = Backend.diskCachePath
root.refresh();
}
onVisibleChanged: {
root.setDefaultValues()
} }
} }

View File

@ -1,232 +1,202 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
ApplicationWindow { ApplicationWindow {
id: root id: root
colorScheme: ProtonStyle.currentStyle
visible: true
property int _defaultWidth: 1080
property int _defaultHeight: 780 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 height: _defaultHeight
minimumWidth: _defaultWidth minimumWidth: _defaultWidth
visible: true
property var notifications width: _defaultWidth
// show Setup Guide on every new user // show Setup Guide on every new user
Connections { 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) { function onRowsAboutToBeRemoved(parent, first, last) {
for (var i = first; i <= last; i++ ) { for (let i = first; i <= last; i++) {
var user = Backend.users.get(i) const user = Backend.users.get(i);
if (setupGuide.user === user) { if (setupGuide.user === user) {
setupGuide.user = null setupGuide.user = null;
contentLayout._showSetup = false contentLayout._showSetup = false;
return 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 { Connections {
target: Backend
function onShowMainWindow() {
root.showAndRise()
}
function onLoginFinished(index, wasSignedOut) { function onLoginFinished(index, wasSignedOut) {
var user = Backend.users.get(index) const user = Backend.users.get(index);
if (user && !wasSignedOut) { 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) { function onSelectUser(userID, forceShowWindow) {
contentWrapper.selectUser(userID) contentWrapper.selectUser(userID);
if (forceShowWindow) { 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 { StackLayout {
id: contentLayout id: contentLayout
anchors.fill: parent
property bool _showSetup: false property bool _showSetup: false
anchors.fill: parent
currentIndex: { currentIndex: {
// show welcome when there are no users // show welcome when there are no users
if (Backend.users.count === 0) { if (Backend.users.count === 0) {
return 1 return 1;
} }
const u = Backend.users.get(0);
var u = Backend.users.get(0)
if (!u) { if (!u) {
console.trace() console.trace();
console.log("empty user") console.log("empty user");
return 1 return 1;
} }
if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) { if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) {
showSignIn(u.primaryEmailOrUsername()) showSignIn(u.primaryEmailOrUsername());
return 0 return 0;
} }
if (contentLayout._showSetup) { if (contentLayout._showSetup) {
return 2 return 2;
}
return 0;
} }
return 0 ContentWrapper {
} // 0
ContentWrapper { // 0
id: contentWrapper id: contentWrapper
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications notifications: root.notifications
Layout.fillHeight: true
Layout.fillWidth: true
onShowSetupGuide: function(user, address) {
root.showSetup(user,address)
}
onCloseWindow: { onCloseWindow: {
root.close() root.close();
} }
onQuitBridge: { onQuitBridge: {
// If we ever want to add a confirmation dialog before quitting: // 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.notifications.askQuestion("Quit Bridge", "Insert warning message here.", "Quit", "Cancel", Backend.quit, null)
root.close() root.close();
Backend.quit() Backend.quit();
}
onShowSetupGuide: function (user, address) {
root.showSetup(user, address);
} }
} }
WelcomeGuide {
WelcomeGuide { // 1
colorScheme: root.colorScheme
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true // 1
colorScheme: root.colorScheme
} }
SetupGuide {
SetupGuide { // 2 // 2
id: setupGuide id: setupGuide
colorScheme: root.colorScheme
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
onDismissed: { onDismissed: {
root.showSetup(null,"") root.showSetup(null, "");
} }
onFinished: { onFinished: {
// TODO: Do not close window. Trigger Backend to check that // TODO: Do not close window. Trigger Backend to check that
// there is a successfully connected client. Then Backend // there is a successfully connected client. Then Backend
// should send another signal to close the setup guide. // should send another signal to close the setup guide.
root.showSetup(null,"") root.showSetup(null, "");
} }
} }
} }
NotificationPopups { NotificationPopups {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications
mainWindow: root mainWindow: root
notifications: root.notifications
} }
SplashScreen { SplashScreen {
id: splashScreen id: splashScreen
colorScheme: root.colorScheme 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()
}
}
} }

View File

@ -1,118 +1,99 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
Dialog { Dialog {
id: root id: root
default property alias data: additionalChildrenContainer.children
property var notification property var notification
shouldShow: notification && notification.active && !notification.dismissed
modal: true modal: true
shouldShow: notification && notification.active && !notification.dismissed
default property alias data: additionalChildrenContainer.children
ColumnLayout { ColumnLayout {
spacing: 0 spacing: 0
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16
sourceSize.width: 64
sourceSize.height: 64
Layout.preferredHeight: 64 Layout.preferredHeight: 64
Layout.preferredWidth: 64 Layout.preferredWidth: 64
Layout.bottomMargin: 16
visible: source != ""
source: { source: {
if (!root.notification) { if (!root.notification) {
return "" return "";
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return "/qml/icons/ic-info.svg" return "/qml/icons/ic-info.svg";
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return "/qml/icons/ic-success.svg" return "/qml/icons/ic-success.svg";
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return "/qml/icons/ic-alert.svg" return "/qml/icons/ic-alert.svg";
} }
} }
sourceSize.height: 64
sourceSize.width: 64
visible: source != ""
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
Layout.bottomMargin: 8 Layout.bottomMargin: 8
colorScheme: root.colorScheme colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: root.notification.title text: root.notification.title
type: Label.LabelType.Title type: Label.LabelType.Title
} }
Label { Label {
Layout.bottomMargin: 16
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: 240 Layout.preferredWidth: 240
Layout.bottomMargin: 16
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: root.notification.description
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: root.notification.description
type: Label.LabelType.Body type: Label.LabelType.Body
onLinkActivated: function(link) { Qt.openUrlExternally(link) } wrapMode: Text.WordWrap
}
onLinkActivated: function (link) {
Qt.openUrlExternally(link);
}
}
Item { Item {
id: additionalChildrenContainer id: additionalChildrenContainer
Layout.fillWidth: true
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.fillWidth: true
visible: children.length > 0
implicitHeight: additionalChildrenContainer.childrenRect.height implicitHeight: additionalChildrenContainer.childrenRect.height
implicitWidth: additionalChildrenContainer.childrenRect.width implicitWidth: additionalChildrenContainer.childrenRect.width
visible: children.length > 0
} }
ColumnLayout { ColumnLayout {
spacing: 8 spacing: 8
Repeater { Repeater {
model: root.notification.action model: root.notification.action
delegate: Button { delegate: Button {
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
action: modelData action: modelData
colorScheme: root.colorScheme
secondary: index > 0
loading: modelData.loading loading: modelData.loading
secondary: index > 0
} }
} }
} }

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
@ -27,118 +21,98 @@ Item {
id: root id: root
property ColorScheme colorScheme property ColorScheme colorScheme
property var notifications
property var mainWindow property var mainWindow
property int notificationWhitelist: NotificationFilter.FilterConsts.All
property int notificationBlacklist: NotificationFilter.FilterConsts.None property int notificationBlacklist: NotificationFilter.FilterConsts.None
property int notificationWhitelist: NotificationFilter.FilterConsts.All
property var notifications
NotificationFilter { NotificationFilter {
id: bannerNotificationFilter id: bannerNotificationFilter
source: root.notifications.all
blacklist: Notifications.Group.Dialogs blacklist: Notifications.Group.Dialogs
source: root.notifications.all
} }
Banner { Banner {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: bannerNotificationFilter.topmost
mainWindow: root.mainWindow mainWindow: root.mainWindow
notification: bannerNotificationFilter.topmost
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.updateManualReady notification: root.notifications.updateManualReady
Switch { Switch {
id: autoUpdate id: autoUpdate
checked: Backend.isAutomaticUpdateOn
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Update automatically in the future") text: qsTr("Update automatically in the future")
checked: Backend.isAutomaticUpdateOn
onClicked: Backend.toggleAutomaticUpdate(autoUpdate.checked) onClicked: Backend.toggleAutomaticUpdate(autoUpdate.checked)
} }
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.updateForce notification: root.notifications.updateForce
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.updateForceError notification: root.notifications.updateForceError
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.enableBeta notification: root.notifications.enableBeta
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.cacheUnavailable notification: root.notifications.cacheUnavailable
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.cacheCantMove notification: root.notifications.cacheCantMove
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.diskFull notification: root.notifications.diskFull
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.enableSplitMode notification: root.notifications.enableSplitMode
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.resetBridge notification: root.notifications.resetBridge
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.changeAllMailVisibility notification: root.notifications.changeAllMailVisibility
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.deleteAccount notification: root.notifications.deleteAccount
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.noKeychain notification: root.notifications.noKeychain
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.rebuildKeychain notification: root.notifications.rebuildKeychain
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.apiCertIssue notification: root.notifications.apiCertIssue
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.noActiveKeyForRecipient notification: root.notifications.noActiveKeyForRecipient
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.userBadEvent notification: root.notifications.userBadEvent
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.genericError notification: root.notifications.genericError
} }
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.genericQuestion notification: root.notifications.genericQuestion

View File

@ -1,54 +1,45 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick.Controls import QtQuick.Controls
QtObject { QtObject {
id: root id: root
default property var children
enum NotificationType { enum NotificationType {
Info = 0, Info,
Success = 1, Success,
Warning = 2, Warning,
Danger = 3 Danger
} }
property list<Action> 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 // title is used in dialogs only
property string title 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> action
property int type 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: { onActiveChanged: {
dismissed = false dismissed = false;
} }
} }

View File

@ -1,114 +1,95 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQml.Models import QtQml.Models
// contains notifications that satisfy black- and whitelist and are sorted in time-occurred order // contains notifications that satisfy black- and whitelist and are sorted in time-occurred order
ListModel { ListModel {
id: root id: root
enum FilterConsts { enum FilterConsts {
None = 0, None,
All = 255 All = 255
} }
property int whitelist: NotificationFilter.FilterConsts.All
property int blacklist: NotificationFilter.FilterConsts.None property int blacklist: NotificationFilter.FilterConsts.None
property Notification topmost
property var source
property bool componentCompleted: false property bool componentCompleted: false
Component.onCompleted: { property var source
root.componentCompleted = true property Notification topmost
root.rebuildList() property int whitelist: NotificationFilter.FilterConsts.All
}
// overriding get method to ignore any role and return directly object itself // overriding get method to ignore any role and return directly object itself
function get(row) { function get(row) {
if (row < 0 || row >= count) { 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() { function rebuildList() {
let i;
// avoid evaluation of the list before Component.onCompleted // avoid evaluation of the list before Component.onCompleted
if (!root.componentCompleted) { if (!root.componentCompleted) {
return return;
} }
for (i = 0; i < root.count; i++) {
for (var i = 0; i < root.count; i++) { root.get(i).onActiveChanged.disconnect(root.updateList);
root.get(i).onActiveChanged.disconnect( root.updateList )
} }
root.clear();
root.clear()
if (!root.source) { if (!root.source) {
return return;
} }
for (i = 0; i < root.source.length; i++) { for (i = 0; i < root.source.length; i++) {
var obj = root.source[i] const obj = root.source[i];
if (obj.group & root.blacklist) { if (obj.group & root.blacklist) {
continue continue;
} }
if (!(obj.group & root.whitelist)) { if (!(obj.group & root.whitelist)) {
continue continue;
} }
root.append({
root.append({obj}) "obj": obj
obj.onActiveChanged.connect( root.updateList ) });
obj.onActiveChanged.connect(root.updateList);
} }
} }
function updateList() { function updateList() {
var topmost = null let topmost = null;
for (let i = 0; i < root.count; i++) {
for (var i = 0; i < root.count; i++) { const obj = root.get(i);
var obj = root.get(i)
if (!obj.active) { if (!obj.active) {
continue continue;
} }
if (topmost && (topmost.type > obj.type)) { if (topmost && (topmost.type > obj.type)) {
continue continue;
} }
if (topmost && (topmost.type === obj.type) && (topmost.occurred > obj.occurred)) { if (topmost && (topmost.type === obj.type) && (topmost.occurred > obj.occurred)) {
continue continue;
}
topmost = obj;
}
root.topmost = topmost;
} }
topmost = obj Component.onCompleted: {
} root.componentCompleted = true;
root.rebuildList();
root.topmost = topmost
}
onWhitelistChanged: {
root.rebuildList()
} }
onBlacklistChanged: { onBlacklistChanged: {
root.rebuildList() root.rebuildList();
} }
onSourceChanged: { onSourceChanged: {
root.rebuildList() root.rebuildList();
}
onWhitelistChanged: {
root.rebuildList();
} }
} }

View File

@ -1,178 +1,157 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
SettingsView { SettingsView {
id: root id: root
fillHeight: false property bool _valuesChanged: (imapField.text * 1 !== Backend.imapPort || smtpField.text * 1 !== Backend.smtpPort)
property var notifications property var notifications
property bool _valuesChanged: ( function isPortFree(field) {
imapField.text*1 !== Backend.imapPort || const num = field.text * 1;
smtpField.text*1 !== Backend.smtpPort 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 { Label {
Layout.fillWidth: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Default ports") text: qsTr("Default ports")
type: Label.Heading type: Label.Heading
Layout.fillWidth: true
} }
Label { Label {
Layout.fillWidth: true
color: root.colorScheme.text_weak
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Changes require reconfiguration of your email client.") text: qsTr("Changes require reconfiguration of your email client.")
type: Label.Body type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
RowLayout { RowLayout {
spacing: 16 spacing: 16
TextField { TextField {
id: imapField id: imapField
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredWidth: 160
colorScheme: root.colorScheme colorScheme: root.colorScheme
label: qsTr("IMAP port") label: qsTr("IMAP port")
Layout.preferredWidth: 160
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
validator: root.validate validator: root.validate
} }
TextField { TextField {
id: smtpField id: smtpField
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredWidth: 160
colorScheme: root.colorScheme colorScheme: root.colorScheme
label: qsTr("SMTP port") label: qsTr("SMTP port")
Layout.preferredWidth: 160
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
validator: root.validate validator: root.validate
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
height: 1
} }
RowLayout { RowLayout {
spacing: 12 spacing: 12
Button { Button {
id: submitButton id: submitButton
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Save")
enabled: (!loading) && root._valuesChanged enabled: (!loading) && root._valuesChanged
text: qsTr("Save")
onClicked: { onClicked: {
// removing error here because we may have set it manually (port occupied) // removing error here because we may have set it manually (port occupied)
imapField.error = false imapField.error = false;
smtpField.error = false smtpField.error = false;
// checking errors separately because we want to display "same port" error only once // checking errors separately because we want to display "same port" error only once
imapField.validate() imapField.validate();
if (imapField.error) { if (imapField.error) {
return return;
} }
smtpField.validate() smtpField.validate();
if (smtpField.error) { if (smtpField.error) {
return return;
} }
submitButton.loading = true;
submitButton.loading = true
// check both ports before returning an error // check both ports before returning an error
var err = false let err = false;
err |= !isPortFree(imapField) err |= !isPortFree(imapField);
err |= !isPortFree(smtpField) err |= !isPortFree(smtpField);
if (err) { if (err) {
submitButton.loading = false submitButton.loading = false;
return return;
} }
// We turn off all port error notification. They well be restored if problems persist // We turn off all port error notification. They will be restored if problems persist
root.notifications.imapPortStartupError.active = false root.notifications.imapPortStartupError.active = false;
root.notifications.smtpPortStartupError.active = false root.notifications.smtpPortStartupError.active = false;
root.notifications.imapPortChangeError.active = false root.notifications.imapPortChangeError.active = false;
root.notifications.smtpPortChangeError.active = false root.notifications.smtpPortChangeError.active = false;
Backend.setMailServerSettings(imapField.text, smtpField.text, Backend.useSSLForIMAP, Backend.useSSLForSMTP);
Backend.setMailServerSettings(imapField.text, smtpField.text, Backend.useSSLForIMAP, Backend.useSSLForSMTP)
} }
} }
Button { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Cancel")
onClicked: root.back()
secondary: true secondary: true
} text: qsTr("Cancel")
onClicked: root.back()
}
Connections { Connections {
target: Backend
function onChangeMailServerSettingsFinished() { function onChangeMailServerSettingsFinished() {
submitButton.loading = false submitButton.loading = false;
}
}
} }
onBack: { target: Backend
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()
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Templates as T import QtQuick.Templates as T

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
@ -25,14 +20,14 @@ import QtQuick.Templates as T
T.ApplicationWindow { T.ApplicationWindow {
id: root id: root
property ColorScheme colorScheme
// popup priority based on types // popup priority based on types
enum PopupType { enum PopupType {
Banner = 0, Banner,
Dialog = 1 Dialog
} }
property ColorScheme colorScheme
// contains currently visible popup // contains currently visible popup
property var popupVisible: null property var popupVisible: null
@ -41,85 +36,61 @@ T.ApplicationWindow {
// overriding get method to ignore any role and return directly object itself // overriding get method to ignore any role and return directly object itself
function get(row) { function get(row) {
if (row < 0 || row >= count) { if (row < 0 || row >= count) {
return undefined return undefined;
} }
return data(index(row, 0), Qt.DisplayRole) 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()
} }
onRowsAboutToBeRemoved: function (parent, first, last) { onRowsAboutToBeRemoved: function (parent, first, last) {
for (var i = first; i <= last; i++ ) { for (let i = first; i <= last; i++) {
var obj = popups.get(i) const obj = popups.get(i);
obj.onShouldShowChanged.disconnect( root.processPopups ) obj.onShouldShowChanged.disconnect(root.processPopups);
// if currently visible popup was removed // if currently visible popup was removed
if (root.popupVisible === obj) { if (root.popupVisible === obj) {
root.popupVisible.visible = false root.popupVisible.visible = false;
root.popupVisible = null 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() { function processPopups() {
if ((root.popupVisible) && (!root.popupVisible.shouldShow)) { if ((root.popupVisible) && (!root.popupVisible.shouldShow)) {
root.popupVisible.visible = false root.popupVisible.visible = false;
} }
let topmost = null;
var topmost = null for (let i = 0; i < popups.count; i++) {
for (var i = 0; i < popups.count; i++) { const obj = popups.get(i);
var obj = popups.get(i)
if (obj.shouldShow === false) { if (obj.shouldShow === false) {
continue continue;
} }
if (topmost && (topmost.popupType > obj.popupType)) { if (topmost && (topmost.popupType > obj.popupType)) {
continue continue;
} }
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) { if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
continue continue;
} }
topmost = obj;
topmost = obj
} }
if (root.popupVisible !== topmost) { if (root.popupVisible !== topmost) {
if (root.popupVisible) { if (root.popupVisible) {
root.popupVisible.visible = false root.popupVisible.visible = false;
} }
root.popupVisible = topmost root.popupVisible = topmost;
} }
if (!root.popupVisible) { if (!root.popupVisible) {
return return;
}
root.popupVisible.visible = true
}
Connections {
target: root.popupVisible
function onVisibleChanged() {
if (root.popupVisible.visible) {
return
}
root.popupVisible = null
root.processPopups()
} }
root.popupVisible.visible = true;
} }
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
@ -127,8 +98,19 @@ T.ApplicationWindow {
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm color: root.colorScheme.backdrop_norm
} }
Overlay.modeless: Rectangle { Overlay.modeless: Rectangle {
color: "transparent" color: "transparent"
} }
Connections {
function onVisibleChanged() {
if (root.popupVisible.visible) {
return;
}
root.popupVisible = null;
root.processPopups();
}
target: root.popupVisible
}
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
@ -23,212 +18,169 @@ import QtQuick.Layouts
import "." as Proton import "." as Proton
T.Button { 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 id: control
implicitWidth: Math.max( property bool borderless: false
implicitBackgroundWidth + leftInset + rightInset, property ColorScheme colorScheme
implicitContentWidth + leftPadding + rightPadding readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0)
) readonly property bool isIcon: control.text === ""
implicitHeight: Math.max( property int labelType: Proton.Label.LabelType.Body
implicitBackgroundHeight + topInset + bottomInset, property bool loading: false
implicitContentHeight + topPadding + bottomPadding readonly property bool primary: !secondary
) property alias secondary: control.flat
property alias textHorizontalAlignment: label.horizontalAlignment
padding: 8 property alias textVerticalAlignment: label.verticalAlignment
horizontalPadding: 16
spacing: 10
font: label.font font: label.font
horizontalPadding: 16
icon.width: 16
icon.height: 16
icon.color: { icon.color: {
if (primary && !isIcon) { if (primary && !isIcon) {
return "#FFFFFF" return "#FFFFFF";
} else { } 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 { contentItem: RowLayout {
id: _contentItem id: _contentItem
spacing: control.hasTextAndIcon ? control.spacing : 0 spacing: control.hasTextAndIcon ? control.spacing : 0
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: label id: label
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Qt.AlignHCenter
visible: !control.isIcon
text: control.text
color: { color: {
if (primary && !isIcon) { if (primary && !isIcon) {
return "#FFFFFF" return "#FFFFFF";
} else { } 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 opacity: control.enabled || control.loading ? 1.0 : 0.5
text: control.text
type: labelType type: labelType
visible: !control.isIcon
} }
ColorImage { ColorImage {
id: iconImage id: iconImage
Layout.alignment: Qt.AlignCenter 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: { width: {
// special case for loading since we want icon to be square for rotation animation // special case for loading since we want icon to be square for rotation animation
if (control.loading) { 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 { RotationAnimation {
target: iconImage direction: RotationAnimation.Clockwise
loops: Animation.Infinite
duration: 1000 duration: 1000
from: 0 from: 0
to: 360 loops: Animation.Infinite
direction: RotationAnimation.Clockwise
running: control.loading 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: { Component.onCompleted: {
if (!control.colorScheme) { if (!control.colorScheme) {
console.trace() console.trace();
var next = root let next = root;
for (var i = 0; i<1000; i++) { for (let i = 0; i < 1000; i++) {
console.log(i, next, "colorscheme", next.colorScheme) console.log(i, next, "colorscheme", next.colorScheme);
next = next.parent next = next.parent;
if (!next) break if (!next)
break;
} }
console.error("ColorScheme not defined") console.error("ColorScheme not defined");
} }
} }
} }

View File

@ -1,97 +1,96 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Templates as T import QtQuick.Templates as T
T.CheckBox { T.CheckBox {
property ColorScheme colorScheme
property bool error: false
id: control id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, property ColorScheme colorScheme
implicitContentWidth + leftPadding + rightPadding) property bool error: false
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding)
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
padding: 0 padding: 0
spacing: 8 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 { 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 implicitHeight: 20
implicitWidth: 20
radius: ProtonStyle.checkbox_radius radius: ProtonStyle.checkbox_radius
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - 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 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 { 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 x: (parent.width - width) / 2
y: (parent.height - height) / 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? // TODO: do we need PartiallyChecked state?
@ -105,30 +104,4 @@ T.CheckBox {
// visible: control.checkState === Qt.PartiallyChecked // 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
}
} }

View File

@ -1,93 +1,88 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQml import QtQml
QtObject { QtObject {
// should be a pointer to ColorScheme object
property var prominent
// Primary // Backdrop
property color primary_norm property color backdrop_norm
property color background_avatar
// Interaction-norm // Background
property color interaction_norm property color background_norm
property color interaction_norm_hover property color background_strong
property color interaction_norm_active property color background_weak
// 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
// Border // Border
property color border_norm property color border_norm
property color border_weak property color border_weak
property color field_disabled
property color field_hover
// Background // Field
property color background_norm property color field_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
// Interaction-default // Interaction-default
property color interaction_default property color interaction_default
property color interaction_default_hover
property color interaction_default_active 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 // Scrollbar
property color scrollbar_norm property color scrollbar_norm
property color scrollbar_hover property color shadow_lifted
// 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
// Shadows // Shadows
property color shadow_norm property color shadow_norm
property color shadow_lifted
// Backdrop // Signal
property color backdrop_norm 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 // Images
property string welcome_img property string welcome_img
property string logo_img
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
import QtQuick.Controls import QtQuick.Controls
@ -26,148 +21,124 @@ T.ComboBox {
property ColorScheme colorScheme property ColorScheme colorScheme
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, bottomPadding: 5
implicitContentWidth + leftPadding + rightPadding) font.family: ProtonStyle.font_family
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, font.letterSpacing: ProtonStyle.body_letter_spacing
implicitContentHeight + topPadding + bottomPadding, font.pixelSize: ProtonStyle.body_font_size
implicitIndicatorHeight + topPadding + bottomPadding) 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) leftPadding: 12 + (!root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing)
rightPadding: 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 spacing: 8
topPadding: 5
font.family: ProtonStyle.font_family background: Rectangle {
font.weight: ProtonStyle.fontWeight_400 border.color: root.colorScheme.border_norm
font.pixelSize: ProtonStyle.body_font_size border.width: 1
font.letterSpacing: ProtonStyle.body_letter_spacing 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 { contentItem: T.TextField {
padding: 5
text: root.editable ? root.editText : root.displayText
font: root.font
enabled: root.editable
autoScroll: 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 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 validator: root.validator
verticalAlignment: TextInput.AlignVCenter 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 { background: Rectangle {
radius: ProtonStyle.context_item_radius
visible: root.enabled && root.editable && !root.flat
border.color: { border.color: {
if (root.activeFocus) { if (root.activeFocus) {
return root.colorScheme.interaction_norm return root.colorScheme.interaction_norm;
} }
if (root.hovered || root.activeFocus) { 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 border.width: 1
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
}
}
background: Rectangle {
implicitWidth: 140
implicitHeight: 36
radius: ProtonStyle.context_item_radius radius: ProtonStyle.context_item_radius
color: { visible: root.enabled && root.editable && !root.flat
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 { 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 property bool selected: root.currentIndex === index
font: root.font
highlighted: root.highlightedIndex === index highlighted: root.highlightedIndex === index
hoverEnabled: root.hoverEnabled
palette.highlightedText: selected ? root.colorScheme.text_invert : root.colorScheme.text_norm 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 { background: PaddedRectangle {
radius: ProtonStyle.context_item_radius
color: { color: {
if (parent.down) { if (parent.down) {
return root.colorScheme.interaction_default_active return root.colorScheme.interaction_default_active;
} }
if (parent.selected) { if (parent.selected) {
return root.colorScheme.interaction_norm return root.colorScheme.interaction_norm;
} }
if (parent.hovered || parent.highlighted) { 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 { popup: T.Popup {
y: root.height bottomMargin: 8
width: root.width
height: Math.min(contentItem.implicitHeight, root.Window.height - topMargin - bottomMargin) height: Math.min(contentItem.implicitHeight, root.Window.height - topMargin - bottomMargin)
topMargin: 8 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 { contentItem: Item {
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
@ -175,21 +146,14 @@ T.ComboBox {
ListView { ListView {
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 anchors.margins: 8
currentIndex: root.highlightedIndex
implicitHeight: contentHeight implicitHeight: contentHeight
model: root.delegateModel model: root.delegateModel
currentIndex: root.highlightedIndex
spacing: 4 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
} }
} }
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Templates as T import QtQuick.Templates as T
@ -23,58 +18,46 @@ import QtQuick.Controls.impl
T.Dialog { T.Dialog {
id: root id: root
property ColorScheme colorScheme 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 readonly property var occurred: shouldShow ? new Date() : undefined
function open() { readonly property int popupType: ApplicationWindow.PopupType.Dialog
root.shouldShow = true property bool shouldShow: false
}
function close() { function close() {
root.shouldShow = false root.shouldShow = false;
}
function open() {
root.shouldShow = true;
} }
anchors.centerIn: Overlay.overlay anchors.centerIn: Overlay.overlay
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, implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding, implicitHeaderWidth, implicitFooterWidth)
contentWidth + leftPadding + rightPadding,
implicitHeaderWidth,
implicitFooterWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
padding: 24 padding: 24
// TODO: Add DropShadow here
T.Overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm
}
T.Overlay.modeless: Rectangle {
color: "transparent"
}
background: Rectangle { background: Rectangle {
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
radius: ProtonStyle.dialog_radius radius: ProtonStyle.dialog_radius
} }
// TODO: Add DropShadow here Component.onCompleted: {
if (!ApplicationWindow.window) {
T.Overlay.modal: Rectangle { return;
color: root.colorScheme.backdrop_norm
} }
if (ApplicationWindow.window.popups === undefined) {
T.Overlay.modeless: Rectangle { return;
color: "transparent" }
const obj = this;
ApplicationWindow.window.popups.append({
"obj": obj
});
} }
} }

View File

@ -1,32 +1,23 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Templates as T import QtQuick.Templates as T
import "." as Proton import "." as Proton
T.Label { T.Label {
id: root id: root
property ColorScheme colorScheme
enum LabelType { enum LabelType {
// weight 700, size 28, height 36 // weight 700, size 28, height 36
Heading, Heading,
@ -47,96 +38,92 @@ T.Label {
// weight 700, size 12, height 16, spacing 0.4 // weight 700, size 12, height 16, spacing 0.4
Caption_bold Caption_bold
} }
property ColorScheme colorScheme
property int type: Proton.Label.LabelType.Body property int type: Proton.Label.LabelType.Body
function link(url, text) {
return `<a href="${url}">${text}</a>`;
}
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
linkColor: root.colorScheme.interaction_norm
palette.link: linkColor
font.family: ProtonStyle.font_family 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: { font.letterSpacing: {
switch (root.type) { switch (root.type) {
case Proton.Label.LabelType.Heading: case Proton.Label.LabelType.Heading:
case Proton.Label.LabelType.Title: case Proton.Label.LabelType.Title:
case Proton.Label.LabelType.Lead: case Proton.Label.LabelType.Lead:
return 0 return 0;
case Proton.Label.LabelType.Body: case Proton.Label.LabelType.Body:
case Proton.Label.LabelType.Body_semibold: case Proton.Label.LabelType.Body_semibold:
case Proton.Label.LabelType.Body_bold: 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:
case Proton.Label.LabelType.Caption_semibold: case Proton.Label.LabelType.Caption_semibold:
case Proton.Label.LabelType.Caption_bold: case Proton.Label.LabelType.Caption_bold:
return ProtonStyle.caption_letter_spacing return ProtonStyle.caption_letter_spacing;
} }
} }
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 verticalAlignment: Text.AlignBottom
function link(url, text) {
return `<a href="${url}">${text}</a>`
}
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
@ -27,22 +22,19 @@ T.Menu {
property ColorScheme colorScheme property ColorScheme colorScheme
implicitWidth: Math.max( implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
implicitBackgroundWidth + leftInset + rightInset, implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
contentWidth + leftPadding + rightPadding
)
implicitHeight: Math.max(
implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
)
margins: 0 margins: 0
overlap: 1 overlap: 1
delegate: MenuItem { background: Rectangle {
colorScheme: control.colorScheme border.color: colorScheme.border_weak
border.width: 1
color: colorScheme.background_norm
implicitHeight: 40
implicitWidth: 200
radius: ProtonStyle.account_row_radius
} }
contentItem: Item { contentItem: Item {
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
@ -50,23 +42,17 @@ T.Menu {
ListView { ListView {
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 anchors.margins: 8
implicitHeight: contentHeight
model: control.contentModel
interactive: Window.window ? contentHeight > Window.window.height : false
clip: true clip: true
currentIndex: control.currentIndex currentIndex: control.currentIndex
implicitHeight: contentHeight
interactive: Window.window ? contentHeight > Window.window.height : false
model: control.contentModel
ScrollIndicator.vertical: ScrollIndicator {} ScrollIndicator.vertical: ScrollIndicator {
} }
} }
}
background: Rectangle { delegate: MenuItem {
implicitWidth: 200 colorScheme: control.colorScheme
implicitHeight: 40
color: colorScheme.background_norm
border.width: 1
border.color: colorScheme.border_weak
radius: ProtonStyle.account_row_radius
} }
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
@ -26,46 +21,39 @@ T.MenuItem {
property ColorScheme colorScheme property ColorScheme colorScheme
width: parent.width // required. Other item overflows to the right of the menu and get clipped. font.family: ProtonStyle.font_family
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, font.letterSpacing: ProtonStyle.body_letter_spacing
implicitContentHeight + topPadding + bottomPadding, font.pixelSize: ProtonStyle.body_font_size
implicitIndicatorHeight + topPadding + bottomPadding) 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 padding: 12
spacing: 6 spacing: 6
width: parent.width // required. Other item overflows to the right of the menu and get clipped.
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
}
background: Rectangle { 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 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
} }
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
@ -23,45 +18,40 @@ import QtQuick.Templates as T
T.Popup { T.Popup {
id: root id: root
property ColorScheme colorScheme 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 readonly property var occurred: shouldShow ? new Date() : undefined
function open() { property int popupType: ApplicationWindow.PopupType.Banner
root.shouldShow = true property bool shouldShow: false
}
function close() { function close() {
root.shouldShow = false root.shouldShow = false;
}
function open() {
root.shouldShow = true;
} }
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
contentWidth + leftPadding + rightPadding) implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
// TODO: Add DropShadow here // TODO: Add DropShadow here
T.Overlay.modal: Rectangle { T.Overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm color: root.colorScheme.backdrop_norm
} }
T.Overlay.modeless: Rectangle { T.Overlay.modeless: Rectangle {
color: "transparent" color: "transparent"
} }
Component.onCompleted: {
if (!ApplicationWindow.window) {
return;
}
if (ApplicationWindow.window.popups === undefined) {
return;
}
const obj = this;
ApplicationWindow.window.popups.append({
"obj": obj
});
}
} }

View File

@ -1,115 +1,91 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Templates as T import QtQuick.Templates as T
T.RadioButton { T.RadioButton {
property ColorScheme colorScheme
property bool error: false
id: control id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, property ColorScheme colorScheme
implicitContentWidth + leftPadding + rightPadding) property bool error: false
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding)
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
padding: 0 padding: 0
spacing: 8 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 { 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 implicitHeight: 20
implicitWidth: 20
radius: width / 2 radius: width / 2
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - 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 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 { Rectangle {
x: (parent.width - width) / 2 color: {
y: (parent.height - height) / 2 if (!control.enabled) {
width: 8 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;
}
height: 8 height: 8
radius: width / 2 radius: width / 2
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.interaction_norm
}
visible: control.checked 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
}
} }

View File

@ -1,387 +1,188 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
pragma Singleton pragma Singleton
import QtQml import QtQml
import QtQuick import QtQuick
import "."
import "./"
// https://wiki.qt.io/Qml_Styling // https://wiki.qt.io/Qml_Styling
// http://imaginativethinking.ca/make-qml-component-singleton/ // http://imaginativethinking.ca/make-qml-component-singleton/
QtObject { QtObject {
id: root 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 { property ColorScheme darkProminentStyle: ColorScheme {
id: _darkProminentStyle id: _darkProminentStyle
prominent: this // Backdrop
backdrop_norm: Qt.rgba(0, 0, 0, 0.32)
background_avatar: "#6D4AFF"
// Primary // Background
primary_norm: "#8A6EFF" background_norm: "#16141c"
background_strong: "#3F3B4C"
// Interaction-norm background_weak: "#292733"
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
border_norm: "#4A4658" border_norm: "#4A4658"
border_weak: "#343140" border_weak: "#343140"
field_disabled: "#3F3B4C"
field_hover: "#6D697D"
// Background // Field
background_norm: "#16141c" field_norm: "#5B576B"
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
interaction_default: "#00000000" 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
scrollbar_norm: "#4A4658" scrollbar_norm: "#4A4658"
scrollbar_hover: "#5B576B" shadow_lifted: Qt.rgba(0, 0, 0, 0.48) // #000000 48% x+0 y+8 blur:24
// 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 // Shadows
shadow_norm: Qt.rgba(0, 0, 0, 0.4) // #000000 40% x+0 y+1 blur:4 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 // Signal
backdrop_norm: Qt.rgba(0,0,0,0.32) 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 // Images
welcome_img: "/qml/icons/img-welcome-dark.png" welcome_img: "/qml/icons/img-welcome-dark.png"
}
property ColorScheme darkStyle: ColorScheme {
id: _darkStyle
// Backdrop
backdrop_norm: Qt.rgba(0, 0, 0, 0.32)
background_avatar: "#6D4AFF"
// 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" 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 ColorScheme currentStyle: lightStyle
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 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 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 int fontWeight_100: Font.Thin property int fontWeight_100: Font.Thin
property int fontWeight_200: Font.Light property int fontWeight_200: Font.Light
property int fontWeight_300: Font.ExtraLight property int fontWeight_300: Font.ExtraLight
@ -391,4 +192,179 @@ QtObject {
property int fontWeight_700: Font.Bold property int fontWeight_700: Font.Bold
property int fontWeight_800: Font.ExtraBold property int fontWeight_800: Font.ExtraBold
property int fontWeight_900: Font.Black 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
} }

View File

@ -1,150 +1,124 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Templates as T import QtQuick.Templates as T
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
T.Switch { T.Switch {
property ColorScheme colorScheme id: control
property ColorScheme colorScheme
property bool loading: false 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? // TODO: store previous enabled state and restore it?
// For now assuming that only enabled buttons could have loading state // For now assuming that only enabled buttons could have loading state
onLoadingChanged: { onLoadingChanged: {
if (loading) { enabled = !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
} }
} }

View File

@ -1,54 +1,37 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Templates as T import QtQuick.Templates as T
import QtQuick.Layouts import QtQuick.Layouts
import "." as Proton import "." as Proton
FocusScope { FocusScope {
id: root 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 alias activeFocusOnPress: control.activeFocusOnPress
property string assistiveText
property alias background: control.background
property alias baseUrl: control.baseUrl property alias baseUrl: control.baseUrl
property alias bottomInset: control.bottomInset
property alias bottomPadding: control.bottomPadding property alias bottomPadding: control.bottomPadding
property alias canPaste: control.canPaste property alias canPaste: control.canPaste
property alias canRedo: control.canRedo property alias canRedo: control.canRedo
property alias canUndo: control.canUndo property alias canUndo: control.canUndo
property alias color: control.color property alias color: control.color
property ColorScheme colorScheme
property alias contentHeight: control.contentHeight property alias contentHeight: control.contentHeight
property alias contentWidth: control.contentWidth property alias contentWidth: control.contentWidth
property alias cursorDelegate: control.cursorDelegate property alias cursorDelegate: control.cursorDelegate
@ -56,21 +39,36 @@ FocusScope {
property alias cursorRectangle: control.cursorRectangle property alias cursorRectangle: control.cursorRectangle
property alias cursorVisible: control.cursorVisible property alias cursorVisible: control.cursorVisible
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment 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 font: control.font
property alias hint: hint.text
property alias horizontalAlignment: control.horizontalAlignment property alias horizontalAlignment: control.horizontalAlignment
property alias hoverEnabled: control.hoverEnabled
property alias hovered: control.hovered
property alias hoveredLink: control.hoveredLink property alias hoveredLink: control.hoveredLink
property alias implicitBackgroundHeight: control.implicitBackgroundHeight
property alias implicitBackgroundWidth: control.implicitBackgroundWidth
property alias inputMethodComposing: control.inputMethodComposing property alias inputMethodComposing: control.inputMethodComposing
property alias inputMethodHints: control.inputMethodHints property alias inputMethodHints: control.inputMethodHints
property alias label: label.text
property alias leftInset: control.leftInset
property alias leftPadding: control.leftPadding property alias leftPadding: control.leftPadding
property alias length: control.length property alias length: control.length
property alias lineCount: control.lineCount property alias lineCount: control.lineCount
property alias mouseSelectionMode: control.mouseSelectionMode property alias mouseSelectionMode: control.mouseSelectionMode
property alias overwriteMode: control.overwriteMode property alias overwriteMode: control.overwriteMode
property alias padding: control.padding property alias padding: control.padding
property alias palette: control.palette
property alias persistentSelection: control.persistentSelection property alias persistentSelection: control.persistentSelection
property alias placeholderText: control.placeholderText
property alias placeholderTextColor: control.placeholderTextColor
property alias preeditText: control.preeditText property alias preeditText: control.preeditText
property alias readOnly: control.readOnly property alias readOnly: control.readOnly
property alias renderType: control.renderType property alias renderType: control.renderType
property alias rightInset: control.rightInset
property alias rightPadding: control.rightPadding property alias rightPadding: control.rightPadding
property alias selectByKeyboard: control.selectByKeyboard property alias selectByKeyboard: control.selectByKeyboard
property alias selectByMouse: control.selectByMouse property alias selectByMouse: control.selectByMouse
@ -84,61 +82,119 @@ FocusScope {
property alias textDocument: control.textDocument property alias textDocument: control.textDocument
property alias textFormat: control.textFormat property alias textFormat: control.textFormat
property alias textMargin: control.textMargin property alias textMargin: control.textMargin
property alias topInset: control.topInset
property alias topPadding: control.topPadding property alias topPadding: control.topPadding
property bool validateOnEditingFinished: true
// We are using our own type of validators. It should be a function // 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 // returning an error string in case of error and undefined if no error
property var validator property var validator
property alias verticalAlignment: control.verticalAlignment property alias verticalAlignment: control.verticalAlignment
property alias wrapMode: control.wrapMode property alias wrapMode: control.wrapMode
implicitWidth: children[0].implicitWidth signal editingFinished
implicitHeight: children[0].implicitHeight
property alias label: label.text function append(text) {
property alias hint: hint.text return control.append(text);
property string assistiveText }
property string errorString function clear() {
return control.clear();
property bool error: false }
function copy() {
signal editingFinished() return control.copy();
}
function append(text) { return control.append(text) } function cut() {
function clear() { return control.clear() } return control.cut();
function copy() { return control.copy() } }
function cut() { return control.cut() } function deselect() {
function deselect() { return control.deselect() } return control.deselect();
function getFormattedText(start, end) { return control.getFormattedText(start, end) } }
function getText(start, end) { return control.getText(start, end) } function getFormattedText(start, end) {
function insert(position, text) { return control.insert(position, text) } return control.getFormattedText(start, end);
function isRightToLeft(start, end) { return control.isRightToLeft(start, end) } }
function linkAt(x, y) { return control.linkAt(x, y) } function getText(start, end) {
function moveCursorSelection(position, mode) { return control.moveCursorSelection(position, mode) } return control.getText(start, end);
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() }
// Calculates the height of the component to make exactly lineNum visible in edit area // Calculates the height of the component to make exactly lineNum visible in edit area
function heightForLinesVisible(lineNum) { function heightForLinesVisible(lineNum) {
var totalHeight = 0 let totalHeight = 0;
totalHeight += headerLayout.height totalHeight += headerLayout.height;
totalHeight += footerLayout.height totalHeight += footerLayout.height;
totalHeight += control.topPadding + control.bottomPadding totalHeight += control.topPadding + control.bottomPadding;
totalHeight += lineNum * fontMetrics.height totalHeight += lineNum * fontMetrics.height;
return totalHeight 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 { FontMetrics {
id: fontMetrics id: fontMetrics
font: control.font font: control.font
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
@ -149,154 +205,123 @@ FocusScope {
spacing: 0 spacing: 0
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: label id: label
Layout.fillWidth: true Layout.fillWidth: true
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
colorScheme: root.colorScheme
type: Proton.Label.LabelType.Body_semibold type: Proton.Label.LabelType.Body_semibold
} }
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: hint id: hint
Layout.fillWidth: true Layout.fillWidth: true
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
type: Proton.Label.LabelType.Caption type: Proton.Label.LabelType.Caption
} }
} }
ScrollView { ScrollView {
id: controlView id: controlView
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true clip: true
T.TextArea { T.TextArea {
id: control id: control
KeyNavigation.backtab: root.KeyNavigation.backtab
implicitWidth: Math.max( KeyNavigation.down: root.KeyNavigation.down
contentWidth + leftPadding + rightPadding, KeyNavigation.left: root.KeyNavigation.left
implicitBackgroundWidth + leftInset + rightInset, KeyNavigation.priority: root.KeyNavigation.priority
placeholder.implicitWidth + leftPadding + rightPadding KeyNavigation.right: root.KeyNavigation.right
) KeyNavigation.tab: root.KeyNavigation.tab
implicitHeight: Math.max( KeyNavigation.up: root.KeyNavigation.up
contentHeight + topPadding + bottomPadding,
implicitBackgroundHeight + topInset + bottomInset,
placeholder.implicitHeight + topPadding + bottomPadding
)
topPadding: 8
bottomPadding: 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 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 // enforcing default focus here within component
focus: root.focus focus: root.focus
font.family: ProtonStyle.font_family
KeyNavigation.priority: root.KeyNavigation.priority font.letterSpacing: ProtonStyle.body_letter_spacing
KeyNavigation.backtab: root.KeyNavigation.backtab font.pixelSize: ProtonStyle.body_font_size
KeyNavigation.tab: root.KeyNavigation.tab font.weight: ProtonStyle.fontWeight_400
KeyNavigation.up: root.KeyNavigation.up implicitHeight: Math.max(contentHeight + topPadding + bottomPadding, implicitBackgroundHeight + topInset + bottomInset, placeholder.implicitHeight + topPadding + bottomPadding)
KeyNavigation.down: root.KeyNavigation.down implicitWidth: Math.max(contentWidth + leftPadding + rightPadding, implicitBackgroundWidth + leftInset + rightInset, placeholder.implicitWidth + leftPadding + rightPadding)
KeyNavigation.left: root.KeyNavigation.left leftPadding: 12
KeyNavigation.right: root.KeyNavigation.right placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
rightPadding: 12
selectByMouse: true selectByMouse: true
selectedTextColor: control.palette.highlightedText
cursorDelegate: Rectangle { selectionColor: control.palette.highlight
id: cursor topPadding: 8
width: 1 wrapMode: TextInput.Wrap
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
}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: ProtonStyle.input_radius
visible: true
color: root.colorScheme.background_norm
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return root.colorScheme.field_disabled return root.colorScheme.field_disabled;
} }
if (control.activeFocus) { if (control.activeFocus) {
return root.colorScheme.interaction_norm return root.colorScheme.interaction_norm;
} }
if (root.error) { if (root.error) {
return root.colorScheme.signal_danger return root.colorScheme.signal_danger;
} }
if (control.hovered) { 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 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 { RowLayout {
id: footerLayout id: footerLayout
Layout.fillWidth: true Layout.fillWidth: true
@ -304,67 +329,29 @@ FocusScope {
ColorImage { ColorImage {
id: errorIcon id: errorIcon
Layout.rightMargin: 4 Layout.rightMargin: 4
visible: root.error && (assistiveText.text.length > 0)
source: "/qml/icons/ic-exclamation-circle-filled.svg"
color: root.colorScheme.signal_danger color: root.colorScheme.signal_danger
height: assistiveText.height height: assistiveText.height
source: "/qml/icons/ic-exclamation-circle-filled.svg"
sourceSize.height: assistiveText.height sourceSize.height: assistiveText.height
visible: root.error && (assistiveText.text.length > 0)
} }
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: assistiveText id: assistiveText
Layout.fillWidth: true Layout.fillWidth: true
text: root.error ? root.errorString : root.assistiveText
color: { color: {
if (!root.enabled) { if (!root.enabled) {
return root.colorScheme.text_disabled return root.colorScheme.text_disabled;
} }
if (root.error) { 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 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 = ""
}
} }

View File

@ -1,54 +1,38 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import QtQuick.Templates as T import QtQuick.Templates as T
import QtQuick.Layouts import QtQuick.Layouts
import "." as Proton import "." as Proton
FocusScope { FocusScope {
id: root 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 acceptableInput: control.acceptableInput
property alias activeFocusOnPress: control.activeFocusOnPress property alias activeFocusOnPress: control.activeFocusOnPress
property string assistiveText
property alias autoScroll: control.autoScroll property alias autoScroll: control.autoScroll
property alias background: control.background
property alias bottomInset: control.bottomInset
property alias bottomPadding: control.bottomPadding property alias bottomPadding: control.bottomPadding
property alias canPaste: control.canPaste property alias canPaste: control.canPaste
property alias canRedo: control.canRedo property alias canRedo: control.canRedo
property alias canUndo: control.canUndo property alias canUndo: control.canUndo
property alias color: control.color property alias color: control.color
property ColorScheme colorScheme
//property alias contentHeight: control.contentHeight //property alias contentHeight: control.contentHeight
//property alias contentWidth: control.contentWidth //property alias contentWidth: control.contentWidth
property alias cursorDelegate: control.cursorDelegate property alias cursorDelegate: control.cursorDelegate
@ -56,24 +40,39 @@ FocusScope {
property alias cursorRectangle: control.cursorRectangle property alias cursorRectangle: control.cursorRectangle
property alias cursorVisible: control.cursorVisible property alias cursorVisible: control.cursorVisible
property alias displayText: control.displayText property alias displayText: control.displayText
property int echoMode: TextInput.Normal
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment
property bool error: false
property string errorString
property alias focusReason: control.focusReason
property alias font: control.font property alias font: control.font
property alias hint: hint.text
property alias horizontalAlignment: control.horizontalAlignment 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 inputMask: control.inputMask
property alias inputMethodComposing: control.inputMethodComposing property alias inputMethodComposing: control.inputMethodComposing
property alias inputMethodHints: control.inputMethodHints property alias inputMethodHints: control.inputMethodHints
property alias label: label.text
property alias leftInset: control.leftInset
property alias leftPadding: control.leftPadding property alias leftPadding: control.leftPadding
property alias length: control.length property alias length: control.length
property alias maximumLength: control.maximumLength property alias maximumLength: control.maximumLength
property alias mouseSelectionMode: control.mouseSelectionMode property alias mouseSelectionMode: control.mouseSelectionMode
property alias overwriteMode: control.overwriteMode property alias overwriteMode: control.overwriteMode
property alias padding: control.padding property alias padding: control.padding
property alias palette: control.palette
property alias passwordCharacter: control.passwordCharacter property alias passwordCharacter: control.passwordCharacter
property alias passwordMaskDelay: control.passwordMaskDelay property alias passwordMaskDelay: control.passwordMaskDelay
property alias persistentSelection: control.persistentSelection property alias persistentSelection: control.persistentSelection
property alias placeholderText: control.placeholderText
property alias placeholderTextColor: control.placeholderTextColor
property alias preeditText: control.preeditText property alias preeditText: control.preeditText
property alias readOnly: control.readOnly property alias readOnly: control.readOnly
property alias renderType: control.renderType property alias renderType: control.renderType
property alias rightInset: control.rightInset
property alias rightPadding: control.rightPadding property alias rightPadding: control.rightPadding
property alias selectByMouse: control.selectByMouse property alias selectByMouse: control.selectByMouse
property alias selectedText: control.selectedText property alias selectedText: control.selectedText
@ -82,47 +81,102 @@ FocusScope {
property alias selectionEnd: control.selectionEnd property alias selectionEnd: control.selectionEnd
property alias selectionStart: control.selectionStart property alias selectionStart: control.selectionStart
property alias text: control.text 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 // 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 // returning an error string in case of error and undefined if no error
property var validator property var validator
property alias verticalAlignment: control.verticalAlignment property alias verticalAlignment: control.verticalAlignment
property alias wrapMode: control.wrapMode 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 implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
property alias label: label.text onEditingFinished: {
property alias hint: hint.text if (!validateOnEditingFinished) {
property string assistiveText return;
property string errorString }
validate();
property int echoMode: TextInput.Normal }
onTextChanged: {
property bool error: false root.error = false;
root.errorString = "";
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() }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -133,19 +187,18 @@ FocusScope {
spacing: 0 spacing: 0
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: label id: label
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: root.colorScheme
type: Proton.Label.LabelType.Body_semibold type: Proton.Label.LabelType.Body_semibold
} }
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: hint id: hint
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
type: Proton.Label.LabelType.Caption type: Proton.Label.LabelType.Caption
} }
@ -156,36 +209,29 @@ FocusScope {
// will be adjusted to background's width making text field and eye button overlap // will be adjusted to background's width making text field and eye button overlap
Rectangle { Rectangle {
id: background id: background
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
radius: ProtonStyle.input_radius
visible: true
color: root.colorScheme.background_norm
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return root.colorScheme.field_disabled return root.colorScheme.field_disabled;
} }
if (control.activeFocus) { if (control.activeFocus) {
return root.colorScheme.interaction_norm return root.colorScheme.interaction_norm;
} }
if (root.error) { if (root.error) {
return root.colorScheme.signal_danger return root.colorScheme.signal_danger;
} }
if (control.hovered) { 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 border.width: 1
color: root.colorScheme.background_norm
implicitWidth: children[0].implicitWidth
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
radius: ProtonStyle.input_radius
visible: true
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -193,190 +239,135 @@ FocusScope {
T.TextField { T.TextField {
id: control 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.fillHeight: true
Layout.fillWidth: 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 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 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 echoMode: eyeButton.checked ? TextInput.Normal : root.echoMode
// enforcing default focus here within component // enforcing default focus here within component
focus: true focus: true
font.family: ProtonStyle.font_family
KeyNavigation.priority: root.KeyNavigation.priority font.letterSpacing: ProtonStyle.body_letter_spacing
KeyNavigation.backtab: root.KeyNavigation.backtab font.pixelSize: ProtonStyle.body_font_size
KeyNavigation.tab: root.KeyNavigation.tab font.weight: ProtonStyle.fontWeight_400
KeyNavigation.up: root.KeyNavigation.up implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding, placeholder.implicitHeight + topPadding + bottomPadding)
KeyNavigation.down: root.KeyNavigation.down implicitWidth: implicitBackgroundWidth + leftInset + rightInset || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
KeyNavigation.left: root.KeyNavigation.left leftPadding: 12
KeyNavigation.right: root.KeyNavigation.right placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
rightPadding: 12
selectByMouse: true 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 { cursorDelegate: Rectangle {
id: cursor id: cursor
width: 1
color: root.colorScheme.interaction_norm color: root.colorScheme.interaction_norm
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
width: 1
Connections { Connections {
target: control
function onCursorPositionChanged() { function onCursorPositionChanged() {
// keep a moving cursor visible // keep a moving cursor visible
cursor.opacity = 1 cursor.opacity = 1;
timer.restart() 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
} }
} }
Timer { onAccepted: {
id: timer root.accepted();
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
} }
onEditingFinished: {
root.editingFinished();
}
onTextEdited: {
root.textEdited();
} }
PlaceholderText { PlaceholderText {
id: placeholder 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 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 verticalAlignment: control.verticalAlignment
visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
elide: Text.ElideRight width: control.width - (control.leftPadding + control.rightPadding)
renderType: control.renderType x: control.leftPadding
} y: control.topPadding
background: Item {
implicitWidth: 80
implicitHeight: 36
visible: false
}
onAccepted: {
root.accepted()
}
onEditingFinished: {
root.editingFinished()
}
onTextEdited: {
root.textEdited()
} }
} }
Proton.Button { Proton.Button {
colorScheme: root.colorScheme
id: eyeButton id: eyeButton
Layout.fillHeight: true Layout.fillHeight: true
visible: root.echoMode === TextInput.Password
icon.color: control.color
checkable: true checkable: true
colorScheme: root.colorScheme
icon.color: control.color
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg" icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
visible: root.echoMode === TextInput.Password
} }
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
ColorImage { ColorImage {
id: errorIcon id: errorIcon
Layout.rightMargin: 4 Layout.rightMargin: 4
visible: root.error && (assistiveText.text.length > 0)
source: "../icons/ic-exclamation-circle-filled.svg"
color: root.colorScheme.signal_danger color: root.colorScheme.signal_danger
height: assistiveText.lineHeight height: assistiveText.lineHeight
source: "../icons/ic-exclamation-circle-filled.svg"
sourceSize.height: assistiveText.lineHeight sourceSize.height: assistiveText.lineHeight
visible: root.error && (assistiveText.text.length > 0)
} }
Proton.Label { Proton.Label {
colorScheme: root.colorScheme
id: assistiveText id: assistiveText
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap
text: root.error ? root.errorString : root.assistiveText
color: { color: {
if (!root.enabled) { if (!root.enabled) {
return root.colorScheme.text_disabled return root.colorScheme.text_disabled;
} }
if (root.error) { 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 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 = ""
}
} }

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
@ -22,92 +17,106 @@ import QtQuick.Controls.impl
Item { Item {
id: root id: root
property var colorScheme
property bool _disabled: !enabled
property bool checked property bool checked
property var colorScheme
property bool hovered property bool hovered
property bool loading property bool loading
signal clicked signal clicked
property bool _disabled: !enabled
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
Rectangle { Rectangle {
id: indicator id: indicator
implicitWidth: 40
implicitHeight: 24
radius: width/2
color: { color: {
if (root.loading) return "transparent" if (root.loading)
if (root._disabled) return root.colorScheme.background_strong return "transparent";
return root.colorScheme.background_norm if (root._disabled)
} return root.colorScheme.background_strong;
border { return root.colorScheme.background_norm;
width: 1
color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm
} }
implicitHeight: 24
implicitWidth: 40
radius: width / 2
border {
color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm
width: 1
}
Rectangle { Rectangle {
anchors.verticalCenter: indicator.verticalCenter
anchors.left: indicator.left anchors.left: indicator.left
anchors.leftMargin: root.checked ? 16 : 0 anchors.leftMargin: root.checked ? 16 : 0
width: 24 anchors.verticalCenter: indicator.verticalCenter
color: {
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;
} else {
if (root.hovered)
return root.colorScheme.field_hover;
return root.colorScheme.field_norm;
}
}
height: 24 height: 24
radius: width / 2 radius: width / 2
color: { width: 24
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
} else {
if (root.hovered) return root.colorScheme.field_hover
return root.colorScheme.field_norm
}
}
ColorImage { ColorImage {
anchors.centerIn: parent anchors.centerIn: parent
source: "/qml/icons/ic-check.svg"
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
height: root.colorScheme.body_font_size height: root.colorScheme.body_font_size
source: "/qml/icons/ic-check.svg"
sourceSize.height: root.colorScheme.body_font_size sourceSize.height: root.colorScheme.body_font_size
visible: root.checked visible: root.checked
} }
} }
ColorImage { ColorImage {
id: loader id: loader
anchors.centerIn: parent anchors.centerIn: parent
source: "/qml/icons/Loader_16.svg"
color: root.colorScheme.text_norm color: root.colorScheme.text_norm
height: root.colorScheme.body_font_size height: root.colorScheme.body_font_size
source: "/qml/icons/Loader_16.svg"
sourceSize.height: root.colorScheme.body_font_size sourceSize.height: root.colorScheme.body_font_size
visible: root.loading visible: root.loading
RotationAnimation { RotationAnimation {
target: loader direction: RotationAnimation.Clockwise
loops: Animation.Infinite
duration: 1000 duration: 1000
from: 0 from: 0
to: 360 loops: Animation.Infinite
direction: RotationAnimation.Clockwise
running: root.loading running: root.loading
target: loader
to: 360
} }
} }
MouseArea { MouseArea {
anchors.fill: indicator anchors.fill: indicator
hoverEnabled: true hoverEnabled: true
onEntered: {root.hovered = true }
onExited: {root.hovered = false } onClicked: {
onClicked: { if (root.enabled) root.clicked();} if (root.enabled)
onPressed: {root.hovered = true } root.clicked();
onReleased: { root.hovered = containsMouse } }
onEntered: {
root.hovered = true;
}
onExited: {
root.hovered = false;
}
onPressed: {
root.hovered = true;
}
onReleased: {
root.hovered = containsMouse;
}
} }
} }
} }

View File

@ -1,51 +1,44 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
Item { Item {
id: root id: root
property var colorScheme enum ActionType {
Toggle = 1,
property string text: "Text" Button,
property string actionText: "Action" PrimaryButton
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
property var _bottomMargin: 20 property var _bottomMargin: 20
property var _lineWidth: 1 property var _lineWidth: 1
property var _toggleTopMargin: 6 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 signal clicked
enum ActionType {
Toggle = 1, Button = 2, PrimaryButton = 3
}
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
@ -54,10 +47,9 @@ Item {
spacing: 16 spacing: 16
ColumnLayout { ColumnLayout {
Layout.bottomMargin: root._bottomMargin
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: root._bottomMargin
spacing: 4 spacing: 4
Label { Label {
@ -66,51 +58,51 @@ Item {
text: root.text text: root.text
type: Label.Body_semibold type: Label.Body_semibold
} }
Label { Label {
id: descriptionLabel id: descriptionLabel
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
color: root.colorScheme.text_weak
wrapMode: Text.WordWrap
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: root.description text: root.description
color: root.colorScheme.text_weak wrapMode: Text.WordWrap
} }
} }
Toggle { Toggle {
id: toggle
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.topMargin: root._toggleTopMargin Layout.topMargin: root._toggleTopMargin
id: toggle checked: root.checked
colorScheme: root.colorScheme colorScheme: root.colorScheme
loading: root.loading
visible: root.type === SettingsItem.ActionType.Toggle visible: root.type === SettingsItem.ActionType.Toggle
checked: root.checked onClicked: {
loading: root.loading if (!root.loading)
onClicked: { if (!root.loading) root.clicked() } root.clicked();
}
} }
Button { Button {
Layout.alignment: Qt.AlignTop
id: button id: button
Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme colorScheme: root.colorScheme
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
text: root.actionText + (root.actionIcon != "" ? " " : "")
loading: root.loading
icon.source: root.actionIcon icon.source: root.actionIcon
onClicked: { if (!root.loading) root.clicked() } loading: root.loading
secondary: root.type !== SettingsItem.PrimaryButton 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 { Rectangle {
anchors.bottom: root.bottom
anchors.left: root.left anchors.left: root.left
anchors.right: root.right anchors.right: root.right
anchors.bottom: root.bottom
color: colorScheme.border_weak color: colorScheme.border_weak
height: root._lineWidth height: root._lineWidth
visible: root.showSeparator visible: root.showSeparator

View File

@ -1,61 +1,53 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
Item { Item {
id: root id: root
property var colorScheme property int _bottomMargin: 32
default property alias items: content.children
signal back()
property int _leftMargin: 64 property int _leftMargin: 64
property int _rightMargin: 64 property int _rightMargin: 64
property int _topMargin: 32
property int _bottomMargin: 32
property int _spacing: 20 property int _spacing: 20
property int _topMargin: 32
property var colorScheme
// fillHeight indicates whether the SettingsView should fill all available explicit height set // fillHeight indicates whether the SettingsView should fill all available explicit height set
property bool fillHeight: false property bool fillHeight: false
default property alias items: content.children
signal back
ScrollView { ScrollView {
id: scrollView id: scrollView
anchors.fill: parent
clip: true clip: true
anchors.fill: parent
Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds // Disable the springy effect when scroll reaches top/bottom. Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds // Disable the springy effect when scroll reaches top/bottom.
Item { Item {
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
width: scrollView.availableWidth
height: scrollView.availableHeight height: scrollView.availableHeight
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin 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 // 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 // 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 // equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed
implicitWidth: width implicitWidth: width
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
width: scrollView.availableWidth
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -63,16 +55,13 @@ Item {
ColumnLayout { ColumnLayout {
id: content id: content
spacing: root._spacing
Layout.fillWidth: true
Layout.topMargin: root._topMargin
Layout.bottomMargin: root._bottomMargin Layout.bottomMargin: root._bottomMargin
Layout.fillWidth: true
Layout.leftMargin: root._leftMargin Layout.leftMargin: root._leftMargin
Layout.rightMargin: root._rightMargin Layout.rightMargin: root._rightMargin
Layout.topMargin: root._topMargin
spacing: root._spacing
} }
Item { Item {
id: filler id: filler
Layout.fillHeight: true Layout.fillHeight: true
@ -81,19 +70,20 @@ Item {
} }
} }
} }
Button { Button {
id: backButton id: backButton
anchors {
top: parent.top
left: parent.left
topMargin: root._topMargin
leftMargin: (root._leftMargin-backButton.width) / 2
}
colorScheme: root.colorScheme colorScheme: root.colorScheme
onClicked: root.back() horizontalPadding: 8
icon.source: "/qml/icons/ic-arrow-left.svg" icon.source: "/qml/icons/ic-arrow-left.svg"
secondary: true secondary: true
horizontalPadding: 8
onClicked: root.back()
anchors {
left: parent.left
leftMargin: (root._leftMargin - backButton.width) / 2
top: parent.top
topMargin: root._topMargin
}
} }
} }

View File

@ -1,110 +1,139 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
Item { Item {
id: root id: root
property string address
property ColorScheme colorScheme property ColorScheme colorScheme
property var user property var user
property string address
signal dismissed() signal dismissed
signal finished() 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 implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
ListModel { ListModel {
id: clients id: clients
property string name : "Apple Mail"
property string iconSource : "/qml/icons/ic-apple-mail.svg"
property bool haveAutoSetup: true 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 link: "https://proton.me/support/protonmail-bridge-clients-apple-mail"
property string name: "Apple Mail"
Component.onCompleted: { Component.onCompleted: {
if (Backend.goos == "darwin") { if (Backend.goos === "darwin") {
append({ append({
"name": "Apple Mail", "name": "Apple Mail",
"iconSource": "/qml/icons/ic-apple-mail.svg", "iconSource": "/qml/icons/ic-apple-mail.svg",
"haveAutoSetup": true, "haveAutoSetup": true,
"link": "https://proton.me/support/protonmail-bridge-clients-apple-mail" "link": "https://proton.me/support/protonmail-bridge-clients-apple-mail"
}) });
append({ append({
"name": "Microsoft Outlook", "name": "Microsoft Outlook",
"iconSource": "/qml/icons/ic-microsoft-outlook.svg", "iconSource": "/qml/icons/ic-microsoft-outlook.svg",
"haveAutoSetup": false, "haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" "link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019"
}) });
} }
if (Backend.goos == "windows") { if (Backend.goos === "windows") {
append({ append({
"name": "Microsoft Outlook", "name": "Microsoft Outlook",
"iconSource": "/qml/icons/ic-microsoft-outlook.svg", "iconSource": "/qml/icons/ic-microsoft-outlook.svg",
"haveAutoSetup": false, "haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019" "link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019"
}) });
} }
append({ append({
"name": "Mozilla Thunderbird", "name": "Mozilla Thunderbird",
"iconSource": "/qml/icons/ic-mozilla-thunderbird.svg", "iconSource": "/qml/icons/ic-mozilla-thunderbird.svg",
"haveAutoSetup": false, "haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird" "link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird"
}) });
append({ append({
"name": "Other", "name": "Other",
"iconSource": "/qml/icons/ic-other-mail-clients.svg", "iconSource": "/qml/icons/ic-other-mail-clients.svg",
"haveAutoSetup": false, "haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-configure-client" "link": "https://proton.me/support/protonmail-bridge-configure-client"
}) });
} }
} }
Rectangle { Rectangle {
anchors.fill: root anchors.fill: root
color: root.colorScheme.background_norm color: root.colorScheme.background_norm
} }
StackLayout { StackLayout {
id: guidePages id: guidePages
anchors.bottomMargin: 70
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 80 anchors.leftMargin: 80
anchors.rightMargin: 80 anchors.rightMargin: 80
anchors.topMargin: 30 anchors.topMargin: 30
anchors.bottomMargin: 70
ColumnLayout {
ColumnLayout { // 0: Client selection // 0: Client selection
id: clientView id: clientView
Layout.fillHeight: true
property int columnWidth: 268 property int columnWidth: 268
Layout.fillHeight: true
spacing: 8 spacing: 8
Label { Label {
@ -112,14 +141,12 @@ Item {
text: qsTr("Setting up email client") text: qsTr("Setting up email client")
type: Label.LabelType.Heading type: Label.LabelType.Heading
} }
Label { Label {
color: root.colorScheme.text_weak
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: address text: address
color: root.colorScheme.text_weak
type: Label.LabelType.Lead type: Label.LabelType.Lead
} }
RowLayout { RowLayout {
Layout.topMargin: 32 - clientView.spacing Layout.topMargin: 32 - clientView.spacing
spacing: 24 spacing: 24
@ -134,185 +161,133 @@ Item {
text: qsTr("Choose an email client") text: qsTr("Choose an email client")
type: Label.LabelType.Body_semibold type: Label.LabelType.Body_semibold
} }
ListView { ListView {
id: clientList id: clientList
Layout.fillHeight: true Layout.fillHeight: true
model: clients
width: clientView.columnWidth width: clientView.columnWidth
model: clients
highlight: Rectangle {
color: root.colorScheme.interaction_default_active
radius: ProtonStyle.context_item_radius
}
delegate: Item { delegate: Item {
implicitWidth: clientRow.width
implicitHeight: clientRow.height implicitHeight: clientRow.height
implicitWidth: clientRow.width
ColumnLayout { ColumnLayout {
id: clientRow id: clientRow
width: clientList.width width: clientList.width
RowLayout { RowLayout {
Layout.topMargin: 12
Layout.bottomMargin: 12 Layout.bottomMargin: 12
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.topMargin: 12
ColorImage { ColorImage {
source: model.iconSource
height: 36 height: 36
source: model.iconSource
sourceSize.height: 36 sourceSize.height: 36
} }
Label { Label {
colorScheme: root.colorScheme
Layout.leftMargin: 12 Layout.leftMargin: 12
colorScheme: root.colorScheme
text: model.name text: model.name
type: Label.LabelType.Body type: Label.LabelType.Body
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
} }
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: {
clientList.currentIndex = index
if (!model.haveAutoSetup) {
root.setupAction(1,index)
}
}
}
}
}
}
onClicked: {
clientList.currentIndex = index;
if (!model.haveAutoSetup) {
root.setupAction(1, index);
}
}
}
}
highlight: Rectangle {
color: root.colorScheme.interaction_default_active
radius: ProtonStyle.context_item_radius
}
}
}
ColumnLayout { ColumnLayout {
id: actionColumn id: actionColumn
visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup
Label { Label {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Choose configuration mode") text: qsTr("Choose configuration mode")
type: Label.LabelType.Body_semibold type: Label.LabelType.Body_semibold
} }
ListView { ListView {
id: actionList id: actionList
Layout.fillHeight: true Layout.fillHeight: true
model: [qsTr("Configure automatically"), qsTr("Configure manually")]
width: clientView.columnWidth 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 { delegate: Item {
implicitWidth: children[0].width
implicitHeight: children[0].height implicitHeight: children[0].height
implicitWidth: children[0].width
ColumnLayout { ColumnLayout {
width: actionList.width width: actionList.width
Label { Label {
Layout.topMargin: 20
Layout.bottomMargin: 20 Layout.bottomMargin: 20
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.topMargin: 20
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: modelData text: modelData
type: Label.LabelType.Body type: Label.LabelType.Body
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
} }
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
actionList.currentIndex = index actionList.currentIndex = index;
root.setupAction(index,clientList.currentIndex) 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 { Button {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Set up later")
flat: true flat: true
text: qsTr("Set up later")
onClicked: { onClicked: {
root.setupAction(-1,-1) root.setupAction(-1, -1);
if (user) { 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
}
} }

View File

@ -1,478 +1,413 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
FocusScope { FocusScope {
id: root id: root
property ColorScheme colorScheme property ColorScheme colorScheme
property alias currentIndex: stackLayout.currentIndex
function reset() { property alias username: usernameTextField.text
stackLayout.currentIndex = 0
loginNormalLayout.reset()
login2FALayout.reset()
login2PasswordLayout.reset()
}
function abort() { function abort() {
root.reset() root.reset();
Backend.loginAbort(usernameTextField.text) Backend.loginAbort(usernameTextField.text);
}
function reset() {
stackLayout.currentIndex = 0;
loginNormalLayout.reset();
login2FALayout.reset();
login2PasswordLayout.reset();
} }
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
property alias username: usernameTextField.text
state: "Page 1" 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 { StackLayout {
id: stackLayout id: stackLayout
function loginFailed() {
signInButton.loading = false;
usernameTextField.enabled = true;
usernameTextField.error = true;
passwordTextField.enabled = true;
passwordTextField.error = true;
}
anchors.fill: parent anchors.fill: parent
function loginFailed() {
signInButton.loading = false
usernameTextField.enabled = true
usernameTextField.error = true
passwordTextField.enabled = true
passwordTextField.error = true
}
Connections { Connections {
target: Backend function onLogin2FAError(_) {
console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError");
function onLoginUsernamePasswordError(errorMsg) { twoFAButton.loading = false;
console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError") twoFactorPasswordTextField.enabled = true;
console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError") twoFactorPasswordTextField.error = true;
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect");
stackLayout.loginFailed() twoFactorPasswordTextField.focus = true;
if (errorMsg!="") errorLabel.text = errorMsg
else errorLabel.text = qsTr("Incorrect login credentials")
} }
function onLogin2FAErrorAbort(_) {
function onLoginFreeUserError() { console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort");
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError") root.reset();
stackLayout.loginFailed() errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
} }
function onLoginConnectionError(errorMsg) {
if (stackLayout.currentIndex == 0 ) {
stackLayout.loginFailed()
}
}
function onLogin2FARequested(username) { function onLogin2FARequested(username) {
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested") console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested");
twoFactorUsernameLabel.text = username twoFactorUsernameLabel.text = username;
stackLayout.currentIndex = 1 stackLayout.currentIndex = 1;
twoFactorPasswordTextField.focus = true twoFactorPasswordTextField.focus = true;
} }
function onLogin2PasswordError(_) {
function onLogin2FAError(errorMsg) { console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError");
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAError") secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
twoFAButton.loading = false secondPasswordTextField.error = true;
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect");
twoFactorPasswordTextField.enabled = true secondPasswordTextField.focus = true;
twoFactorPasswordTextField.error = true
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect")
twoFactorPasswordTextField.focus = true
} }
function onLogin2PasswordErrorAbort(_) {
function onLogin2FAErrorAbort(errorMsg) { console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort");
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort") root.reset();
root.reset() errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
} }
function onLogin2PasswordRequested() { function onLogin2PasswordRequested() {
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested") console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested");
stackLayout.currentIndex = 2 stackLayout.currentIndex = 2;
secondPasswordTextField.focus = true secondPasswordTextField.focus = true;
} }
function onLogin2PasswordError(errorMsg) { function onLoginAlreadyLoggedIn(_) {
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordError") stackLayout.currentIndex = 0;
root.reset();
secondPasswordButton.loading = false
secondPasswordTextField.enabled = true
secondPasswordTextField.error = true
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect")
secondPasswordTextField.focus = true
} }
function onLogin2PasswordErrorAbort(errorMsg) { function onLoginConnectionError(_) {
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort") if (stackLayout.currentIndex === 0) {
root.reset() stackLayout.loginFailed();
errorLabel.text = qsTr("Incorrect login credentials. Please try again.") }
}
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 onLoginFinished(index) { target: Backend
stackLayout.currentIndex = 0
root.reset()
} }
function onLoginAlreadyLoggedIn(index) {
stackLayout.currentIndex = 0
root.reset()
}
}
ColumnLayout { ColumnLayout {
id: loginNormalLayout id: loginNormalLayout
function reset() { function reset() {
signInButton.loading = false signInButton.loading = false;
errorLabel.text = "";
errorLabel.text = "" usernameTextField.enabled = true;
usernameTextField.error = false;
usernameTextField.enabled = true usernameTextField.errorString = "";
usernameTextField.error = false usernameTextField.focus = true;
usernameTextField.errorString = "" passwordTextField.enabled = true;
usernameTextField.focus = true passwordTextField.error = false;
passwordTextField.errorString = "";
passwordTextField.enabled = true passwordTextField.text = "";
passwordTextField.error = false
passwordTextField.errorString = ""
passwordTextField.text = ""
} }
spacing: 0 spacing: 0
Label { Label {
colorScheme: root.colorScheme
text: qsTr("Sign in")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16 Layout.topMargin: 16
colorScheme: root.colorScheme
text: qsTr("Sign in")
type: Label.LabelType.Title type: Label.LabelType.Title
} }
Label { Label {
colorScheme: root.colorScheme
id: subTitle id: subTitle
text: qsTr("Enter your Proton Account details.")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8 Layout.topMargin: 8
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
colorScheme: root.colorScheme
text: qsTr("Enter your Proton Account details.")
type: Label.LabelType.Body type: Label.LabelType.Body
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 36 Layout.topMargin: 36
spacing: 0 spacing: 0
visible: errorLabel.text.length > 0 visible: errorLabel.text.length > 0
ColorImage { ColorImage {
color: root.colorScheme.signal_danger color: root.colorScheme.signal_danger
source: "/qml/icons/ic-exclamation-circle-filled.svg"
height: errorLabel.lineHeight height: errorLabel.lineHeight
source: "/qml/icons/ic-exclamation-circle-filled.svg"
sourceSize.height: errorLabel.lineHeight sourceSize.height: errorLabel.lineHeight
} }
Label { Label {
colorScheme: root.colorScheme
id: errorLabel id: errorLabel
wrapMode: Text.WordWrap Layout.fillWidth: true
Layout.fillWidth: true;
Layout.leftMargin: 4 Layout.leftMargin: 4
color: root.colorScheme.signal_danger color: root.colorScheme.signal_danger
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
}
}
TextField {
colorScheme: root.colorScheme colorScheme: root.colorScheme
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
wrapMode: Text.WordWrap
}
}
TextField {
id: usernameTextField id: usernameTextField
label: qsTr("Email or username")
focus: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
colorScheme: root.colorScheme
focus: true
label: qsTr("Email or username")
validateOnEditingFinished: false 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) { if (str.length === 0) {
return qsTr("Enter email or username") return qsTr("Enter email or username");
} }
return
} }
onAccepted: passwordTextField.forceActiveFocus() onAccepted: passwordTextField.forceActiveFocus()
}
TextField {
colorScheme: root.colorScheme
id: passwordTextField
label: qsTr("Password")
Layout.fillWidth: true
Layout.topMargin: 8
echoMode: TextInput.Password
validateOnEditingFinished: false
onTextChanged: { onTextChanged: {
// remove "invalid username / password error" // remove "invalid username / password error"
if (error || errorLabel.text.length > 0) { if (error || errorLabel.text.length > 0) {
errorLabel.text = "" errorLabel.text = "";
usernameTextField.error = false usernameTextField.error = false;
passwordTextField.error = false passwordTextField.error = false;
} }
} }
}
TextField {
id: passwordTextField
Layout.fillWidth: true
Layout.topMargin: 8
colorScheme: root.colorScheme
echoMode: TextInput.Password
label: qsTr("Password")
validateOnEditingFinished: false
validator: function (str) { validator: function (str) {
if (str.length === 0) { if (str.length === 0) {
return qsTr("Enter password") return qsTr("Enter password");
} }
return
} }
onAccepted: signInButton.checkAndSignIn() onAccepted: signInButton.checkAndSignIn()
onTextChanged: {
// remove "invalid username / password error"
if (error || errorLabel.text.length > 0) {
errorLabel.text = "";
usernameTextField.error = false;
passwordTextField.error = false;
}
}
}
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));
} }
Button {
colorScheme: root.colorScheme
id: signInButton
text: loading ? qsTr("Signing in") : qsTr("Sign in")
enabled: !loading
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
onClicked: checkAndSignIn()
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 {
colorScheme: root.colorScheme colorScheme: root.colorScheme
textFormat: Text.StyledText enabled: !loading
text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) text: loading ? qsTr("Signing in") : qsTr("Sign in")
onClicked: {
checkAndSignIn();
}
}
Label {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 24 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 type: Label.LabelType.Body
onLinkActivated: { onLinkActivated: {
Qt.openUrlExternally(link) Qt.openUrlExternally(link);
} }
} }
} }
ColumnLayout { ColumnLayout {
id: login2FALayout id: login2FALayout
function reset() { function reset() {
twoFAButton.loading = false twoFAButton.loading = false;
twoFactorPasswordTextField.enabled = true;
twoFactorPasswordTextField.enabled = true twoFactorPasswordTextField.error = false;
twoFactorPasswordTextField.error = false twoFactorPasswordTextField.errorString = "";
twoFactorPasswordTextField.errorString = "" twoFactorPasswordTextField.text = "";
twoFactorPasswordTextField.text = ""
} }
spacing: 0 spacing: 0
Label { Label {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 16
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Two-factor authentication") text: qsTr("Two-factor authentication")
Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter
type: Label.LabelType.Heading type: Label.LabelType.Heading
} }
Label { Label {
colorScheme: root.colorScheme
id: twoFactorUsernameLabel id: twoFactorUsernameLabel
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.topMargin: 8 Layout.topMargin: 8
type: Label.LabelType.Lead
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
}
TextField {
colorScheme: root.colorScheme colorScheme: root.colorScheme
type: Label.LabelType.Lead
}
TextField {
id: twoFactorPasswordTextField id: twoFactorPasswordTextField
label: qsTr("Two-factor code")
assistiveText: qsTr("Enter the 6-digit code")
validateOnEditingFinished: false
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
assistiveText: qsTr("Enter the 6-digit code")
colorScheme: root.colorScheme
label: qsTr("Two-factor code")
validateOnEditingFinished: false
validator: function (str) { validator: function (str) {
if (str.length === 0) { if (str.length === 0) {
return qsTr("Enter the 6-digit code") return qsTr("Enter the 6-digit code");
}
}
onTextChanged: {
if (text.length >= 6) {
twoFAButton.onClicked()
} }
} }
onAccepted: { onAccepted: {
twoFAButton.onClicked() twoFAButton.onClicked();
}
onTextChanged: {
if (text.length >= 6) {
twoFAButton.onClicked();
}
} }
} }
Button { Button {
colorScheme: root.colorScheme
id: twoFAButton id: twoFAButton
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
enabled: !loading
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
colorScheme: root.colorScheme
enabled: !loading
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
onClicked: { onClicked: {
twoFactorPasswordTextField.validate() twoFactorPasswordTextField.validate();
if (twoFactorPasswordTextField.error) { if (twoFactorPasswordTextField.error) {
return return;
} }
twoFactorPasswordTextField.enabled = false;
twoFactorPasswordTextField.enabled = false loading = true;
loading = true Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text));
Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
} }
} }
} }
ColumnLayout { ColumnLayout {
id: login2PasswordLayout id: login2PasswordLayout
function reset() { function reset() {
secondPasswordButton.loading = false secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
secondPasswordTextField.enabled = true secondPasswordTextField.error = false;
secondPasswordTextField.error = false secondPasswordTextField.errorString = "";
secondPasswordTextField.errorString = "" secondPasswordTextField.text = "";
secondPasswordTextField.text = ""
} }
spacing: 0 spacing: 0
Label { Label {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 16
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Unlock your mailbox") text: qsTr("Unlock your mailbox")
Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter
type: Label.LabelType.Heading type: Label.LabelType.Heading
} }
TextField { TextField {
colorScheme: root.colorScheme
id: secondPasswordTextField id: secondPasswordTextField
label: qsTr("Mailbox password")
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight
colorScheme: root.colorScheme
echoMode: TextInput.Password echoMode: TextInput.Password
label: qsTr("Mailbox password")
validateOnEditingFinished: false validateOnEditingFinished: false
validator: function (str) { validator: function (str) {
if (str.length === 0) { if (str.length === 0) {
return qsTr("Enter password") return qsTr("Enter password");
} }
return
} }
onAccepted: { onAccepted: {
secondPasswordButton.onClicked() secondPasswordButton.onClicked();
} }
} }
Button { Button {
colorScheme: root.colorScheme
id: secondPasswordButton id: secondPasswordButton
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
enabled: !loading
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
colorScheme: root.colorScheme
enabled: !loading
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
onClicked: { onClicked: {
secondPasswordTextField.validate() secondPasswordTextField.validate();
if (secondPasswordTextField.error) { if (secondPasswordTextField.error) {
return return;
} }
secondPasswordTextField.enabled = false;
secondPasswordTextField.enabled = false loading = true;
loading = true Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text));
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
}
}
]
} }

View File

@ -1,194 +1,169 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
Dialog { Dialog {
id: root id: root
shouldShow: Backend.showSplashScreen
modal: true
topPadding : 0
leftPadding: 0 leftPadding: 0
modal: true
rightPadding: 0 rightPadding: 0
shouldShow: Backend.showSplashScreen
topPadding: 0
ColumnLayout { ColumnLayout {
spacing: 20 spacing: 20
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
sourceSize.width: 384
sourceSize.height: 144
Layout.preferredWidth: 384
Layout.preferredHeight: 144 Layout.preferredHeight: 144
Layout.preferredWidth: 384
source: "./icons/img-splash.png" source: "./icons/img-splash.png"
sourceSize.height: 144
sourceSize.width: 384
} }
Label { Label {
colorScheme: root.colorScheme; Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignHCenter;
Layout.leftMargin: 24 Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.preferredWidth: 336 Layout.preferredWidth: 336
Layout.rightMargin: 24
type: Label.Title colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("What's new in Bridge") text: qsTr("What's new in Bridge")
type: Label.Title
} }
RowLayout { RowLayout {
width: root.width width: root.width
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
width: 24
Layout.leftMargin: 32 Layout.leftMargin: 32
Layout.rightMargin: 16 Layout.rightMargin: 16
width: 24
Image { Image {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 24
sourceSize.height: 24
source: "./icons/ic-splash-check.svg" source: "./icons/ic-splash-check.svg"
sourceSize.height: 24
sourceSize.width: 24
} }
} }
Label { Label {
colorScheme: root.colorScheme Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter;
Layout.preferredWidth: 264
Layout.leftMargin: 0 Layout.leftMargin: 0
Layout.preferredWidth: 264
Layout.rightMargin: 24 Layout.rightMargin: 24
wrapMode: Text.WordWrap colorScheme: root.colorScheme
type: Label.Body
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
textFormat: Text.StyledText
text: qsTr("<b>New IMAP engine</b><br/>For improved stability and performance.") text: qsTr("<b>New IMAP engine</b><br/>For improved stability and performance.")
textFormat: Text.StyledText
type: Label.Body
wrapMode: Text.WordWrap
} }
} }
RowLayout { RowLayout {
width: root.width width: root.width
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
width: 24
Layout.leftMargin: 32 Layout.leftMargin: 32
Layout.rightMargin: 16 Layout.rightMargin: 16
width: 24
Image { Image {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 24
sourceSize.height: 24
source: "./icons/ic-splash-check.svg" source: "./icons/ic-splash-check.svg"
sourceSize.height: 24
sourceSize.width: 24
} }
} }
Label { Label {
colorScheme: root.colorScheme Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter;
Layout.preferredWidth: 264
Layout.leftMargin: 0 Layout.leftMargin: 0
Layout.preferredWidth: 264
Layout.rightMargin: 24 Layout.rightMargin: 24
wrapMode: Text.WordWrap colorScheme: root.colorScheme
type: Label.Body
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
textFormat: Text.StyledText
text: qsTr("<b>Faster than ever</b><br/>Up to 10x faster syncing and receiving.") text: qsTr("<b>Faster than ever</b><br/>Up to 10x faster syncing and receiving.")
textFormat: Text.StyledText
type: Label.Body
wrapMode: Text.WordWrap
} }
} }
RowLayout { RowLayout {
width: root.width width: root.width
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
width: 24
Layout.leftMargin: 32 Layout.leftMargin: 32
Layout.rightMargin: 16 Layout.rightMargin: 16
width: 24
Image { Image {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
sourceSize.width: 24
sourceSize.height: 24
source: "./icons/ic-splash-check.svg" source: "./icons/ic-splash-check.svg"
sourceSize.height: 24
sourceSize.width: 24
} }
} }
Label { Label {
colorScheme: root.colorScheme Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter;
Layout.preferredWidth: 264
Layout.leftMargin: 0 Layout.leftMargin: 0
Layout.preferredWidth: 264
Layout.rightMargin: 24 Layout.rightMargin: 24
wrapMode: Text.WordWrap colorScheme: root.colorScheme
type: Label.Body
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
textFormat: Text.StyledText
text: qsTr("<b>Extra security</b><br/>New, encrypted local database and keychain improvements.") text: qsTr("<b>Extra security</b><br/>New, encrypted local database and keychain improvements.")
textFormat: Text.StyledText
type: Label.Body
wrapMode: Text.WordWrap
} }
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 24 Layout.leftMargin: 24
Layout.rightMargin: 24 Layout.rightMargin: 24
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: "Got it" text: "Got it"
onClicked: Backend.showSplashScreen = false onClicked: Backend.showSplashScreen = false
} }
Label { Label {
colorScheme: root.colorScheme Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter;
Layout.preferredWidth: 336
Layout.leftMargin: 24 Layout.leftMargin: 24
Layout.preferredWidth: 336
Layout.rightMargin: 24 Layout.rightMargin: 24
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Note that your client will redownload all the emails.<br/>") + link("https://proton.me/blog/new-proton-mail-bridge", qsTr("Learn more about new Bridge."))
textFormat: Text.StyledText
type: Label.Body
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
type: Label.Body onLinkActivated: function (link) {
horizontalAlignment: Text.AlignHCenter Qt.openUrlExternally(link);
textFormat: Text.StyledText }
text: qsTr("Note that your client will redownload all the emails.<br/>") + link("https://proton.me/blog/new-proton-mail-bridge", qsTr("Learn more about new Bridge."))
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
} }
} }
} }

View File

@ -1,111 +1,93 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl import QtQuick.Controls.impl
import Proton import Proton
import Notifications import Notifications
Item { Item {
id: root 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 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 implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
NotificationFilter { NotificationFilter {
id: notificationFilter id: notificationFilter
blacklist: root.notificationBlacklist
source: root.notifications ? root.notifications.all : undefined source: root.notifications ? root.notifications.all : undefined
whitelist: root.notificationWhitelist whitelist: root.notificationWhitelist
blacklist: root.notificationBlacklist
onTopmostChanged: { onTopmostChanged: {
if (!topmost) { if (!topmost) {
image.source = "/qml/icons/ic-connected.svg" image.source = "/qml/icons/ic-connected.svg";
image.color = root.colorScheme.signal_success image.color = root.colorScheme.signal_success;
label.text = qsTr("Connected") label.text = qsTr("Connected");
label.color = root.colorScheme.signal_success label.color = root.colorScheme.signal_success;
return; return;
} }
image.source = topmost.icon;
image.source = topmost.icon label.text = topmost.brief;
label.text = topmost.brief
switch (topmost.type) { switch (topmost.type) {
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
image.color = root.colorScheme.signal_danger image.color = root.colorScheme.signal_danger;
label.color = root.colorScheme.signal_danger label.color = root.colorScheme.signal_danger;
break; break;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
image.color = root.colorScheme.signal_warning image.color = root.colorScheme.signal_warning;
label.color = root.colorScheme.signal_warning label.color = root.colorScheme.signal_warning;
break; break;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
image.color = root.colorScheme.signal_success image.color = root.colorScheme.signal_success;
label.color = root.colorScheme.signal_success label.color = root.colorScheme.signal_success;
break; break;
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
image.color = root.colorScheme.signal_info image.color = root.colorScheme.signal_info;
label.color = root.colorScheme.signal_info label.color = root.colorScheme.signal_info;
break; break;
} }
} }
} }
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 8 spacing: 8
ColorImage { ColorImage {
id: image id: image
width: 16
height: 16
sourceSize.width: width
sourceSize.height: height
source: "/qml/icons/ic-connected.svg"
color: root.colorScheme.signal_success color: root.colorScheme.signal_success
height: 16
source: "/qml/icons/ic-connected.svg"
sourceSize.height: height
sourceSize.width: width
width: 16
} }
Label { Label {
colorScheme: root.colorScheme
id: label id: label
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: qsTr("Connected")
color: root.colorScheme.signal_success color: root.colorScheme.signal_success
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignLeft
text: qsTr("Connected")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
} }
} }
} }

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG // Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge. // This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful, // Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
Item { Item {
@ -34,24 +28,46 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
Rectangle { states: [
color: root.colorScheme.background_norm 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.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
visible: signInItem.currentIndex === 0
visible: signInItem.currentIndex == 0
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
columnSpacing: 0 columnSpacing: 0
rowSpacing: 0
columns: 3 columns: 3
rowSpacing: 0
// top margin // top margin
Item { Item {
@ -66,134 +82,116 @@ Item {
// left margin // left margin
Item { Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height Layout.preferredHeight: welcomeContentItem.height
} }
ColumnLayout { ColumnLayout {
id: welcomeContentItem id: welcomeContentItem
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
Image { Image {
source: colorScheme.welcome_img
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16 Layout.topMargin: 16
source: colorScheme.welcome_img
sourceSize.height: 148 sourceSize.height: 148
sourceSize.width: 264 sourceSize.width: 264
} }
Label { Label {
colorScheme: root.colorScheme
text: qsTr("Welcome to\nProton Mail Bridge")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to\nProton Mail Bridge")
type: Label.LabelType.Heading type: Label.LabelType.Heading
} }
Label { Label {
colorScheme: root.colorScheme
id: longTextLabel 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.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16
Layout.preferredWidth: 320 Layout.preferredWidth: 320
Layout.topMargin: 16
wrapMode: Text.WordWrap colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter 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 type: Label.LabelType.Body
wrapMode: Text.WordWrap
} }
} }
// Right margin // Right margin
Item { Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height Layout.preferredHeight: welcomeContentItem.height
} }
// bottom margin // bottom margin
Item { Item {
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
Image { Image {
id: logoImage id: logoImage
source: colorScheme.logo_img
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.topMargin: 48
anchors.bottomMargin: 48 anchors.bottomMargin: 48
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 48
source: colorScheme.logo_img
sourceSize.height: 25 sourceSize.height: 25
sourceSize.width: 200 sourceSize.width: 200
} }
} }
} }
} }
Rectangle { Rectangle {
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth implicitWidth: children[0].implicitWidth
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4 Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
Button { Button {
colorScheme: root.colorScheme
anchors.left: parent.left
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 80
anchors.left: parent.left
anchors.leftMargin: 80 anchors.leftMargin: 80
anchors.rightMargin: 80 anchors.rightMargin: 80
anchors.topMargin: 80 anchors.topMargin: 80
anchors.bottomMargin: 80 colorScheme: root.colorScheme
visible: signInItem.currentIndex != 0
secondary: true secondary: true
text: qsTr("Back") text: qsTr("Back")
visible: signInItem.currentIndex != 0
onClicked: { onClicked: {
signInItem.abort() signInItem.abort();
} }
} }
} }
GridLayout { GridLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
columnSpacing: 0 columnSpacing: 0
rowSpacing: 0
columns: 3 columns: 3
rowSpacing: 0
// top margin // top margin
Item { Item {
@ -208,69 +206,40 @@ Item {
// left margin // left margin
Item { Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height Layout.preferredHeight: signInItem.height
} }
SignIn { SignIn {
id: signInItem id: signInItem
colorScheme: root.colorScheme
Layout.preferredWidth: 320
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: 320
colorScheme: root.colorScheme
focus: true focus: true
username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : "" username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : ""
} }
// Right margin // Right margin
Item { Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height Layout.preferredHeight: signInItem.height
} }
// bottom margin // bottom margin
Item { Item {
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
} }
} }
Item { Item {
Layout.fillHeight: true 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
}
}
]
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
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"
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
import QtQuick.Window
import "../Proton"
Window {
width: 800
height: 600
visible: true
TestComponents {
anchors.fill: parent
colorScheme: ProtonStyle.currentStyle
}
onClosing: {
Qt.quit()
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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"
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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"
}
}
}