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
//
// 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
Item {
id: root
property ColorScheme colorScheme
property var user
enum ViewType {
SmallView,
LargeView
}
property var _spacing: 12 * ProtonStyle.px
property color progressColor : {
if (!root.enabled) return root.colorScheme.text_weak
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
if (root.user && root.user.isSyncing) return root.colorScheme.text_weak
if (root.progressRatio < .50) return root.colorScheme.signal_success
if (root.progressRatio < .75) return root.colorScheme.signal_warning
return root.colorScheme.signal_danger
property ColorScheme colorScheme
property color progressColor: {
if (!root.enabled)
return root.colorScheme.text_weak;
if (root.type === AccountDelegate.SmallView)
return root.colorScheme.text_weak;
if (root.user && root.user.isSyncing)
return root.colorScheme.text_weak;
if (root.progressRatio < .50)
return root.colorScheme.signal_success;
if (root.progressRatio < .75)
return root.colorScheme.signal_warning;
return root.colorScheme.signal_danger;
}
property real progressRatio: {
if (!root.user)
return 0
return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes)
return 0;
return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes);
}
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
property var type: AccountDelegate.SmallView
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
function reasonableFraction(used, total){
var usedSafe = root.reasonableBytes(used)
var totalSafe = root.reasonableBytes(total)
if (totalSafe == 0 || usedSafe == 0) return 0
if (totalSafe <= usedSafe) return 1
return usedSafe / totalSafe
}
function reasonableBytes(bytes){
var safeBytes = bytes+0
if (safeBytes != bytes) return 0
if (safeBytes < 0) return 0
return Math.ceil(safeBytes)
}
function spaceWithUnits(bytes){
if (bytes*1 !== bytes || bytes == 0 ) return "0 kB"
var units = ['B',"kB", "MB", "GB", "TB"];
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
}
property var user
function primaryEmail() {
return root.user ? root.user.primaryEmailOrUsername() : ""
return root.user ? root.user.primaryEmailOrUsername() : "";
}
function reasonableBytes(bytes) {
const safeBytes = bytes + 0;
if (safeBytes !== bytes)
return 0;
if (safeBytes < 0)
return 0;
return Math.ceil(safeBytes);
}
function reasonableFraction(used, total) {
const usedSafe = root.reasonableBytes(used);
const totalSafe = root.reasonableBytes(total);
if (totalSafe === 0 || usedSafe === 0)
return 0;
if (totalSafe <= usedSafe)
return 1;
return usedSafe / totalSafe;
}
function spaceWithUnits(bytes) {
if (bytes * 1 !== bytes || bytes === 0)
return "0 kB";
const units = ['B', "kB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes * 10 / Math.pow(1024, i)) / 10 + " " + units[i];
}
// width expected to be set by parent object
implicitHeight : children[0].implicitHeight
enum ViewType{
SmallView, LargeView
}
property var type : AccountDelegate.SmallView
implicitHeight: children[0].implicitHeight
RowLayout {
spacing: root._spacing
anchors {
top: root.top
left: root.left
right: root.right
top: root.top
}
Rectangle {
id: avatar
Layout.fillHeight: true
Layout.preferredWidth: height
color: root.colorScheme.background_avatar
radius: ProtonStyle.avatar_radius
color: root.colorScheme.background_avatar
Label {
colorScheme: root.colorScheme
anchors.fill: parent
text: root.user ? root.user.avatarText.toUpperCase(): ""
color: "#FFFFFF"
colorScheme: root.colorScheme
font.weight: Font.Normal
horizontalAlignment: Qt.AlignHCenter
text: root.user ? root.user.avatarText.toUpperCase() : ""
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Body
case AccountDelegate.LargeView: return Label.Title
case AccountDelegate.SmallView:
return Label.Body;
case AccountDelegate.LargeView:
return Label.Title;
}
}
font.weight: Font.Normal
color: "#FFFFFF"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}
ColumnLayout {
id: account
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0
Label {
id: labelEmail
Layout.maximumWidth: root.width - (root._spacing + avatar.width)
colorScheme: root.colorScheme
elide: Text.ElideMiddle
text: primaryEmail()
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Body
case AccountDelegate.LargeView: return Label.Title
case AccountDelegate.SmallView:
return Label.Body;
case AccountDelegate.LargeView:
return Label.Title;
}
}
elide: Text.ElideMiddle
MouseArea {
id: labelArea
anchors.fill:parent
anchors.fill: parent
hoverEnabled: true
}
ToolTip {
id: toolTipEmail
visible: labelArea.containsMouse && labelEmail.truncated
text: primaryEmail()
delay: 1000
text: primaryEmail()
visible: labelArea.containsMouse && labelEmail.truncated
background: Rectangle {
border.color: root.colorScheme.background_strong
color: root.colorScheme.background_norm
}
contentItem: Text {
color: root.colorScheme.text_norm
text: toolTipEmail.text
}
}
}
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 }
Item {
implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0
}
RowLayout {
spacing: 0
Label {
color: root.progressColor
colorScheme: root.colorScheme
text: {
if (!root.user)
return qsTr("Signed out")
return qsTr("Signed out");
switch (root.user.state) {
case EUserState.SignedOut:
default:
return qsTr("Signed out")
return qsTr("Signed out");
case EUserState.Locked:
return qsTr("Connecting") + dotsTimer.dots
return qsTr("Connecting") + dotsTimer.dots;
case EUserState.Connected:
if (root.user.isSyncing)
return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots
return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots;
else
return root.usedSpace
return root.usedSpace;
}
}
Timer { // dots animation while connecting & syncing.
id:dotsTimer
property string dots: ""
interval: 500;
repeat: true;
running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
onTriggered: {
dots += "."
if (dots.length > 3)
dots = ""
}
onRunningChanged: {
dots = ""
}
}
color: root.progressColor
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
case AccountDelegate.LargeView: return Label.Body
case AccountDelegate.SmallView:
return Label.Caption;
case AccountDelegate.LargeView:
return Label.Body;
}
}
Timer {
// dots animation while connecting & syncing.
id: dotsTimer
property string dots: ""
interval: 500
repeat: true
running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
onRunningChanged: {
dots = "";
}
onTriggered: {
dots += ".";
if (dots.length > 3)
dots = "";
}
}
}
Label {
colorScheme: root.colorScheme
text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
color: root.colorScheme.text_weak
colorScheme: root.colorScheme
text: root.user && root.user.state === EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
case AccountDelegate.LargeView: return Label.Body
case AccountDelegate.SmallView:
return Label.Caption;
case AccountDelegate.LargeView:
return Label.Body;
}
}
}
}
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 }
Item {
implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0
}
Rectangle {
id: progress_bar
visible: root.user ? root.type == AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
color: root.colorScheme.border_weak
height: 4 * ProtonStyle.px
radius: ProtonStyle.progress_bar_radius
color: root.colorScheme.border_weak
visible: root.user ? root.type === AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
Rectangle {
id: progress_bar_filled
radius: ProtonStyle.progress_bar_radius
color: root.progressColor
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false
radius: ProtonStyle.progress_bar_radius
visible: root.user ? parent.visible && (root.user.state === EUserState.Connected) : false
width: Math.min(1, Math.max(0.02, root.progressRatio)) * parent.width
anchors {
top : parent.top
bottom : parent.bottom
left : parent.left
bottom: parent.bottom
left: parent.left
top: parent.top
}
width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width
}
}
}
Item {
Layout.fillWidth: true
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.impl
import Proton
SettingsView {
@ -31,144 +25,138 @@ SettingsView {
fillHeight: false
Label {
Layout.fillWidth: true
colorScheme: root.colorScheme
text: qsTr("Settings")
type: Label.Heading
Layout.fillWidth: true
}
SettingsItem {
id: autoUpdate
colorScheme: root.colorScheme
text: qsTr("Automatic updates")
description: qsTr("Bridge will automatically update in the background.")
type: SettingsItem.Toggle
checked: Backend.isAutomaticUpdateOn
onClicked: Backend.toggleAutomaticUpdate(!autoUpdate.checked)
Layout.fillWidth: true
}
checked: Backend.isAutomaticUpdateOn
colorScheme: root.colorScheme
description: qsTr("Bridge will automatically update in the background.")
text: qsTr("Automatic updates")
type: SettingsItem.Toggle
onClicked: Backend.toggleAutomaticUpdate(!autoUpdate.checked)
}
SettingsItem {
id: autostart
colorScheme: root.colorScheme
text: qsTr("Open on startup")
description: qsTr("Bridge will open upon startup.")
type: SettingsItem.Toggle
checked: Backend.isAutostartOn
onClicked: {
autostart.loading = true
Backend.toggleAutostart(!autostart.checked)
}
Connections{
target: Backend
function onToggleAutostartFinished() {
autostart.loading = false
}
}
Layout.fillWidth: true
}
checked: Backend.isAutostartOn
colorScheme: root.colorScheme
description: qsTr("Bridge will open upon startup.")
text: qsTr("Open on startup")
type: SettingsItem.Toggle
onClicked: {
autostart.loading = true;
Backend.toggleAutostart(!autostart.checked);
}
Connections {
function onToggleAutostartFinished() {
autostart.loading = false;
}
target: Backend
}
}
SettingsItem {
id: beta
colorScheme: root.colorScheme
text: qsTr("Beta access")
description: qsTr("Be among the first to try new features.")
type: SettingsItem.Toggle
Layout.fillWidth: true
checked: Backend.isBetaEnabled
colorScheme: root.colorScheme
description: qsTr("Be among the first to try new features.")
text: qsTr("Beta access")
type: SettingsItem.Toggle
onClicked: {
if (!beta.checked) {
root.notifications.askEnableBeta()
root.notifications.askEnableBeta();
} else {
Backend.toggleBeta(false)
Backend.toggleBeta(false);
}
}
Layout.fillWidth: true
}
RowLayout {
ColorImage {
Layout.alignment: Qt.AlignCenter
source: root._isAdvancedShown ? "/qml/icons/ic-chevron-down.svg" : "/qml/icons/ic-chevron-right.svg"
color: root.colorScheme.interaction_norm
height: root.colorScheme.body_font_size
source: root._isAdvancedShown ? "/qml/icons/ic-chevron-down.svg" : "/qml/icons/ic-chevron-right.svg"
sourceSize.height: root.colorScheme.body_font_size
MouseArea {
anchors.fill: parent
onClicked: root._isAdvancedShown = !root._isAdvancedShown
}
}
Label {
id: advSettLabel
color: root.colorScheme.interaction_norm
colorScheme: root.colorScheme
text: qsTr("Advanced settings")
color: root.colorScheme.interaction_norm
type: Label.Body
MouseArea {
anchors.fill: parent
onClicked: root._isAdvancedShown = !root._isAdvancedShown
}
}
}
SettingsItem {
id: keychains
visible: root._isAdvancedShown && Backend.availableKeychain.length > 1
colorScheme: root.colorScheme
text: qsTr("Change keychain")
description: qsTr("Change which keychain Bridge uses as default")
actionText: qsTr("Change")
type: SettingsItem.Button
checked: Backend.isDoHEnabled
onClicked: root.parent.showKeychainSettings()
Layout.fillWidth: true
}
actionText: qsTr("Change")
checked: Backend.isDoHEnabled
colorScheme: root.colorScheme
description: qsTr("Change which keychain Bridge uses as default")
text: qsTr("Change keychain")
type: SettingsItem.Button
visible: root._isAdvancedShown && Backend.availableKeychain.length > 1
onClicked: root.parent.showKeychainSettings()
}
SettingsItem {
id: doh
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Alternative routing")
description: qsTr("If 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
}
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 {
id: darkMode
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Dark mode")
description: qsTr("Choose dark color theme.")
type: SettingsItem.Toggle
checked: Backend.colorSchemeName == "dark"
onClicked: Backend.changeColorScheme( darkMode.checked ? "light" : "dark")
Layout.fillWidth: true
}
checked: Backend.colorSchemeName === "dark"
colorScheme: root.colorScheme
description: qsTr("Choose dark color theme.")
text: qsTr("Dark mode")
type: SettingsItem.Toggle
visible: root._isAdvancedShown
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
}
SettingsItem {
id: allMail
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Show All Mail")
description: qsTr("Choose to list the All Mail folder in your local client.")
type: SettingsItem.Toggle
checked: Backend.isAllMailVisible
onClicked: root.notifications.askChangeAllMailVisibility(Backend.isAllMailVisible)
Layout.fillWidth: true
}
checked: Backend.isAllMailVisible
colorScheme: root.colorScheme
description: qsTr("Choose to list the All Mail folder in your local client.")
text: qsTr("Show All Mail")
type: SettingsItem.Toggle
visible: root._isAdvancedShown
onClicked: root.notifications.askChangeAllMailVisibility(Backend.isAllMailVisible)
}
SettingsItem {
id: telemetry
Layout.fillWidth: true
@ -181,73 +169,68 @@ SettingsView {
onClicked: Backend.toggleIsTelemetryDisabled(telemetry.checked)
}
SettingsItem {
id: ports
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Default ports")
actionText: qsTr("Change")
description: qsTr("Choose which ports are used by default.")
type: SettingsItem.Button
onClicked: root.parent.showPortSettings()
Layout.fillWidth: true
}
actionText: qsTr("Change")
colorScheme: root.colorScheme
description: qsTr("Choose which ports are used by default.")
text: qsTr("Default ports")
type: SettingsItem.Button
visible: root._isAdvancedShown
onClicked: root.parent.showPortSettings()
}
SettingsItem {
id: imap
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Connection mode")
actionText: qsTr("Change")
description: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.")
type: SettingsItem.Button
onClicked: root.parent.showConnectionModeSettings()
Layout.fillWidth: true
}
actionText: qsTr("Change")
colorScheme: root.colorScheme
description: qsTr("Change the protocol Bridge and the email client use to connect for IMAP and SMTP.")
text: qsTr("Connection mode")
type: SettingsItem.Button
visible: root._isAdvancedShown
onClicked: root.parent.showConnectionModeSettings()
}
SettingsItem {
id: cache
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Local cache")
actionText: qsTr("Configure")
description: qsTr("Configure Bridge's local cache.")
type: SettingsItem.Button
onClicked: root.parent.showLocalCacheSettings()
Layout.fillWidth: true
}
actionText: qsTr("Configure")
colorScheme: root.colorScheme
description: qsTr("Configure Bridge's local cache.")
text: qsTr("Local cache")
type: SettingsItem.Button
visible: root._isAdvancedShown
onClicked: root.parent.showLocalCacheSettings()
}
SettingsItem {
id: exportTLSCertificates
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Export TLS certificates")
actionText: qsTr("Export")
description: qsTr("Export the TLS private key and certificate used by the IMAP and SMTP servers.")
type: SettingsItem.Button
onClicked: {
Backend.exportTLSCertificates()
}
Layout.fillWidth: true
actionText: qsTr("Export")
colorScheme: root.colorScheme
description: qsTr("Export the TLS private key and certificate used by the IMAP and SMTP servers.")
text: qsTr("Export TLS certificates")
type: SettingsItem.Button
visible: root._isAdvancedShown
onClicked: {
Backend.exportTLSCertificates();
}
}
SettingsItem {
id: reset
visible: root._isAdvancedShown
colorScheme: root.colorScheme
text: qsTr("Reset Bridge")
actionText: qsTr("Reset")
description: qsTr("Remove all accounts, clear cached data, and restore the original settings.")
type: SettingsItem.Button
onClicked: {
root.notifications.askResetBridge()
}
Layout.fillWidth: true
actionText: qsTr("Reset")
colorScheme: root.colorScheme
description: qsTr("Remove all accounts, clear cached data, and restore the original settings.")
text: qsTr("Reset Bridge")
type: SettingsItem.Button
visible: root._isAdvancedShown
onClicked: {
root.notifications.askResetBridge();
}
}
}

View File

@ -1,126 +1,110 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
SettingsView {
id: root
fillHeight: true
Label {
Layout.fillWidth: true
colorScheme: root.colorScheme
text: qsTr("Help")
type: Label.Heading
Layout.fillWidth: true
}
SettingsItem {
id: setupPage
colorScheme: root.colorScheme
text: qsTr("Installation and setup")
actionText: qsTr("Go to help topics")
Layout.fillWidth: true
actionIcon: "/qml/icons/ic-external-link.svg"
actionText: qsTr("Go to help topics")
colorScheme: root.colorScheme
description: qsTr("Get help setting up your client with our instructions and FAQs.")
text: qsTr("Installation and setup")
type: SettingsItem.PrimaryButton
onClicked: {
Backend.notifyKBArticleClicked("https://proton.me/support/bridge");
Qt.openUrlExternally("https://proton.me/support/bridge")}
Layout.fillWidth: true
Qt.openUrlExternally("https://proton.me/support/bridge");
}
}
SettingsItem {
id: checkUpdates
colorScheme: root.colorScheme
text: qsTr("Updates")
Layout.fillWidth: true
actionText: qsTr("Check now")
colorScheme: root.colorScheme
description: qsTr("Check that you're using the latest version of Bridge. To stay up to date, enable auto-updates in settings.")
text: qsTr("Updates")
type: SettingsItem.Button
onClicked: {
checkUpdates.loading = true
Backend.checkUpdates()
checkUpdates.loading = true;
Backend.checkUpdates();
}
Connections {
function onCheckUpdatesFinished() {
checkUpdates.loading = false;
}
target: Backend
function onCheckUpdatesFinished() { checkUpdates.loading = false }
}
Layout.fillWidth: true
}
SettingsItem {
id: logs
colorScheme: root.colorScheme
text: qsTr("Logs")
actionText: qsTr("View logs")
description: qsTr("Open and review logs to troubleshoot.")
type: SettingsItem.Button
onClicked: Qt.openUrlExternally(Backend.logsPath)
Layout.fillWidth: true
}
actionText: qsTr("View logs")
colorScheme: root.colorScheme
description: qsTr("Open and review logs to troubleshoot.")
text: qsTr("Logs")
type: SettingsItem.Button
onClicked: Qt.openUrlExternally(Backend.logsPath)
}
SettingsItem {
id: reportBug
colorScheme: root.colorScheme
text: qsTr("Report a problem")
actionText: qsTr("Report a problem")
description: qsTr("Something not working as expected? Let us know.")
type: SettingsItem.Button
onClicked: {
Backend.updateCurrentMailClient()
Backend.notifyReportBugClicked()
root.parent.showBugReport()
}
Layout.fillWidth: true
actionText: qsTr("Report a problem")
colorScheme: root.colorScheme
description: qsTr("Something not working as expected? Let us know.")
text: qsTr("Report a problem")
type: SettingsItem.Button
onClicked: {
Backend.updateCurrentMailClient();
Backend.notifyReportBugClicked();
root.parent.showBugReport();
}
}
// fill height so the footer label will be always attached to the bottom
// fill height so the footer label will always be attached to the bottom
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
Label {
Layout.alignment: Qt.AlignHCenter
colorScheme: root.colorScheme
type: Label.Caption
color: root.colorScheme.text_weak
textFormat: Text.StyledText
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("%1 v%2 (%3)<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").
arg(Backend.appname).
arg(Backend.version).
arg(Backend.tag).
arg(Backend.buildYear()).
arg(Backend.vendor).
arg(link(Backend.licensePath, qsTr("License"))).
arg(link(Backend.dependencyLicensesLink, qsTr("Dependencies"))).
arg(link(Backend.releaseNotesLink, qsTr("Release notes")))
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
onLinkActivated: function (link) {
Qt.openUrlExternally(link);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,97 +1,96 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Templates as T
T.CheckBox {
property ColorScheme colorScheme
property bool error: false
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
property ColorScheme colorScheme
property bool error: false
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding)
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
padding: 0
spacing: 8
contentItem: CheckLabel {
color: {
if (!enabled) {
return control.colorScheme.text_disabled;
}
if (error) {
return control.colorScheme.signal_danger;
}
return control.colorScheme.text_norm;
}
font.family: ProtonStyle.font_family
font.letterSpacing: ProtonStyle.body_letter_spacing
font.pixelSize: ProtonStyle.body_font_size
font.weight: ProtonStyle.fontWeight_400
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
lineHeight: ProtonStyle.body_line_height
lineHeightMode: Text.FixedHeight
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
}
indicator: Rectangle {
implicitWidth: 20
border.color: {
if (!control.enabled) {
return control.colorScheme.field_disabled;
}
if (control.error) {
return control.colorScheme.signal_danger;
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover;
}
return control.colorScheme.field_norm;
}
border.width: control.checked ? 0 : 1
color: {
if (!checked) {
return control.colorScheme.background_norm;
}
if (!control.enabled) {
return control.colorScheme.field_disabled;
}
if (control.error) {
return control.colorScheme.signal_danger;
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover;
}
return control.colorScheme.interaction_norm;
}
implicitHeight: 20
implicitWidth: 20
radius: ProtonStyle.checkbox_radius
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
color: {
if (!checked) {
return control.colorScheme.background_norm
}
if (!control.enabled) {
return control.colorScheme.field_disabled
}
if (control.error) {
return control.colorScheme.signal_danger
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover
}
return control.colorScheme.interaction_norm
}
border.width: control.checked ? 0 : 1
border.color: {
if (!control.enabled) {
return control.colorScheme.field_disabled
}
if (control.error) {
return control.colorScheme.signal_danger
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover
}
return control.colorScheme.field_norm
}
ColorImage {
color: "#FFFFFF"
height: parent.height - 4
source: "/qml/icons/ic-check.svg"
sourceSize.height: parent.height - 4
sourceSize.width: parent.width - 4
visible: control.checkState === Qt.Checked
width: parent.width - 4
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: parent.width - 4
height: parent.height - 4
sourceSize.width: parent.width - 4
sourceSize.height: parent.height - 4
color: "#FFFFFF"
source: "/qml/icons/ic-check.svg"
visible: control.checkState === Qt.Checked
}
// TODO: do we need PartiallyChecked state?
@ -105,30 +104,4 @@ T.CheckBox {
// visible: control.checkState === Qt.PartiallyChecked
//}
}
contentItem: CheckLabel {
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
color: {
if (!enabled) {
return control.colorScheme.text_disabled
}
if (error) {
return control.colorScheme.signal_danger
}
return control.colorScheme.text_norm
}
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWeight_400
font.pixelSize: ProtonStyle.body_font_size
lineHeight: ProtonStyle.body_line_height
lineHeightMode: Text.FixedHeight
font.letterSpacing: ProtonStyle.body_letter_spacing
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,15 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
@ -26,46 +21,39 @@ T.MenuItem {
property ColorScheme colorScheme
width: parent.width // required. Other item overflows to the right of the menu and get clipped.
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
font.family: ProtonStyle.font_family
font.letterSpacing: ProtonStyle.body_letter_spacing
font.pixelSize: ProtonStyle.body_font_size
font.weight: ProtonStyle.fontWeight_400
icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
icon.height: 24
icon.width: 24
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding)
padding: 12
spacing: 6
icon.width: 24
icon.height: 24
icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWeight_400
font.pixelSize: ProtonStyle.body_font_size
font.letterSpacing: ProtonStyle.body_letter_spacing
contentItem: IconLabel {
id: iconLabel
readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
display: control.display
alignment: Qt.AlignLeft
icon: control.icon
text: control.text
font: control.font
color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
}
width: parent.width // required. Other item overflows to the right of the menu and get clipped.
background: Rectangle {
implicitWidth: 164
implicitHeight: 36
radius: ProtonStyle.button_radius
color: control.down ? control.colorScheme.interaction_default_active : control.highlighted ? control.colorScheme.interaction_default_hover : control.colorScheme.interaction_default
implicitHeight: 36
implicitWidth: 164
radius: ProtonStyle.button_radius
}
contentItem: IconLabel {
id: iconLabel
readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
alignment: Qt.AlignLeft
color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
display: control.display
font: control.font
icon: control.icon
leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
mirrored: control.mirrored
rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
text: control.text
}
}

View File

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

View File

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

View File

@ -1,387 +1,188 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
pragma Singleton
import QtQml
import QtQuick
import "./"
import "."
// https://wiki.qt.io/Qml_Styling
// http://imaginativethinking.ca/make-qml-component-singleton/
QtObject {
id: root
// TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows:
// https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components
// component ColorScheme: QtObject {
// property color primary_norm
// ...
// }
property ColorScheme lightStyle: ColorScheme {
id: _lightStyle
prominent: lightProminentStyle
// Primary
primary_norm: "#6D4AFF"
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_hover: "#4D34B3"
interaction_norm_active: "#372580"
// Text
text_norm: "#0C0C14"
text_weak: "#706D6B"
text_hint: "#8F8D8A"
text_disabled: "#C2BFBC"
text_invert: "#FFFFFF"
// Field
field_norm: "#ADABA8"
field_hover: "#8F8D8A"
field_disabled: "#D1CFCD"
// Border
border_norm: "#D1CFCD"
border_weak: "#EAE7E4"
// Background
background_norm: "#FFFFFF"
background_weak: "#F5F4F2"
background_strong: "#EAE7E4"
background_avatar: "#C2BFBC"
// Interaction-weak
interaction_weak: "#D1CFCD"
interaction_weak_hover: "#C2BFBC"
interaction_weak_active: "#A8A6A3"
// Interaction-default
interaction_default: Qt.rgba(0,0,0,0)
interaction_default_hover: Qt.rgba(194./255., 191./255., 188./255., 0.2)
interaction_default_active: Qt.rgba(194./255., 191./255., 188./255., 0.4)
// Scrollbar
scrollbar_norm: "#D1CFCD"
scrollbar_hover: "#C2BFBC"
// Signal
signal_danger: "#DC3251"
signal_danger_hover: "#F74F6D"
signal_danger_active: "#B72346"
signal_warning: "#FF9900"
signal_warning_hover: "#FFB800"
signal_warning_active: "#FF851A"
signal_success: "#1EA885"
signal_success_hover: "#23C299"
signal_success_active: "#198F71"
signal_info: "#239ECE"
signal_info_hover: "#27B1E8"
signal_info_active: "#1F83B5"
// Shadows
shadow_norm: Qt.rgba(0,0,0, 0.1) // #000000 10% x:0 y:1 blur:4
shadow_lifted: Qt.rgba(0,0,0, 0.16) // #000000 16% x:0 y:8 blur:24
// Backdrop
backdrop_norm: Qt.rgba(12./255., 12./255., 20./255., 0.32)
// Images
welcome_img: "/qml/icons/img-welcome.png"
logo_img: "/qml/icons/product_logos.svg"
}
property ColorScheme lightProminentStyle: ColorScheme {
id: _lightProminentStyle
prominent: this
// Primary
primary_norm: "#8A6EFF"
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_hover: "#7C5CFF"
interaction_norm_active: "#8A6EFF"
// Text
text_norm: "#FFFFFF"
text_weak: "#9282D4"
text_hint: "#544399"
text_disabled: "#4A398F"
text_invert: "#1B1340"
// Field
field_norm: "#9282D4"
field_hover: "#7C5CFF"
field_disabled: "#38277A"
// Border
border_norm: "#413085"
border_weak: "#3C2B80"
// Background
background_norm: "#1B1340"
background_weak: "#271C57"
background_strong: "#38277A"
background_avatar: "#6D4AFF"
// Interaction-weak
interaction_weak: "#4A398F"
interaction_weak_hover: "#6D4AFF"
interaction_weak_active: "#8A6EFF"
// Interaction-default
interaction_default: Qt.rgba(0,0,0,0)
interaction_default_hover: Qt.rgba(68./255., 78./255., 114./255., 0.2)
interaction_default_active: Qt.rgba(68./255., 78./255., 114./255., 0.3)
// Scrollbar
scrollbar_norm: "#413085"
scrollbar_hover: "#4A398F"
// Signal
signal_danger: "#F5385A"
signal_danger_hover: "#FF5473"
signal_danger_active: "#DC3251"
signal_warning: "#FF9900"
signal_warning_hover: "#FFB800"
signal_warning_active: "#FF8419"
signal_success: "#1EA885"
signal_success_hover: "#23C299"
signal_success_active: "#198F71"
signal_info: "#2C89DB"
signal_info_hover: "#3491E3"
signal_info_active: "#1F83B5"
// Shadows
shadow_norm: Qt.rgba(0,0,0, 0.32) // #000000 32% x:0 y:1 blur:4
shadow_lifted: Qt.rgba(0,0,0, 0.40) // #000000 40% x:0 y:8 blur:24
// Backdrop
backdrop_norm: Qt.rgba(0,0,0, 0.32)
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
logo_img: "/qml/icons/product_logos_dark.svg"
}
property ColorScheme darkStyle: ColorScheme {
id: _darkStyle
prominent: darkProminentStyle
// Primary
primary_norm: "#8A6EFF"
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_hover: "#7C5CFF"
interaction_norm_active: "#8A6EFF"
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
text_hint: "#6D697D"
text_disabled: "#5B576B"
text_invert: "#1C1B24"
// Field
field_norm: "#5B576B"
field_hover: "#6D697D"
field_disabled: "#3F3B4C"
// Border
border_norm: "#4A4658"
border_weak: "#343140"
// Background
background_norm: "#1C1B24"
background_weak: "#292733"
background_strong: "#3F3B4C"
background_avatar: "#6D4AFF"
// Interaction-weak
interaction_weak: "#4A4658"
interaction_weak_hover: "#5B576B"
interaction_weak_active: "#6D697D"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2)
interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4)
// Scrollbar
scrollbar_norm: "#4A4658"
scrollbar_hover: "#5B576B"
// Signal
signal_danger: "#F5385A"
signal_danger_hover: "#FF5473"
signal_danger_active: "#DC3251"
signal_warning: "#FF9900"
signal_warning_hover: "#FFB800"
signal_warning_active: "#FF8419"
signal_success: "#1EA885"
signal_success_hover: "#23C299"
signal_success_active: "#198F71"
signal_info: "#239ECE"
signal_info_hover: "#27B1E8"
signal_info_active: "#1F83B5"
// Shadows
shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4
shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24
// Backdrop
backdrop_norm: Qt.rgba(0,0,0,0.32)
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
logo_img: "/qml/icons/product_logos_dark.svg"
}
property real account_hover_radius: 12 * root.px // px
property real account_row_radius: 12 * root.px // px
property real avatar_radius: 8 * root.px // px
property real banner_radius: 12 * root.px // px
property real big_avatar_radius: 12 * root.px // px
property int body_font_size: 14
property real body_letter_spacing: 0.2 * root.px
property int body_line_height: 20
property real button_radius: 8 * root.px // px
property int caption_font_size: 12
property real caption_letter_spacing: 0.4 * root.px
property int caption_line_height: 16
property real card_radius: 12 * root.px // px
property real checkbox_radius: 4 * root.px // px
property real context_item_radius: 8 * root.px // px
property ColorScheme currentStyle: lightStyle
property ColorScheme darkProminentStyle: ColorScheme {
id: _darkProminentStyle
prominent: this
// Backdrop
backdrop_norm: Qt.rgba(0, 0, 0, 0.32)
background_avatar: "#6D4AFF"
// Primary
primary_norm: "#8A6EFF"
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_hover: "#7C5CFF"
interaction_norm_active: "#8A6EFF"
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
text_hint: "#6D697D"
text_disabled: "#5B576B"
text_invert: "#1C1B24"
// Field
field_norm: "#5B576B"
field_hover: "#6D697D"
field_disabled: "#3F3B4C"
// Background
background_norm: "#16141c"
background_strong: "#3F3B4C"
background_weak: "#292733"
// Border
border_norm: "#4A4658"
border_weak: "#343140"
field_disabled: "#3F3B4C"
field_hover: "#6D697D"
// Background
background_norm: "#16141c"
background_weak: "#292733"
background_strong: "#3F3B4C"
background_avatar: "#6D4AFF"
// Interaction-weak
interaction_weak: "#4A4658"
interaction_weak_hover: "#5B576B"
interaction_weak_active: "#6D697D"
// Field
field_norm: "#5B576B"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2)
interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4)
interaction_default_active: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.4)
interaction_default_hover: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.2)
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_active: "#8A6EFF"
interaction_norm_hover: "#7C5CFF"
// Interaction-weak
interaction_weak: "#4A4658"
interaction_weak_active: "#6D697D"
interaction_weak_hover: "#5B576B"
logo_img: "/qml/icons/product_logos_dark.svg"
// Primary
primary_norm: "#8A6EFF"
prominent: this
scrollbar_hover: "#5B576B"
// Scrollbar
scrollbar_norm: "#4A4658"
scrollbar_hover: "#5B576B"
shadow_lifted: Qt.rgba(0, 0, 0, 0.48) // #000000 48% x+0 y+8 blur:24
// Shadows
shadow_norm: Qt.rgba(0, 0, 0, 0.4) // #000000 40% x+0 y+1 blur:4
// Signal
signal_danger: "#F5385A"
signal_danger_hover: "#FF5473"
signal_danger_active: "#DC3251"
signal_warning: "#FF9900"
signal_warning_hover: "#FFB800"
signal_warning_active: "#FF8419"
signal_success: "#1EA885"
signal_success_hover: "#23C299"
signal_success_active: "#198F71"
signal_danger_hover: "#FF5473"
signal_info: "#239ECE"
signal_info_hover: "#27B1E8"
signal_info_active: "#1F83B5"
signal_info_hover: "#27B1E8"
signal_success: "#1EA885"
signal_success_active: "#198F71"
signal_success_hover: "#23C299"
signal_warning: "#FF9900"
signal_warning_active: "#FF8419"
signal_warning_hover: "#FFB800"
text_disabled: "#5B576B"
text_hint: "#6D697D"
text_invert: "#1C1B24"
// Shadows
shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4
shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24
// Backdrop
backdrop_norm: Qt.rgba(0,0,0,0.32)
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
logo_img: "/qml/icons/product_logos_dark.svg"
}
property ColorScheme darkStyle: ColorScheme {
id: _darkStyle
property ColorScheme currentStyle: lightStyle
// Backdrop
backdrop_norm: Qt.rgba(0, 0, 0, 0.32)
background_avatar: "#6D4AFF"
property string font_family: {
switch (Qt.platform.os) {
case "windows":
return "Segoe UI"
case "osx":
return ".AppleSystemUIFont" // should be SF Pro for the foreseeable future. Using "SF Pro Display" directly here is not allowed by the font's license.
case "linux":
return "Ubuntu"
default:
console.error("Unknown platform")
}
// Background
background_norm: "#1C1B24"
background_strong: "#3F3B4C"
background_weak: "#292733"
// Border
border_norm: "#4A4658"
border_weak: "#343140"
field_disabled: "#3F3B4C"
field_hover: "#6D697D"
// Field
field_norm: "#5B576B"
// Interaction-default
interaction_default: "#00000000"
interaction_default_active: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.4)
interaction_default_hover: Qt.rgba(91. / 255., 87. / 255., 107. / 255., 0.2)
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_active: "#8A6EFF"
interaction_norm_hover: "#7C5CFF"
// Interaction-weak
interaction_weak: "#4A4658"
interaction_weak_active: "#6D697D"
interaction_weak_hover: "#5B576B"
logo_img: "/qml/icons/product_logos_dark.svg"
// Primary
primary_norm: "#8A6EFF"
prominent: darkProminentStyle
scrollbar_hover: "#5B576B"
// Scrollbar
scrollbar_norm: "#4A4658"
shadow_lifted: Qt.rgba(0, 0, 0, 0.48) // #000000 48% x+0 y+8 blur:24
// Shadows
shadow_norm: Qt.rgba(0, 0, 0, 0.4) // #000000 40% x+0 y+1 blur:4
// Signal
signal_danger: "#F5385A"
signal_danger_active: "#DC3251"
signal_danger_hover: "#FF5473"
signal_info: "#239ECE"
signal_info_active: "#1F83B5"
signal_info_hover: "#27B1E8"
signal_success: "#1EA885"
signal_success_active: "#198F71"
signal_success_hover: "#23C299"
signal_warning: "#FF9900"
signal_warning_active: "#FF8419"
signal_warning_hover: "#FFB800"
text_disabled: "#5B576B"
text_hint: "#6D697D"
text_invert: "#1C1B24"
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
}
property real px : 1.00 // px
property real input_radius : 8 * root.px // px
property real button_radius : 8 * root.px // px
property real checkbox_radius : 4 * root.px // px
property real avatar_radius : 8 * root.px // px
property real big_avatar_radius : 12 * root.px // px
property real account_hover_radius : 12 * root.px // px
property real account_row_radius : 12 * root.px // px
property real context_item_radius : 8 * root.px // px
property real banner_radius : 12 * root.px // px
property real dialog_radius : 12 * root.px // px
property real card_radius : 12 * root.px // px
property real progress_bar_radius : 3 * root.px // px
property real tooltip_radius : 8 * root.px // px
property int heading_font_size: 28
property int heading_line_height: 36
property int title_font_size: 20
property int title_line_height: 24
property int lead_font_size: 18
property int lead_line_height: 26
property int body_font_size: 14
property int body_line_height: 20
property real body_letter_spacing: 0.2 * root.px
property int caption_font_size: 12
property int caption_line_height: 16
property real caption_letter_spacing: 0.4 * root.px
property real dialog_radius: 12 * root.px // px
property int fontWeight_100: Font.Thin
property int fontWeight_200: Font.Light
property int fontWeight_300: Font.ExtraLight
@ -391,4 +192,179 @@ QtObject {
property int fontWeight_700: Font.Bold
property int fontWeight_800: Font.ExtraBold
property int fontWeight_900: Font.Black
property string font_family: {
switch (Qt.platform.os) {
case "windows":
return "Segoe UI";
case "osx":
return ".AppleSystemUIFont"; // should be SF Pro for the foreseeable future. Using "SF Pro Display" directly here is not allowed by the font's license.
case "linux":
return "Ubuntu";
default:
console.error("Unknown platform");
}
}
property int heading_font_size: 28
property int heading_line_height: 36
property real input_radius: 8 * root.px // px
property int lead_font_size: 18
property int lead_line_height: 26
property ColorScheme lightProminentStyle: ColorScheme {
id: _lightProminentStyle
// Backdrop
backdrop_norm: Qt.rgba(0, 0, 0, 0.32)
background_avatar: "#6D4AFF"
// Background
background_norm: "#1B1340"
background_strong: "#38277A"
background_weak: "#271C57"
// Border
border_norm: "#413085"
border_weak: "#3C2B80"
field_disabled: "#38277A"
field_hover: "#7C5CFF"
// Field
field_norm: "#9282D4"
// Interaction-default
interaction_default: Qt.rgba(0, 0, 0, 0)
interaction_default_active: Qt.rgba(68. / 255., 78. / 255., 114. / 255., 0.3)
interaction_default_hover: Qt.rgba(68. / 255., 78. / 255., 114. / 255., 0.2)
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_active: "#8A6EFF"
interaction_norm_hover: "#7C5CFF"
// Interaction-weak
interaction_weak: "#4A398F"
interaction_weak_active: "#8A6EFF"
interaction_weak_hover: "#6D4AFF"
logo_img: "/qml/icons/product_logos_dark.svg"
// Primary
primary_norm: "#8A6EFF"
prominent: this
scrollbar_hover: "#4A398F"
// Scrollbar
scrollbar_norm: "#413085"
shadow_lifted: Qt.rgba(0, 0, 0, 0.40) // #000000 40% x:0 y:8 blur:24
// Shadows
shadow_norm: Qt.rgba(0, 0, 0, 0.32) // #000000 32% x:0 y:1 blur:4
// Signal
signal_danger: "#F5385A"
signal_danger_active: "#DC3251"
signal_danger_hover: "#FF5473"
signal_info: "#2C89DB"
signal_info_active: "#1F83B5"
signal_info_hover: "#3491E3"
signal_success: "#1EA885"
signal_success_active: "#198F71"
signal_success_hover: "#23C299"
signal_warning: "#FF9900"
signal_warning_active: "#FF8419"
signal_warning_hover: "#FFB800"
text_disabled: "#4A398F"
text_hint: "#544399"
text_invert: "#1B1340"
// Text
text_norm: "#FFFFFF"
text_weak: "#9282D4"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
}
// TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows:
// https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components
// component ColorScheme: QtObject {
// property color primary_norm
// ...
// }
property ColorScheme lightStyle: ColorScheme {
id: _lightStyle
// Backdrop
backdrop_norm: Qt.rgba(12. / 255., 12. / 255., 20. / 255., 0.32)
background_avatar: "#C2BFBC"
// Background
background_norm: "#FFFFFF"
background_strong: "#EAE7E4"
background_weak: "#F5F4F2"
// Border
border_norm: "#D1CFCD"
border_weak: "#EAE7E4"
field_disabled: "#D1CFCD"
field_hover: "#8F8D8A"
// Field
field_norm: "#ADABA8"
// Interaction-default
interaction_default: Qt.rgba(0, 0, 0, 0)
interaction_default_active: Qt.rgba(194. / 255., 191. / 255., 188. / 255., 0.4)
interaction_default_hover: Qt.rgba(194. / 255., 191. / 255., 188. / 255., 0.2)
// Interaction-norm
interaction_norm: "#6D4AFF"
interaction_norm_active: "#372580"
interaction_norm_hover: "#4D34B3"
// Interaction-weak
interaction_weak: "#D1CFCD"
interaction_weak_active: "#A8A6A3"
interaction_weak_hover: "#C2BFBC"
logo_img: "/qml/icons/product_logos.svg"
// Primary
primary_norm: "#6D4AFF"
prominent: lightProminentStyle
scrollbar_hover: "#C2BFBC"
// Scrollbar
scrollbar_norm: "#D1CFCD"
shadow_lifted: Qt.rgba(0, 0, 0, 0.16) // #000000 16% x:0 y:8 blur:24
// Shadows
shadow_norm: Qt.rgba(0, 0, 0, 0.1) // #000000 10% x:0 y:1 blur:4
// Signal
signal_danger: "#DC3251"
signal_danger_active: "#B72346"
signal_danger_hover: "#F74F6D"
signal_info: "#239ECE"
signal_info_active: "#1F83B5"
signal_info_hover: "#27B1E8"
signal_success: "#1EA885"
signal_success_active: "#198F71"
signal_success_hover: "#23C299"
signal_warning: "#FF9900"
signal_warning_active: "#FF851A"
signal_warning_hover: "#FFB800"
text_disabled: "#C2BFBC"
text_hint: "#8F8D8A"
text_invert: "#FFFFFF"
// Text
text_norm: "#0C0C14"
text_weak: "#706D6B"
// Images
welcome_img: "/qml/icons/img-welcome.png"
}
property real progress_bar_radius: 3 * root.px // px
property real px: 1.00 // px
property int title_font_size: 20
property int title_line_height: 24
property real tooltip_radius: 8 * root.px // px
}

View File

@ -1,150 +1,124 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Templates as T
import QtQuick.Controls
import QtQuick.Controls.impl
T.Switch {
property ColorScheme colorScheme
id: control
property ColorScheme colorScheme
property bool loading: false
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding)
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
padding: 0
spacing: 7
contentItem: CheckLabel {
id: label
color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled
font.family: ProtonStyle.font_family
font.letterSpacing: ProtonStyle.body_letter_spacing
font.pixelSize: ProtonStyle.body_font_size
font.weight: ProtonStyle.fontWeight_400
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
lineHeight: ProtonStyle.body_line_height
lineHeightMode: Text.FixedHeight
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
}
indicator: Rectangle {
border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm
border.width: control.enabled && !loading ? 1 : 0
color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong
implicitHeight: 24
implicitWidth: 40
radius: height / 2.
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
Rectangle {
color: {
if (!control.enabled) {
return control.colorScheme.field_disabled;
}
if (control.checked) {
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover;
}
return control.colorScheme.interaction_norm;
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.field_hover;
}
return control.colorScheme.field_norm;
}
height: 24
radius: parent.radius
visible: !loading
width: 24
x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
y: (parent.height - height) / 2
Behavior on x {
enabled: !control.down
SmoothedAnimation {
velocity: 200
}
}
ColorImage {
color: "#FFFFFF"
height: 16
source: "/qml/icons/ic-check.svg"
sourceSize.height: 16
sourceSize.width: 16
visible: control.checked
width: 16
x: (parent.width - width) / 2
y: (parent.height - height) / 2
}
}
ColorImage {
id: loadingImage
color: control.colorScheme.interaction_norm_hover
height: 18
source: "/qml/icons/Loader_16.svg"
sourceSize.height: 18
sourceSize.width: 18
visible: control.loading
width: 18
x: parent.width - width
y: (parent.height - height) / 2
RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 1000
from: 0
loops: Animation.Infinite
running: control.loading
target: loadingImage
to: 360
}
}
}
// TODO: store previous enabled state and restore it?
// For now assuming that only enabled buttons could have loading state
onLoadingChanged: {
if (loading) {
enabled = false
} else {
enabled = true
}
}
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 0
spacing: 7
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 24
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
radius: height / 2.
color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong
border.width: control.enabled && !loading ? 1 : 0
border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm
Rectangle {
x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
y: (parent.height - height) / 2
width: 24
height: 24
radius: parent.radius
visible: !loading
color: {
if (!control.enabled) {
return control.colorScheme.field_disabled
}
if (control.checked) {
if (control.hovered || control.activeFocus) {
return control.colorScheme.interaction_norm_hover
}
return control.colorScheme.interaction_norm
}
if (control.hovered || control.activeFocus) {
return control.colorScheme.field_hover
}
return control.colorScheme.field_norm
}
ColorImage {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 16
height: 16
sourceSize.width: 16
sourceSize.height: 16
color: "#FFFFFF"
source: "/qml/icons/ic-check.svg"
visible: control.checked
}
Behavior on x {
enabled: !control.down
SmoothedAnimation { velocity: 200 }
}
}
ColorImage {
id: loadingImage
x: parent.width - width
y: (parent.height - height) / 2
width: 18
height: 18
sourceSize.width: 18
sourceSize.height: 18
color: control.colorScheme.interaction_norm_hover
source: "/qml/icons/Loader_16.svg"
visible: control.loading
RotationAnimation {
target: loadingImage
loops: Animation.Infinite
duration: 1000
from: 0
to: 360
direction: RotationAnimation.Clockwise
running: control.loading
}
}
}
contentItem: CheckLabel {
id: label
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWeight_400
font.pixelSize: ProtonStyle.body_font_size
lineHeight: ProtonStyle.body_line_height
lineHeightMode: Text.FixedHeight
font.letterSpacing: ProtonStyle.body_letter_spacing
enabled = !loading;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,19 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
Item {
@ -34,24 +28,46 @@ Item {
anchors.fill: parent
spacing: 0
Rectangle {
color: root.colorScheme.background_norm
states: [
State {
name: "Page 1"
PropertyChanges {
currentIndex: 0
target: signInItem
}
},
State {
name: "Page 2"
PropertyChanges {
currentIndex: 1
target: signInItem
}
},
State {
name: "Page 3"
PropertyChanges {
currentIndex: 2
target: signInItem
}
}
]
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
visible: signInItem.currentIndex == 0
visible: signInItem.currentIndex === 0
GridLayout {
anchors.fill: parent
columnSpacing: 0
rowSpacing: 0
columns: 3
rowSpacing: 0
// top margin
Item {
@ -59,141 +75,123 @@ Item {
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
Binding on Layout.preferredHeight {
value: (parent.height - welcomeContentItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height
}
ColumnLayout {
id: welcomeContentItem
Layout.fillWidth: true
spacing: 0
Image {
source: colorScheme.welcome_img
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
source: colorScheme.welcome_img
sourceSize.height: 148
sourceSize.width: 264
}
Label {
colorScheme: root.colorScheme
text: qsTr("Welcome to\nProton Mail Bridge")
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: 16
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to\nProton Mail Bridge")
type: Label.LabelType.Heading
}
Label {
colorScheme: root.colorScheme
id: longTextLabel
text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.")
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: 16
Layout.preferredWidth: 320
wrapMode: Text.WordWrap
Layout.topMargin: 16
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
implicitWidth: children[0].implicitWidth
Image {
id: logoImage
source: colorScheme.logo_img
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.topMargin: 48
anchors.bottomMargin: 48
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 48
source: colorScheme.logo_img
sourceSize.height: 25
sourceSize.width: 200
}
}
}
}
Rectangle {
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
Layout.fillHeight: true
Layout.fillWidth: true
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
RowLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
Button {
colorScheme: root.colorScheme
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.bottomMargin: 80
anchors.left: parent.left
anchors.leftMargin: 80
anchors.rightMargin: 80
anchors.topMargin: 80
anchors.bottomMargin: 80
visible: signInItem.currentIndex != 0
colorScheme: root.colorScheme
secondary: true
text: qsTr("Back")
visible: signInItem.currentIndex != 0
onClicked: {
signInItem.abort()
signInItem.abort();
}
}
}
GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 0
rowSpacing: 0
columns: 3
rowSpacing: 0
// top margin
Item {
@ -201,76 +199,47 @@ Item {
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
Binding on Layout.preferredHeight {
value: (parent.height - signInItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height
}
SignIn {
id: signInItem
colorScheme: root.colorScheme
Layout.preferredWidth: 320
Layout.fillWidth: true
Layout.preferredWidth: 320
colorScheme: root.colorScheme
focus: true
username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : ""
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
}
}
Item {
Layout.fillHeight: true
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
Layout.preferredWidth: signInItem.currentIndex === 0 ? 0 : parent.width / 4
}
}
}
states: [
State {
name: "Page 1"
PropertyChanges {
target: signInItem
currentIndex: 0
}
},
State {
name: "Page 2"
PropertyChanges {
target: signInItem
currentIndex: 1
}
},
State {
name: "Page 3"
PropertyChanges {
target: signInItem
currentIndex: 2
}
}
]
}
}

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"
}
}
}