GODT-22: Facelift

- GODT-1199: Add menu to status window
- GODT-22: use ColorImage instead of IconLabel
- GODT-22: remove banners from MainWindow
- GODT-1199: Fix separator width
- GODT-1199: Fix StatusWindow button position
- GODT-1198: Open main window on startup if no users
- GODT-1199: Fix avatar text color
- GODT-1198: refactor main window layout
- GODT-22: add missing components to qmldir
- GODT-22: refactor components having Layout as root item
- GODT-22: add more user controls
- GODT-1199: Add status window resize and maximum height
- GODT-22: WIP: notification arch
- GODT-22: Notifications WIP
- GODT-22: Fix notification filter, topmost notification
- GODT-1199: Add notifications to status window
- GODT-22: Add strict typization to colorScheme variable
- GODT-1198: WIP Notifications, dialogs and banners
- GODT-22: Add backend notifications (Banners & Dialogs)

D
This commit is contained in:
Alexander Bilyak
2021-08-04 14:00:31 +02:00
committed by Jakub
parent 6bd0739013
commit 0a9748a15d
51 changed files with 3277 additions and 1056 deletions

View File

@ -21,49 +21,58 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { Item {
id: root id: root
property var colorScheme: parent.colorScheme
property var text: "janedoe@protonmail.com" property ColorScheme colorScheme
property var avatarText: "jd" property var user
property var captionText: "50.5 MB / 20 GB"
spacing: 16 implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
Rectangle { RowLayout {
id: avatar anchors.fill: parent
Layout.preferredHeight: account.height spacing: 12
Layout.preferredWidth: account.height
radius: 4
color: root.colorScheme.background_avatar Rectangle {
id: avatar
ProtonLabel { Layout.fillHeight: true
anchors.centerIn: avatar Layout.preferredWidth: height
color: root.colorScheme.text_norm
text: root.avatarText.toUpperCase()
state: "body"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}
ColumnLayout { radius: 4
id: account
Layout.fillHeight: true
Layout.fillWidth: true
ProtonLabel { color: root.colorScheme.background_avatar
text: root.text
color: root.colorScheme.text_norm Label {
state: "body" colorScheme: root.colorScheme
anchors.fill: parent
text: root.user.avatarText.toUpperCase()
type: Label.LabelType.Body
color: root.colorScheme.text_invert
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
} }
ProtonLabel { ColumnLayout {
text: root.captionText id: account
color: root.colorScheme.text_weak Layout.fillHeight: true
state: "caption" Layout.fillWidth: true
spacing: 0
Label {
colorScheme: root.colorScheme
text: user.username
type: Label.LabelType.Body
}
Label {
colorScheme: root.colorScheme
text: user.captionText
type: Label.LabelType.Caption
}
} }
} }
} }

View File

@ -21,28 +21,34 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
ColumnLayout { Item {
id: root id: root
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
spacing: 0 implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
Rectangle { ColumnLayout {
Layout.fillWidth: true anchors.fill: parent
Layout.minimumHeight: 277 spacing: 0
Layout.maximumHeight: 277
color: root.colorScheme.background_norm Rectangle {
Layout.fillWidth: true
Layout.minimumHeight: 277
Layout.maximumHeight: 277
ColumnLayout { color: root.colorScheme.background_norm
ColumnLayout {
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: root.colorScheme.background_weak
} }
} }
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: root.colorScheme.background_weak
}
} }

View File

@ -0,0 +1,226 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import Proton 4.0
import Notifications 1.0
Popup {
id: root
property ColorScheme colorScheme
property Notification notification
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
implicitWidth: contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
popupType: ApplicationWindow.PopupType.Banner
shouldShow: notification ? (notification.active && !notification.dismissed) : false
Action {
id: defaultDismissAction
text: qsTr("OK")
onTriggered: {
if (!root.notification) {
return
}
root.notification.dismissed = true
}
}
RowLayout {
id: contentLayout
anchors.fill: parent
spacing: 0
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: 10
color: {
if (!root.notification) {
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
}
}
}
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
ColorImage {
color: root.colorScheme.text_invert
width: 24
height: 24
sourceSize.width: 24
sourceSize.height: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: {
if (!root.notification) {
return ""
}
switch (root.notification.type) {
case Notification.NotificationType.Info:
return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Success:
return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Warning:
return "./icons/ic-exclamation-circle-filled.svg"
case Notification.NotificationType.Danger:
return "./icons/ic-exclamation-circle-filled.svg"
}
}
}
Label {
colorScheme: root.colorScheme
Layout.fillWidth: true
Layout.fillHeight: true
color: root.colorScheme.text_invert
text: root.notification ? root.notification.text : ""
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
}
}
}
Rectangle {
Layout.fillHeight: true
width: 1
color: {
if (!root.notification) {
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
}
}
}
Button {
colorScheme: root.colorScheme
Layout.fillHeight: true
id: actionButton
action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction
background: Item {
clip: true
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width + 10
radius: 10
color: {
if (!root.notification) {
return "transparent"
}
var norm
var hover
var 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
break;
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
break;
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
}
if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) {
return hover
}
if (actionButton.loading) {
return hover
}
return norm
}
}
}
}
}
}

View File

@ -1,121 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import Proton 4.0
import QtQuick.Controls 2.13
Rectangle {
id: root
property var window
property bool onTop: false
property bool blocking: root.nDangers != 0
property int nDangers: 0
color: root.getTransparentVersion(window.colorScheme.text_norm,root.blocking ? 0.5 : 0)
MouseArea {
anchors.fill: root
acceptedButtons: root.blocking ? Qt.AllButtons : Qt.NoButton
enabled: root.blocking
}
ListModel {
id: notifications
}
ListView {
id: view
anchors.top : root.top
anchors.bottom : root.bottom
anchors.horizontalCenter : root.horizontalCenter
anchors.topMargin : root.height/20
anchors.bottomMargin : root.height/20
layoutDirection: ListView.Vertical
verticalLayoutDirection: root.onTop ? ListView.TopToBottom : ListView.BottomToTop
spacing: 5
model: notifications
delegate: Banner {
id: bannerDelegate
anchors.horizontalCenter: parent.horizontalCenter
text: model.text
actionText: model.buttonText
state: model.state
onAccepted: {
switch (model.submitAction) {
case "update":
console.log("I am updating now")
break;
default:
console.log("NOOP")
}
if (model.state == "danger") root.nDangers-=1
anchors.horizontalCenter = undefined
notifications.remove(index)
}
}
}
function notify(descriptionText, buttonText, type = "info", submitAction = "noop") {
if (type === "danger") root.nDangers+=1
notifications.append({
"text": descriptionText,
"buttonText": buttonText,
"state": type,
"submitAction": submitAction
})
}
function notifyOnlyPaidUsers(){
root.notify(
qsTr("Bridge is exclusive to our paid plans. Upgrade your account to use Bridge."),
qsTr("ok"), "danger"
)
}
function notifyConnectionLostWhileLogin(){
root.notify(
qsTr("Can't connect to the server. Check your internet connection and try again."),
qsTr("ok"), "danger"
)
}
function notifyUpdateManually(){
root.notify(
qsTr("Bridge could not update automatically."),
qsTr("update"), "warning", "update"
)
}
function notifyUserAdded(){
root.notify(
qsTr("Your account has been added to Bridge and you are now signed in."),
qsTr("ok"), "success"
)
}
function getTransparentVersion(original, transparency){
return Qt.rgba(original.r, original.g, original.b, transparency)
}
}

View File

@ -18,25 +18,34 @@
import QtQml 2.12 import QtQml 2.12
import QtQuick 2.13 import QtQuick 2.13
import QtQuick.Window 2.13 import QtQuick.Window 2.13
import Qt.labs.platform 1.0 import Qt.labs.platform 1.1
import Notifications 1.0
QtObject { QtObject {
id: root id: root
property var backend property var backend
property var users
signal login(string username, string password) signal login(string username, string password)
signal login2FA(string username, string code) signal login2FA(string username, string code)
signal login2Password(string username, string password) signal login2Password(string username, string password)
signal loginAbort(string username) signal loginAbort(string username)
property var mainWindow: MainWindow { property Notifications _notifications: Notifications {
id: notifications
backend: root.backend
frontendMain: mainWindow
frontendStatus: statusWindow
frontendTray: trayIcon
}
property MainWindow _mainWindow: MainWindow {
id: mainWindow id: mainWindow
visible: true visible: false
backend: root.backend backend: root.backend
users: root.users notifications: notifications
onLogin: { onLogin: {
root.login(username, password) root.login(username, password)
@ -52,38 +61,142 @@ QtObject {
} }
} }
property var _trayMenu: Window { property StatusWindow _statusWindow: StatusWindow {
id: trayMenu id: statusWindow
title: "window 2"
visible: false visible: false
flags: Qt.Dialog
backend: root.backend
notifications: notifications
onShowMainWindow: {
mainWindow.visible = true
}
onShowHelp: {
}
onShowSettings: {
}
onQuit: {
backend.quit()
}
} }
property var _trayIcon: SystemTrayIcon { property SystemTrayIcon _trayIcon: SystemTrayIcon {
id: trayIcon id: trayIcon
visible: true visible: true
iconSource: "./icons/ic-systray.svg" iconSource: "./icons/ic-systray.svg"
onActivated: { onActivated: {
function calcStatusWindowPosition(statusWidth, statusHeight) {
function bound(num, lower_limit, upper_limit) {
return Math.max(lower_limit, Math.min(upper_limit, num))
}
// checks if rect1 fits within rect2
function isRectFit(rect1, rect2) {
//if (rect2.)
if ((rect2.left > rect1.left) ||
(rect2.right < rect1.right) ||
(rect2.top > rect1.top) ||
(rect2.bottom < rect1.bottom)) {
return false
}
return true
}
// First we get icon center position.
// On some platforms (X11 / Wayland) Qt does not provide icon geometry info.
// In this case we rely on cursor position
var iconCenter = Qt.point(geometry.x + (geometry.width / 2), geometry.y + (geometry.height / 2))
if (geometry.width == 0 && geometry.height == 0) {
iconCenter = backend.getCursorPos()
}
// Now bound this position to virtual screen available rect
// TODO: here we should detect which screen mouse is on and use that screen available geometry to bound
iconCenter.x = bound(iconCenter.x, 0, Qt.application.screens[0].desktopAvailableWidth)
iconCenter.y = bound(iconCenter.y, 0, Qt.application.screens[0].desktopAvailableHeight)
var x = 0
var y = 0
// Check if window may fit above
x = iconCenter.x - statusWidth / 2
y = iconCenter.y - statusHeight
if (isRectFit(
Qt.rect(x, y, statusWidth, statusHeight),
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
)) {
return Qt.point(x, y)
}
// Check if window may fit below
x = iconCenter.x - statusWidth / 2
y = iconCenter.y
if (isRectFit(
Qt.rect(x, y, statusWidth, statusHeight),
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
)) {
return Qt.point(x, y)
}
// Check if window may fit to the left
x = iconCenter.x - statusWidth
y = iconCenter.y - statusHeight / 2
if (isRectFit(
Qt.rect(x, y, statusWidth, statusHeight),
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
)) {
return Qt.point(x, y)
}
// Check if window may fit to the right
x = iconCenter.x
y = iconCenter.y - statusHeight / 2
if (isRectFit(
Qt.rect(x, y, statusWidth, statusHeight),
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
)) {
return Qt.point(x, y)
}
// TODO: add fallback
}
switch (reason) { switch (reason) {
case SystemTrayIcon.Unknown: case SystemTrayIcon.Unknown:
break; break;
case SystemTrayIcon.Context: case SystemTrayIcon.Context:
trayMenu.x = (Screen.desktopAvailableWidth - trayMenu.width) / 2 case SystemTrayIcon.Trigger:!statusWindow.visible
trayMenu.visible = !trayMenu.visible if (!statusWindow.visible) {
var point = calcStatusWindowPosition(statusWindow.width, statusWindow.height)
statusWindow.x = point.x
statusWindow.y = point.y
}
statusWindow.visible = !statusWindow.visible
break break
case SystemTrayIcon.DoubleClick: case SystemTrayIcon.DoubleClick:
case SystemTrayIcon.MiddleClick:
mainWindow.visible = !mainWindow.visible mainWindow.visible = !mainWindow.visible
break; break;
case SystemTrayIcon.Trigger: default:
trayMenu.x = (Screen.desktopAvailableWidth - trayMenu.width) / 2
trayMenu.visible = !trayMenu.visible
break;
case SystemTrayIcon.MiddleClick:
mainWindow.visible = !mainWindow.visible
break;
default:
break; break;
} }
} }
} }
Component.onCompleted: {
if (root.backend.users.count === 0) {
mainWindow.show()
}
if (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) {
mainWindow.show()
}
}
} }

View File

@ -20,7 +20,11 @@ import QtQuick 2.13
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import Proton 4.0
ColumnLayout { ColumnLayout {
id: root
property var user property var user
property var backend property var backend
@ -29,9 +33,10 @@ ColumnLayout {
Layout.fillHeight: true Layout.fillHeight: true
//Layout.fillWidth: true //Layout.fillWidth: true
property var colorScheme property ColorScheme colorScheme
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: user !== undefined ? user.username : "" text: user !== undefined ? user.username : ""
@ -41,39 +46,57 @@ ColumnLayout {
} }
} }
RowLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Button { Switch {
//Layout.fillWidth: true id: userLoginSwitch
colorScheme: root.colorScheme
text: "Login" text: "LoggedIn"
enabled: user !== undefined && !user.loggedIn && user.username.length > 0 enabled: user !== undefined && user.username.length > 0
onClicked: { checked: user ? user.loggedIn : false
if (user === backend.loginUser) {
var newUserObject = backend.userComponent.createObject(backend, {username: user.username, loggedIn: true})
backend.users.append( { object: newUserObject } )
user.username = "" onCheckedChanged: {
user.resetLoginRequests() if (!user) {
return return
} }
user.loggedIn = true if (checked) {
user.resetLoginRequests() if (user === backend.loginUser) {
var newUserObject = backend.userComponent.createObject(backend, {username: user.username, loggedIn: true, setupGuideSeen: user.setupGuideSeen})
backend.users.append( { object: newUserObject } )
user.username = ""
user.resetLoginRequests()
return
}
user.loggedIn = true
user.resetLoginRequests()
return
} else {
user.loggedIn = false
user.resetLoginRequests()
}
} }
} }
Button { Switch {
//Layout.fillWidth: true colorScheme: root.colorScheme
text: "Logout" text: "Setup guide seen"
enabled: user !== undefined && user.loggedIn && user.username.length > 0 enabled: user !== undefined && user.username.length > 0
onClicked: { checked: user ? user.setupGuideSeen : false
user.loggedIn = false
user.resetLoginRequests() onCheckedChanged: {
if (!user) {
return
}
user.setupGuideSeen = checked
} }
} }
} }
@ -83,6 +106,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
colorScheme: root.colorScheme
id: loginLabel id: loginLabel
text: "Login:" text: "Login:"
@ -90,6 +114,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "name/pass error" text: "name/pass error"
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
@ -100,6 +125,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "free user error" text: "free user error"
enabled: user !== undefined && user.isLoginRequested enabled: user !== undefined && user.isLoginRequested
onClicked: { onClicked: {
@ -109,6 +135,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "connection error" text: "connection error"
enabled: user !== undefined && user.isLoginRequested enabled: user !== undefined && user.isLoginRequested
onClicked: { onClicked: {
@ -122,6 +149,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
colorScheme: root.colorScheme
id: faLabel id: faLabel
text: "2FA:" text: "2FA:"
@ -129,6 +157,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "request" text: "request"
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
@ -139,6 +168,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "error" text: "error"
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided) enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
@ -149,6 +179,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "Abort" text: "Abort"
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided) enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
@ -163,6 +194,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
colorScheme: root.colorScheme
id: passLabel id: passLabel
text: "2 Password:" text: "2 Password:"
@ -170,6 +202,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "request" text: "request"
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided) enabled: user !== undefined && user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
@ -180,6 +213,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "error" text: "error"
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided) enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
@ -191,6 +225,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
text: "Abort" text: "Abort"
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided) enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)

View File

@ -24,7 +24,7 @@ import Proton 4.0
ColumnLayout { ColumnLayout {
id: root id: root
property var colorScheme property ColorScheme colorScheme
property var backend property var backend
property alias currentIndex: usersListView.currentIndex property alias currentIndex: usersListView.currentIndex
@ -46,10 +46,10 @@ ColumnLayout {
anchors.margins: 10 anchors.margins: 10
Label { Label {
colorScheme: root.colorScheme
text: modelData.username text: modelData.username
anchors.margins: 10 anchors.margins: 10
anchors.fill: parent anchors.fill: parent
color: root.colorScheme.text_norm
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -69,14 +69,25 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Button { Button {
colorScheme: root.colorScheme
text: "+" text: "+"
onClicked: { onClicked: {
var newUserObject = backend.userComponent.createObject(backend, { username: "test@protonmail.com", loggedIn: false } ) var newUserObject = backend.userComponent.createObject(backend)
newUserObject.username = backend.loginUser.username.length > 0 ? backend.loginUser.username : "test@protonmail.com"
newUserObject.loggedIn = true
newUserObject.setupGuideSeen = true // backend.loginUser.setupGuideSeen
backend.loginUser.username = ""
backend.loginUser.loggedIn = false
backend.loginUser.setupGuideSeen = false
backend.users.append( { object: newUserObject } ) backend.users.append( { object: newUserObject } )
} }
} }
Button { Button {
colorScheme: root.colorScheme
text: "-" text: "-"
enabled: usersListView.currentIndex != 0 enabled: usersListView.currentIndex != 0

View File

@ -26,22 +26,32 @@ import QtQml.Models 2.12
import Proton 4.0 import Proton 4.0
import "./BridgeTest" import "./BridgeTest"
import BridgePreview 1.0
import Notifications 1.0
Window { Window {
id: root id: root
width: 640 width: 640
height: 480 height: 480
x: 100
y: 100
property var colorScheme: ProtonStyle.darkStyle property ColorScheme colorScheme: ProtonStyle.darkStyle
flags : Qt.Window | Qt.Dialog flags : Qt.Window | Qt.Dialog
visible : true visible : true
title : "Bridge Test GUI" title : "Bridge Test GUI"
color : colorScheme.background_norm color : colorScheme.background_norm
function getCursorPos() {
return BridgePreview.getCursorPos()
}
function quit() {
if (bridge !== undefined && bridge !== null) {
bridge.destroy()
}
}
function _log(msg, color) { function _log(msg, color) {
logTextArea.text += "<p style='color: " + color + ";'>" + msg + "</p>" logTextArea.text += "<p style='color: " + color + ";'>" + msg + "</p>"
logTextArea.text += "\n" logTextArea.text += "\n"
@ -94,6 +104,11 @@ Window {
property string username: "" property string username: ""
property bool loggedIn: false property bool loggedIn: false
property bool setupGuideSeen: true
property string captionText: "50.3 MB / 20 GB"
property string avatarText: "jd"
signal loginUsernamePasswordError() signal loginUsernamePasswordError()
signal loginFreeUserError() signal loginFreeUserError()
signal loginConnectionError() signal loginConnectionError()
@ -166,6 +181,7 @@ Window {
Component.onCompleted: { Component.onCompleted: {
var newLoginUser = _userComponent.createObject() var newLoginUser = _userComponent.createObject()
root.loginUser = newLoginUser root.loginUser = newLoginUser
root.loginUser.setupGuideSeen = false
_usersTest.append({object: newLoginUser}) _usersTest.append({object: newLoginUser})
newLoginUser.loginUsernamePasswordError.connect(root.loginUsernamePasswordError) newLoginUser.loginUsernamePasswordError.connect(root.loginUsernamePasswordError)
@ -194,7 +210,7 @@ Window {
} }
TabButton { TabButton {
text: "Playground" text: "Notifications"
} }
TabButton { TabButton {
@ -215,16 +231,13 @@ Window {
RowLayout { RowLayout {
id: globalTab id: globalTab
spacing : 5 spacing : 5
property alias colorScheme: root.colorScheme
ColumnLayout { ColumnLayout {
spacing : 5 spacing : 5
property alias colorScheme: globalTab.colorScheme Label {
colorScheme: root.colorScheme
ProtonLabel {
text: "Global settings" text: "Global settings"
color: globalTab.colorScheme.text_norm
} }
ButtonGroup { ButtonGroup {
@ -232,6 +245,7 @@ Window {
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Light UI" text: "Light UI"
@ -246,6 +260,7 @@ Window {
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Dark UI" text: "Dark UI"
@ -262,6 +277,7 @@ Window {
} }
Button { Button {
colorScheme: root.colorScheme
//Layout.fillWidth: true //Layout.fillWidth: true
text: "Open Bridge" text: "Open Bridge"
@ -272,6 +288,7 @@ Window {
} }
Button { Button {
colorScheme: root.colorScheme
//Layout.fillWidth: true //Layout.fillWidth: true
text: "Close Bridge" text: "Close Bridge"
@ -289,14 +306,13 @@ Window {
ColumnLayout { ColumnLayout {
spacing : 5 spacing : 5
property alias colorScheme: globalTab.colorScheme Label {
colorScheme: root.colorScheme
ProtonLabel {
text: "Notifications" text: "Notifications"
color: globalTab.colorScheme.text_norm
} }
Button { Button {
colorScheme: root.colorScheme
text: "Notify: danger" text: "Notify: danger"
enabled: bridge !== undefined && bridge !== null enabled: bridge !== undefined && bridge !== null
onClicked: { onClicked: {
@ -305,6 +321,7 @@ Window {
} }
Button { Button {
colorScheme: root.colorScheme
text: "Notify: warning" text: "Notify: warning"
enabled: bridge !== undefined && bridge !== null enabled: bridge !== undefined && bridge !== null
onClicked: { onClicked: {
@ -313,6 +330,7 @@ Window {
} }
Button { Button {
colorScheme: root.colorScheme
text: "Notify: success" text: "Notify: success"
enabled: bridge !== undefined && bridge !== null enabled: bridge !== undefined && bridge !== null
onClicked: { onClicked: {
@ -320,8 +338,6 @@ Window {
} }
} }
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
@ -345,14 +361,131 @@ Window {
} }
RowLayout { RowLayout {
id: playgroundTab id: notificationsTab
spacing: 5
property var colorScheme: root.colorScheme ColumnLayout {
spacing: 5
AccountDelegate{} Switch {
colorScheme: root.colorScheme
text: "Internet connection"
checked: true
onCheckedChanged: {
checked ? root.internetOn() : root.internetOff()
}
}
Button {
colorScheme: root.colorScheme
text: "Update manual ready"
onClicked: {
root.updateManualReady("3.14.1592")
}
}
Button {
colorScheme: root.colorScheme
text: "Update manual done"
onClicked: {
root.updateManualRestartNeeded()
}
}
Button {
colorScheme: root.colorScheme
text: "Update manual error"
onClicked: {
root.updateManualError()
}
}
Button {
colorScheme: root.colorScheme
text: "Update force"
onClicked: {
root.updateForce("3.14.1592")
}
}
Button {
colorScheme: root.colorScheme
text: "Update force error"
onClicked: {
root.updateForceError()
}
}
Button {
colorScheme: root.colorScheme
text: "Update silent done"
onClicked: {
root.updateSilentRestartNeeded()
}
}
Button {
colorScheme: root.colorScheme
text: "Update solent error"
onClicked: {
root.updateSilentError()
}
}
Button {
colorScheme: root.colorScheme
text: "Bug report send OK"
onClicked: {
root.bugReportSendSuccess()
}
}
Button {
colorScheme: root.colorScheme
text: "Bug report send error"
onClicked: {
root.bugReportSendError()
}
}
Button {
colorScheme: root.colorScheme
text: "Cache anavailable"
onClicked: {
root.cacheAnavailable()
}
}
Button {
colorScheme: root.colorScheme
text: "Cache can't move"
onClicked: {
root.cacheCantMove()
}
}
Button {
colorScheme: root.colorScheme
text: "Disk full"
onClicked: {
root.diskFull()
}
}
}
} }
TextArea { TextArea {
colorScheme: root.colorScheme
id: logTextArea id: logTextArea
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
@ -365,7 +498,7 @@ Window {
} }
} }
property var bridge property Bridge bridge
// this signals are used only when trying to login with new user (i.e. not in users model) // this signals are used only when trying to login with new user (i.e. not in users model)
signal loginUsernamePasswordError() signal loginUsernamePasswordError()
@ -378,6 +511,25 @@ Window {
signal login2PasswordError() signal login2PasswordError()
signal login2PasswordErrorAbort() signal login2PasswordErrorAbort()
signal internetOff()
signal internetOn()
signal updateManualReady(var version)
signal updateManualRestartNeeded()
signal updateManualError()
signal updateForce(var version)
signal updateForceError()
signal updateSilentRestartNeeded()
signal updateSilentError()
signal bugReportSendSuccess()
signal bugReportSendError()
signal cacheAnavailable()
signal cacheCantMove()
signal diskFull()
onLoginUsernamePasswordError: { onLoginUsernamePasswordError: {
console.debug("<- loginUsernamePasswordError") console.debug("<- loginUsernamePasswordError")
} }
@ -406,6 +558,13 @@ Window {
console.debug("<- login2PasswordErrorAbort") console.debug("<- login2PasswordErrorAbort")
} }
onInternetOff: {
console.debug("<- internetOff")
}
onInternetOn: {
console.debug("<- internetOn")
}
Component { Component {
id: bridgeComponent id: bridgeComponent

View File

@ -23,9 +23,14 @@ import Proton 4.0
Item { Item {
id: root id: root
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
property var window property var backend
signal login(string username, string password)
signal login2FA(string username, string code)
signal login2Password(string username, string password)
signal loginAbort(string username)
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -33,7 +38,7 @@ Item {
Rectangle { Rectangle {
id: leftBar id: leftBar
property var colorScheme: ProtonStyle.prominentStyle property ColorScheme colorScheme: root.colorScheme.prominent
Layout.minimumWidth: 264 Layout.minimumWidth: 264
Layout.maximumWidth: 320 Layout.maximumWidth: 320
@ -55,9 +60,8 @@ Item {
Layout.preferredHeight: 60 Layout.preferredHeight: 60
spacing: 0 spacing: 0
property var colorScheme: leftBar.colorScheme
Status { Status {
colorScheme: leftBar.colorScheme
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.topMargin: 24 Layout.topMargin: 24
Layout.bottomMargin: 17 Layout.bottomMargin: 17
@ -72,6 +76,7 @@ Item {
} }
Button { Button {
colorScheme: leftBar.colorScheme
Layout.minimumHeight: 36 Layout.minimumHeight: 36
Layout.maximumHeight: 36 Layout.maximumHeight: 36
Layout.preferredHeight: 36 Layout.preferredHeight: 36
@ -89,6 +94,7 @@ Item {
} }
Button { Button {
colorScheme: leftBar.colorScheme
Layout.minimumHeight: 36 Layout.minimumHeight: 36
Layout.maximumHeight: 36 Layout.maximumHeight: 36
Layout.preferredHeight: 36 Layout.preferredHeight: 36
@ -127,20 +133,20 @@ Item {
header: Rectangle { header: Rectangle {
height: headerLabel.height+16 height: headerLabel.height+16
color: ProtonStyle.transparent // color: ProtonStyle.transparent
ProtonLabel{ Label{
id:headerLabel colorScheme: leftBar.colorScheme
id: headerLabel
text: qsTr("Accounts") text: qsTr("Accounts")
color: leftBar.colorScheme.text_norm type: Label.LabelType.Body
state: "body"
} }
} }
model: window.backend.users model: root.backend.users
delegate: AccountDelegate{ delegate: AccountDelegate{
id: accountDelegate id: accountDelegate
colorScheme: leftBar.colorScheme colorScheme: leftBar.colorScheme
text: modelData.username user: modelData
} }
} }
@ -160,9 +166,8 @@ Item {
Layout.maximumHeight: 52 Layout.maximumHeight: 52
Layout.preferredHeight: 52 Layout.preferredHeight: 52
property var colorScheme: leftBar.colorScheme
Button { Button {
colorScheme: leftBar.colorScheme
width: 36 width: 36
height: 36 height: 36
@ -209,14 +214,13 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
colorScheme: root.colorScheme colorScheme: root.colorScheme
user: (root.window.backend.users.count === 1 && root.window.backend.users.get(0).loggedIn === false) ? root.window.backend.users.get(0) : undefined user: (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) ? root.backend.users.get(0) : undefined
backend: root.window.backend backend: root.backend
window: root.window
onLogin : { root.window.login ( username , password ) } onLogin : { root.login ( username , password ) }
onLogin2FA : { root.window.login2FA ( username , code ) } onLogin2FA : { root.login2FA ( username , code ) }
onLogin2Password : { root.window.login2Password ( username , password ) } onLogin2Password : { root.login2Password ( username , password ) }
onLoginAbort : { root.window.loginAbort ( username ) } onLoginAbort : { root.loginAbort ( username ) }
} }
} }
} }

View File

@ -18,16 +18,20 @@
import QtQuick 2.13 import QtQuick 2.13
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import Proton 4.0
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "transparent" color: "transparent"
border.color: "red" border.color: "red"
border.width: 1 border.width: 1
z: parent.z - 1 //z: parent.z - 1
z: 10000000
Label { Label {
text: parent.width + "x" + parent.height text: parent.width + "x" + parent.height
anchors.centerIn: parent anchors.centerIn: parent
color: "black" color: "black"
colorScheme: ProtonStyle.currentStyle
} }
} }

View File

@ -22,10 +22,11 @@ import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
import Notifications 1.0
import "tests" import "tests"
Window { ApplicationWindow {
id: root id: root
title: "ProtonMail Bridge" title: "ProtonMail Bridge"
@ -35,33 +36,104 @@ Window {
minimumHeight: contentLayout.implicitHeight minimumHeight: contentLayout.implicitHeight
minimumWidth: contentLayout.implicitWidth minimumWidth: contentLayout.implicitWidth
property var colorScheme: ProtonStyle.currentStyle colorScheme: ProtonStyle.currentStyle
property var backend property var backend
property var users property var notifications
property bool isNoUser: backend.users.count === 0
property bool isNoLoggedUser: backend.users.count === 1 && backend.users.get(0).loggedIn === false
property bool showSetup: true
signal login(string username, string password) signal login(string username, string password)
signal login2FA(string username, string code) signal login2FA(string username, string code)
signal login2Password(string username, string password) signal login2Password(string username, string password)
signal loginAbort(string username) signal loginAbort(string username)
// show Setup Guide on every new user
Connections {
target: root.backend.users
onRowsInserted: {
// considerring that users are added one-by-one
var user = root.backend.users.get(first)
if (!user.loggedIn) {
return
}
if (user.setupGuideSeen) {
return
}
root.showSetup(user)
}
onRowsAboutToBeRemoved: {
for (var i = first; i <= last; i++ ) {
var user = root.backend.users.get(i)
if (setupGuide.user === user) {
setupGuide.user = null
contentLayout._showSetup = false
return
}
}
}
}
function showSetup(user) {
setupGuide.user = user
if (setupGuide.user) {
contentLayout._showSetup = true
} else {
contentLayout._showSetup = false
}
}
StackLayout { StackLayout {
id: contentLayout id: contentLayout
anchors.fill: parent anchors.fill: parent
currentIndex: (root.isNoUser || root.isNoLoggedUser) ? 0 : ( root.showSetup ? 1 : 2) property bool _showSetup: false
currentIndex: {
// show welcome when there are no users or only one non-logged-in user is present
if (backend.users.count === 0) {
return 1
}
WelcomeWindow { if (backend.users.count === 1 && backend.users.get(0).loggedIn === false) {
return 1
}
if (contentLayout._showSetup) {
return 2
}
return 0
}
ContentWrapper {
colorScheme: root.colorScheme
backend: root.backend
Layout.fillHeight: true
Layout.fillWidth: true
onLogin: {
root.login(username, password)
}
onLogin2FA: {
root.login2FA(username, code)
}
onLogin2Password: {
root.login2Password(username, password)
}
onLoginAbort: {
root.loginAbort(username)
}
}
WelcomeGuide {
colorScheme: root.colorScheme colorScheme: root.colorScheme
backend: root.backend backend: root.backend
window: root
enabled: !banners.blocking
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
@ -81,38 +153,21 @@ Window {
} }
SetupGuide { SetupGuide {
id: setupGuide
colorScheme: root.colorScheme colorScheme: root.colorScheme
window: root backend: root.backend
enabled: !banners.blocking
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
}
ContentWrapper { onDismissed: {
colorScheme: root.colorScheme root.showSetup(null)
window: root }
enabled: !banners.blocking
Layout.fillHeight: true
Layout.fillWidth: true
} }
} }
Banners { NotificationPopups {
id: banners colorScheme: root.colorScheme
anchors.fill: parent notifications: root.notifications
window: root
onTop: contentLayout.currentIndex == 0
}
function notifyOnlyPaidUsers() { banners.notifyOnlyPaidUsers() }
function notifyConnectionLostWhileLogin() { banners.notifyConnectionLostWhileLogin() }
function notifyUpdateManually() { banners.notifyUpdateManually() }
function notifyUserAdded() { banners.notifyUserAdded() }
function showSetupGuide(user) {
setupGuide.user = user
root.showSetup = true
} }
} }

View File

@ -0,0 +1,117 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
import Notifications 1.0
Dialog {
id: root
property var notification
shouldShow: notification && notification.active && !notification.dismissed
modal: true
default property alias data: additionalChildrenContainer.children
ColumnLayout {
spacing: 0
Image {
Layout.alignment: Qt.AlignHCenter
sourceSize.width: 64
sourceSize.height: 64
Layout.preferredHeight: 64
Layout.preferredWidth: 64
Layout.bottomMargin: 16
visible: source != ""
source: {
if (!root.notification) {
return ""
}
switch (root.notification.type) {
case Notification.NotificationType.Info:
// TODO: Add info icon?
return ""
case Notification.NotificationType.Success:
return "./icons/ic-success.svg"
case Notification.NotificationType.Warning:
case Notification.NotificationType.Danger:
return "./icons/ic-alert.svg"
}
}
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 8
colorScheme: root.colorScheme
text: root.notification.text
type: Label.LabelType.Title
}
Label {
Layout.fillWidth: true
Layout.preferredWidth: 240
Layout.bottomMargin: 16
colorScheme: root.colorScheme
text: root.notification.description
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
type: Label.LabelType.Body
}
Item {
id: additionalChildrenContainer
Layout.fillWidth: true
Layout.bottomMargin: 16
visible: children.length > 0
implicitHeight: additionalChildrenContainer.childrenRect.height
implicitWidth: additionalChildrenContainer.childrenRect.width
}
ColumnLayout {
spacing: 8
Repeater {
model: root.notification.action
delegate: Button {
Layout.fillWidth: true
colorScheme: root.colorScheme
action: modelData
secondary: index > 0
}
}
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
import Notifications 1.0
Item {
id: root
property ColorScheme colorScheme
property var notifications
property int notificationWhitelist: NotificationFilter.FilterConsts.All
property int notificationBlacklist: NotificationFilter.FilterConsts.None
NotificationFilter {
id: bannerNotificationFilter
source: root.notifications.all
blacklist: Notifications.Group.Dialogs
}
Banner {
colorScheme: root.colorScheme
notification: bannerNotificationFilter.topmost
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.updateManualReady
Switch {
colorScheme: root.colorScheme
text: qsTr("Update automatically in the future")
}
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.updateForce
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.updateForceError
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.bugReportSendSuccess
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.bugReportSendError
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.cacheAnavailable
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.cacheCantMove
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.diskFull
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick.Controls 2.12
QtObject {
id: root
default property var children
enum NotificationType {
Info = 0,
Success = 1,
Warning = 2,
Danger = 3
}
property string text
property string description
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
}
}

View File

@ -0,0 +1,114 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQml.Models 2.12
// contains notifications that satisfy black- and whitelist and are sorted in time-occurred order
ListModel {
id: root
enum FilterConsts {
None = 0,
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()
}
// overriding get method to ignore any role and return directly object itself
function get(row) {
if (row < 0 || row >= count) {
return undefined
}
return data(index(row, 0), Qt.DisplayRole)
}
function rebuildList() {
// avoid evaluation of the list before Component.onCompleted
if (!root.componentCompleted) {
return
}
for (var i = 0; i < root.count; i++) {
root.get(i).onActiveChanged.disconnect( root.updateList )
}
root.clear()
if (!root.source) {
return
}
for (i = 0; i < root.source.length; i++) {
var obj = root.source[i]
if (obj.group & root.blacklist) {
continue
}
if (!(obj.group & root.whitelist)) {
continue
}
root.append({obj})
obj.onActiveChanged.connect( root.updateList )
}
}
function updateList() {
var topmost = null
for (var i = 0; i < root.count; i++) {
var obj = root.get(i)
if (!obj.active) {
continue
}
if (topmost && (topmost.type > obj.type)) {
continue
}
if (topmost && (topmost.type === obj.type) && (topmost.occurred > obj.occurred)) {
continue
}
topmost = obj
}
root.topmost = topmost
}
onWhitelistChanged: {
root.rebuildList()
}
onBlacklistChanged: {
root.rebuildList()
}
onSourceChanged: {
root.rebuildList()
}
}

View File

@ -0,0 +1,428 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import Qt.labs.platform 1.1
import QtQuick.Controls 2.12
import ".."
QtObject {
id: root
property var backend
property MainWindow frontendMain
property StatusWindow frontendStatus
property SystemTrayIcon frontendTray
enum Group {
Connection = 1,
Update = 2,
Configuration = 4,
API = 32,
// Special group for notifications that require dialog popup instead of banner
Dialogs = 64
}
property var all: [
root.noInternet,
root.updateManualReady,
root.updateManualRestartNeeded,
root.updateManualError,
root.updateForce,
root.updateForceError,
root.updateSilentRestartNeeded,
root.updateSilentError,
root.bugReportSendSuccess,
root.bugReportSendError,
root.cacheAnavailable,
root.cacheCantMove,
root.accountChanged,
root.diskFull
]
// Connection
property Notification noInternet: Notification {
text: qsTr("No connection")
icon: "./icons/ic-no-connection.svg"
type: Notification.NotificationType.Danger
group: Notifications.Group.Connection
Connections {
target: root.backend
onInternetOff: {
root.noInternet.active = true
}
onInternetOn: {
root.noInternet.active = false
}
}
}
// Updates
property Notification updateManualReady: Notification {
text: qsTr("Update to Bridge") + " " + (data ? data.version : "")
description: qsTr("A new version of ProtonMail Bridge is available. See what's changed.")
icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info
group: Notifications.Group.Update | Notifications.Group.Dialogs
Connections {
target: root.backend
onUpdateManualReady: {
root.updateManualReady.data = { version: version }
root.updateManualReady.active = true
}
}
action: [
Action {
text: qsTr("Update")
onTriggered: {
// TODO: call update from backend
root.updateManualReady.active = false
}
},
Action {
text: qsTr("Remind me later")
onTriggered: {
// TODO: start timer here
root.updateManualReady.active = false
}
}
]
}
property Notification updateManualRestartNeeded: Notification {
text: qsTr("Bridge update is ready")
icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info
group: Notifications.Group.Update
Connections {
target: root.backend
onUpdateManualRestartNeeded: {
root.updateManualRestartNeeded.active = true
}
}
action: Action {
text: qsTr("Restart Bridge")
onTriggered: {
// TODO
root.updateManualRestartNeeded.active = false
}
}
}
property Notification updateManualError: Notification {
text: qsTr("Bridge couldnt update")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning
group: Notifications.Group.Update
Connections {
target: root.backend
onUpdateManualError: {
root.updateManualError.active = true
}
}
action: Action {
text: qsTr("Update manually")
onTriggered: {
// TODO
root.updateManualError.active = false
}
}
}
property Notification updateForce: Notification {
text: qsTr("Update to ProtonMail Bridge") + " " + (data ? data.version : "")
description: qsTr("This version of Bridge is no longer supported, please update. Learn why. To update manually, go to: https:/protonmail.com/bridge/download")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
group: Notifications.Group.Update | Notifications.Group.Dialogs
Connections {
target: root.backend
onUpdateForce: {
root.updateForce.data = { version: version }
root.updateForce.active = true
}
}
action: [
Action {
text: qsTr("Update")
onTriggered: {
// TODO: trigger update here
root.updateForce.active = false
}
},
Action {
text: qsTr("Quite Bridge")
onTriggered: {
// TODO: quit Bridge here
root.updateForce.active = false
}
}
]
}
property Notification updateForceError: Notification {
text: qsTr("Bridge coudnt update")
description: qsTr("You must update manually. Go to: https:/protonmail.com/bridge/download")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
group: Notifications.Group.Update | Notifications.Group.Dialogs
Connections {
target: root.backend
onUpdateForceError: {
root.updateForceError.active = true
}
}
action: [
Action {
text: qsTr("Update manually")
onTriggered: {
// TODO: trigger update here
root.updateForceError.active = false
}
},
Action {
text: qsTr("Quite Bridge")
onTriggered: {
// TODO: quit Bridge here
root.updateForce.active = false
}
}
]
}
property Notification updateSilentRestartNeeded: Notification {
text: qsTr("Bridge update is ready")
icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info
group: Notifications.Group.Update
Connections {
target: root.backend
onUpdateSilentRestartNeeded: {
root.updateSilentRestartNeeded.active = true
}
}
action: Action {
text: qsTr("Restart Bridge")
onTriggered: {
// TODO
root.updateSilentRestartNeeded.active = false
}
}
}
property Notification updateSilentError: Notification {
text: qsTr("Bridge couldnt update")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning
group: Notifications.Group.Update
Connections {
target: root.backend
onUpdateSilentError: {
root.updateSilentError.active = true
}
}
action: Action {
text: qsTr("Update manually")
onTriggered: {
// TODO
root.updateSilentError.active = false
}
}
}
// Bug reports
property Notification bugReportSendSuccess: Notification {
text: qsTr("Bug report sent")
description: qsTr("Weve received your report, thank you! Our team will get back to you as soon as we can.")
type: Notification.NotificationType.Success
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
Connections {
target: root.backend
onBugReportSendSuccess: {
root.bugReportSendSuccess.active = true
}
}
action: [
Action {
text: qsTr("Ok")
onTriggered: {
root.bugReportSendSuccess.active = false
}
},
Action {
text: "test"
}
]
}
property Notification bugReportSendError: Notification {
text: qsTr("There was a problem")
description: qsTr("There was a problem with sending your report. Please try again later or contact us directly at security@protonmail.com")
type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
Connections {
target: root.backend
onBugReportSendError: {
root.bugReportSendError.active = true
}
}
action: Action {
text: qsTr("Ok")
onTriggered: {
root.bugReportSendError.active = false
}
}
}
// Cache
property Notification cacheAnavailable: Notification {
text: qsTr("Cache location is unavailable")
description: qsTr("Check the directory or change it in your settings.")
type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
Connections {
target: root.backend
onCacheAnavailable: {
root.cacheAnavailable.active = true
}
}
action: [
Action {
text: qsTr("Quit Bridge")
onTriggered: {
root.cacheAnavailable.active = false
}
},
Action {
text: qsTr("Change location")
onTriggered: {
root.cacheAnavailable.active = false
}
}
]
}
property Notification cacheCantMove: Notification {
text: qsTr("Cant move cache")
description: qsTr("The location you have selected is not available. Make sure you have enough free space or choose another location.")
type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
Connections {
target: root.backend
onCacheCantMove: {
root.cacheCantMove.active = true
}
}
action: [
Action {
text: qsTr("Cancel")
onTriggered: {
root.cacheCantMove.active = false
}
},
Action {
text: qsTr("Change location")
onTriggered: {
root.cacheCantMove.active = false
}
}
]
}
// Other
property Notification accountChanged: Notification {
text: qsTr("The address list for your account has changed")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
group: Notifications.Group.Configuration
action: Action {
text: qsTr("Reconfigure")
onTriggered: {
// TODO: open configuration window here
}
}
}
property Notification diskFull: Notification {
text: qsTr("Your disk is almost full")
description: qsTr("Quit Bridge and free disk space or disable the local cache (not recommended).")
type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
Connections {
target: root.backend
onDiskFull: {
root.diskFull.active = true
}
}
action: [
Action {
text: qsTr("Quit Bridge")
onTriggered: {
root.diskFull.active = false
}
},
Action {
text: qsTr("Settings")
onTriggered: {
root.diskFull.active = false
}
}
]
}
}

View File

@ -0,0 +1,6 @@
module Notifications
depends QtQml 2.12
Notifications 1.0 Notifications.qml
NotificationFilter 1.0 NotificationFilter.qml
Notification 1.0 Notification.qml

View File

@ -0,0 +1,136 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
import "."
T.ApplicationWindow {
id: root
property ColorScheme colorScheme
// popup priority based on types
enum PopupType {
Banner = 0,
Dialog = 1
}
// contains currently visible popup
property var popupVisible: null
// list of all popups within ApplicationWindow
property ListModel popups: ListModel {
// overriding get method to ignore any role and return directly object itself
function get(row) {
if (row < 0 || row >= count) {
return undefined
}
return data(index(row, 0), Qt.DisplayRole)
}
onRowsInserted: {
for (var i = first; i <= last; i++) {
var obj = popups.get(i)
obj.onShouldShowChanged.connect( root.processPopups )
}
processPopups()
}
onRowsAboutToBeRemoved: {
for (var i = first; i <= last; i++ ) {
var 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
}
}
processPopups()
}
}
function processPopups() {
if ((root.popupVisible) && (!root.popupVisible.shouldShow)) {
root.popupVisible.visible = false
}
// do nothing if there is already visible popup
if (root.popupVisible) {
return
}
var topmost = null
for (var i = 0; i < popups.count; i++) {
var obj = popups.get(i)
if (obj.shouldShow === false) {
continue
}
if (topmost && (topmost.popupType > obj.popupType)) {
continue
}
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
continue
}
topmost = obj
}
root.popupVisible = topmost
if (!topmost) {
return
}
topmost.visible = true
}
Connections {
target: root.popupVisible
onVisibleChanged: {
if (root.popupVisible.visible) {
return
}
root.popupVisible = null
root.processPopups()
}
}
color: root.colorScheme.background_norm
overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm
}
overlay.modeless: Rectangle {
color: "transparent"
}
}

View File

@ -1,117 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls.impl 2.12
Rectangle {
id: root
width: layout.width
height: layout.height
radius: 10
signal accepted()
property alias text: description.text
property var actionText: ""
property var colorText: Style.currentStyle.text_invert
property var colorMain: "#000"
property var colorHover: "#000"
property var colorActive: "#000"
property var iconSource: "../icons/ic-exclamation-circle-filled.svg"
color: root.colorMain
border.color: root.colorActive
border.width: 1
property var maxWidth: 600
property var minWidth: 400
property var usedWidth: button.width + icon.width
RowLayout {
id: layout
IconLabel {
id:icon
Layout.alignment: Qt.AlignCenter
Layout.leftMargin: 17.5
Layout.topMargin: 15.5
Layout.bottomMargin: 15.5
color: root.colorText
icon.source: root.iconSource
icon.color: root.colorText
icon.height: Style.title_line_height
}
ProtonLabel {
id: description
Layout.alignment: Qt.AlignCenter
Layout.leftMargin: 9.5
Layout.minimumWidth: root.minWidth - root.usedWidth
Layout.maximumWidth: root.maxWidth - root.usedWidth
color: root.colorText
state: "body"
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
}
Button {
id:button
Layout.fillHeight: true
hoverEnabled: true
text: root.actionText.toUpperCase()
onClicked: root.accepted()
background: RoundedRectangle {
width:parent.width
height:parent.height
strokeColor: root.colorActive
strokeWidth: root.border.width
radiusTopRight : root.radius
radiusBottomRight : root.radius
radiusTopLeft : 0
radiusBottomLeft : 0
fillColor: button.down ? root.colorActive : (
button.hovered ? root.colorHover :
root.colorMain
)
}
}
}
state: "info"
states: [
State{ name : "danger" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_danger ; colorHover : Style.currentStyle.signal_danger_hover ; colorActive : Style.currentStyle.signal_danger_active ; iconSource: "../icons/ic-exclamation-circle-filled.svg"}} ,
State{ name : "warning" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_warning ; colorHover : Style.currentStyle.signal_warning_hover ; colorActive : Style.currentStyle.signal_warning_active ; iconSource: "../icons/ic-exclamation-circle-filled.svg"}} ,
State{ name : "success" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_success ; colorHover : Style.currentStyle.signal_success_hover ; colorActive : Style.currentStyle.signal_success_active ; iconSource: "../icons/ic-info-circle-filled.svg"}} ,
State{ name : "info" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_info ; colorHover : Style.currentStyle.signal_info_hover ; colorActive : Style.currentStyle.signal_info_active ; iconSource: "../icons/ic-info-circle-filled.svg"}}
]
}

View File

@ -22,7 +22,7 @@ import QtQuick.Templates 2.12 as T
import "." import "."
T.Button { T.Button {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property alias secondary: control.flat property alias secondary: control.flat
readonly property bool primary: !secondary readonly property bool primary: !secondary
@ -30,6 +30,10 @@ T.Button {
property bool loading: false property bool loading: false
property bool borderless: false
property int labelType: Label.LabelType.Body
// TODO: store previous enabled state and restore it? // TODO: store previous enabled state and restore it?
// For now assuming that only enabled buttons could have loading state // For now assuming that only enabled buttons could have loading state
onLoadingChanged: { onLoadingChanged: {
@ -55,9 +59,7 @@ T.Button {
horizontalPadding: 16 horizontalPadding: 16
spacing: 10 spacing: 10
font.family: Style.font_family font: label.font
font.pixelSize: Style.body_font_size
font.letterSpacing: Style.body_letter_spacing
icon.width: 16 icon.width: 16
icon.height: 16 icon.height: 16
@ -65,7 +67,7 @@ T.Button {
if (primary && !isIcon) { if (primary && !isIcon) {
return "#FFFFFF" return "#FFFFFF"
} else { } else {
return colorScheme.text_norm return control.colorScheme.text_norm
} }
} }
@ -103,6 +105,7 @@ T.Button {
} }
Label { Label {
colorScheme: root.colorScheme
id: label id: label
anchors.left: labelIcon.left anchors.left: labelIcon.left
anchors.top: labelIcon.top anchors.top: labelIcon.top
@ -115,15 +118,16 @@ T.Button {
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
text: control.text text: control.text
font: control.font
color: { color: {
if (primary && !isIcon) { if (primary && !isIcon) {
return "#FFFFFF" return "#FFFFFF"
} else { } else {
return colorScheme.text_norm return control.colorScheme.text_norm
} }
} }
opacity: control.enabled || control.loading ? 1.0 : 0.5 opacity: control.enabled || control.loading ? 1.0 : 0.5
type: labelType
} }
ColorImage { ColorImage {
@ -176,74 +180,74 @@ T.Button {
// Primary colors // Primary colors
if (control.down) { if (control.down) {
return colorScheme.interaction_norm_active return control.colorScheme.interaction_norm_active
} }
if (control.enabled && (control.highlighted || control.hovered || control.checked)) { if (control.enabled && (control.highlighted || control.hovered || control.checked)) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
if (control.loading) { if (control.loading) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.interaction_norm return control.colorScheme.interaction_norm
} else { } else {
// Secondary colors // Secondary colors
if (control.down) { if (control.down) {
return colorScheme.interaction_default_active return control.colorScheme.interaction_default_active
} }
if (control.enabled && (control.highlighted || control.hovered || control.checked)) { if (control.enabled && (control.highlighted || control.hovered || control.checked)) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
if (control.loading) { if (control.loading) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
return colorScheme.interaction_default return control.colorScheme.interaction_default
} }
} else { } else {
if (primary) { if (primary) {
// Primary icon colors // Primary icon colors
if (control.down) { if (control.down) {
return colorScheme.interaction_default_active return control.colorScheme.interaction_default_active
} }
if (control.enabled && (control.highlighted || control.hovered || control.checked)) { if (control.enabled && (control.highlighted || control.hovered || control.checked)) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
if (control.loading) { if (control.loading) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
return colorScheme.interaction_default return control.colorScheme.interaction_default
} else { } else {
// Secondary icon colors // Secondary icon colors
if (control.down) { if (control.down) {
return colorScheme.interaction_default_active return control.colorScheme.interaction_default_active
} }
if (control.enabled && (control.highlighted || control.hovered || control.checked)) { if (control.enabled && (control.highlighted || control.hovered || control.checked)) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
if (control.loading) { if (control.loading) {
return colorScheme.interaction_default_hover return control.colorScheme.interaction_default_hover
} }
return colorScheme.interaction_default return control.colorScheme.interaction_default
} }
} }
} }
border.color: colorScheme.border_norm border.color: control.colorScheme.border_norm
border.width: secondary ? 1 : 0 border.width: secondary && !borderless ? 1 : 0
opacity: control.enabled || control.loading ? 1.0 : 0.5 opacity: control.enabled || control.loading ? 1.0 : 0.5
} }

View File

@ -21,7 +21,7 @@ import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T import QtQuick.Templates 2.12 as T
T.CheckBox { T.CheckBox {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property bool error: false property bool error: false
@ -46,39 +46,39 @@ T.CheckBox {
color: { color: {
if (!checked) { if (!checked) {
return colorScheme.background_norm return control.colorScheme.background_norm
} }
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return control.colorScheme.field_disabled
} }
if (control.error) { if (control.error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.interaction_norm return control.colorScheme.interaction_norm
} }
border.width: control.checked ? 0 : 1 border.width: control.checked ? 0 : 1
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return control.colorScheme.field_disabled
} }
if (control.error) { if (control.error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.field_norm return control.colorScheme.field_norm
} }
ColorImage { ColorImage {
@ -112,21 +112,21 @@ T.CheckBox {
color: { color: {
if (!enabled) { if (!enabled) {
return colorScheme.text_disabled return control.colorScheme.text_disabled
} }
if (error) { if (error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
return colorScheme.text_norm return control.colorScheme.text_norm
} }
font.family: Style.font_family font.family: Style.font_family
font.weight: Style.fontWidth_400 font.weight: Style.fontWeight_400
font.pixelSize: 14 font.pixelSize: Style.body_font_size
lineHeight: 20 lineHeight: Style.body_line_height
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2 font.letterSpacing: Style.body_letter_spacing
} }
} }

View File

@ -0,0 +1,82 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Templates 2.12 as T
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import "."
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
}
function close() {
root.shouldShow = false
}
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))
padding: 24
background: Rectangle {
color: root.colorScheme.background_norm
radius: 10
}
// TODO: Add DropShadow here
T.Overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm
}
T.Overlay.modeless: Rectangle {
color: "transparent"
}
}

View File

@ -0,0 +1,138 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.13
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
import "."
T.Label {
id: root
property ColorScheme colorScheme
enum LabelType {
// weight 700, size 28, height 36
Heading,
// weight 700, size 20, height 24
Title,
// weight 400, size 18, height 26
Lead,
// weight 400, size 14, height 20, spacing 0.2
Body,
// weight 600, size 14, height 20, spacing 0.2
Body_semibold,
// weight 700, size 14, height 20, spacing 0.2
Body_bold,
// weight 400, size 12, height 16, spacing 0.4
Caption,
// weight 600, size 12, height 16, spacing 0.4
Caption_semibold,
// weight 700, size 12, height 16, spacing 0.4
Caption_bold
}
property int type: Label.LabelType.Body
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
palette.link: root.colorScheme.interaction_norm
font.family: Style.font_family
lineHeightMode: Text.FixedHeight
font.weight: {
switch (root.type) {
case Label.LabelType.Heading:
return Style.fontWeight_700
case Label.LabelType.Title:
return Style.fontWeight_700
case Label.LabelType.Lead:
return Style.fontWeight_400
case Label.LabelType.Body:
return Style.fontWeight_400
case Label.LabelType.Body_semibold:
return Style.fontWeight_600
case Label.LabelType.Body_bold:
return Style.fontWeight_700
case Label.LabelType.Caption:
return Style.fontWeight_400
case Label.LabelType.Caption_semibold:
return Style.fontWeight_600
case Label.LabelType.Caption_bold:
return Style.fontWeight_700
}
}
font.pixelSize: {
switch (root.type) {
case Label.LabelType.Heading:
return Style.heading_font_size
case Label.LabelType.Title:
return Style.title_font_size
case Label.LabelType.Lead:
return Style.lead_font_size
case Label.LabelType.Body:
case Label.LabelType.Body_semibold:
case Label.LabelType.Body_bold:
return Style.body_font_size
case Label.LabelType.Caption:
case Label.LabelType.Caption_semibold:
case Label.LabelType.Caption_bold:
return Style.caption_font_size
}
}
lineHeight: {
switch (root.type) {
case Label.LabelType.Heading:
return Style.heading_line_height
case Label.LabelType.Title:
return Style.title_line_height
case Label.LabelType.Lead:
return Style.lead_line_height
case Label.LabelType.Body:
case Label.LabelType.Body_semibold:
case Label.LabelType.Body_bold:
return Style.body_line_height
case Label.LabelType.Caption:
case Label.LabelType.Caption_semibold:
case Label.LabelType.Caption_bold:
return Style.caption_line_height
}
}
font.letterSpacing: {
switch (root.type) {
case Label.LabelType.Heading:
case Label.LabelType.Title:
case Label.LabelType.Lead:
return 0
case Label.LabelType.Body:
case Label.LabelType.Body_semibold:
case Label.LabelType.Body_bold:
return Style.body_letter_spacing
case Label.LabelType.Caption:
case Label.LabelType.Caption_semibold:
case Label.LabelType.Caption_bold:
return Style.caption_letter_spacing
}
}
function link(url, text) {
return `<a href="${url}">${text}</a>`
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
import QtQuick.Window 2.12
import "."
T.Menu {
id: control
property ColorScheme colorScheme
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
margins: 0
overlap: 1
delegate: MenuItem {
colorScheme: control.colorScheme
}
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
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
ScrollIndicator.vertical: ScrollIndicator {}
}
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 40
color: colorScheme.background_norm
border.width: 1
border.color: colorScheme.border_weak
radius: 10
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
import "."
T.MenuItem {
id: control
property ColorScheme colorScheme
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 6
spacing: 6
icon.width: 24
icon.height: 24
icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
font.family: Style.font_family
font.weight: Style.fontWeight_400
font.pixelSize: Style.body_font_size
font.letterSpacing: Style.body_letter_spacing
contentItem: IconLabel {
id: iconLabel
readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
display: control.display
alignment: Qt.AlignLeft
icon: control.icon
text: control.text
font: control.font
color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
}
background: Rectangle {
implicitWidth: 164
implicitHeight: 36
radius: 4
color: control.down ? control.colorScheme.interaction_default_active : control.highlighted ? control.colorScheme.interaction_default_hover : control.colorScheme.interaction_default
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 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
}
function close() {
root.shouldShow = false
}
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
// TODO: Add DropShadow here
T.Overlay.modal: Rectangle {
color: root.colorScheme.backdrop_norm
}
T.Overlay.modeless: Rectangle {
color: "transparent"
}
}

View File

@ -1,43 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.13
import QtQuick.Controls 2.12
Label {
id: root
color: Style.currentStyle.text_norm
palette.link: Style.currentStyle.interaction_norm
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWidth_400
lineHeightMode: Text.FixedHeight
function putLink(linkURL,linkText) {
return `<a href="${linkURL}">${linkText}</a>`
}
state: "title"
states: [
State { name : "heading" ; PropertyChanges { target : root ; font.pixelSize : Style.heading_font_size ; lineHeight : Style.heading_line_height } },
State { name : "title" ; PropertyChanges { target : root ; font.pixelSize : Style.title_font_size ; lineHeight : Style.title_line_height } },
State { name : "lead" ; PropertyChanges { target : root ; font.pixelSize : Style.lead_font_size ; lineHeight : Style.lead_line_height } },
State { name : "body" ; PropertyChanges { target : root ; font.pixelSize : Style.body_font_size ; lineHeight : Style.body_line_height ; font.letterSpacing : Style.body_letter_spacing } },
State { name : "caption" ; PropertyChanges { target : root ; font.pixelSize : Style.caption_font_size ; lineHeight : Style.caption_line_height ; font.letterSpacing : Style.caption_letter_spacing } }
]
}

View File

@ -21,7 +21,7 @@ import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T import QtQuick.Templates 2.12 as T
T.RadioButton { T.RadioButton {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property bool error: false property bool error: false
@ -44,22 +44,22 @@ T.RadioButton {
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2 y: control.topPadding + (control.availableHeight - height) / 2
color: colorScheme.background_norm color: control.colorScheme.background_norm
border.width: 1 border.width: 1
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return control.colorScheme.field_disabled
} }
if (control.error) { if (control.error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.field_norm return control.colorScheme.field_norm
} }
Rectangle { Rectangle {
@ -70,18 +70,18 @@ T.RadioButton {
radius: width / 2 radius: width / 2
color: { color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return control.colorScheme.field_disabled
} }
if (control.error) { if (control.error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.interaction_norm return control.colorScheme.interaction_norm
} }
visible: control.checked visible: control.checked
} }
@ -95,21 +95,21 @@ T.RadioButton {
color: { color: {
if (!enabled) { if (!enabled) {
return colorScheme.text_disabled return control.colorScheme.text_disabled
} }
if (error) { if (error) {
return colorScheme.signal_danger return control.colorScheme.signal_danger
} }
return colorScheme.text_norm return control.colorScheme.text_norm
} }
font.family: Style.font_family font.family: Style.font_family
font.weight: Style.fontWidth_400 font.weight: Style.fontWeight_400
font.pixelSize: 14 font.pixelSize: Style.body_font_size
lineHeight: 20 lineHeight: Style.body_line_height
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2 font.letterSpacing: Style.body_letter_spacing
} }
} }

View File

@ -32,9 +32,8 @@ QtObject {
// property color primay_norm // property color primay_norm
// ... // ...
// } // }
// and instead of "var" later on "ColorScheme" should be used (also in each component)
property var lightStyle: ColorScheme { property ColorScheme lightStyle: ColorScheme {
id: _lightStyle id: _lightStyle
prominent: prominentStyle prominent: prominentStyle
@ -105,7 +104,7 @@ QtObject {
backdrop_norm: "#7A262A33" backdrop_norm: "#7A262A33"
} }
property var prominentStyle: ColorScheme { property ColorScheme prominentStyle: ColorScheme {
id: _prominentStyle id: _prominentStyle
prominent: this prominent: this
@ -176,7 +175,7 @@ QtObject {
backdrop_norm: "#52000000" backdrop_norm: "#52000000"
} }
property var darkStyle: ColorScheme { property ColorScheme darkStyle: ColorScheme {
id: _darkStyle id: _darkStyle
prominent: prominentStyle prominent: prominentStyle
@ -249,7 +248,7 @@ QtObject {
// TODO: if default style should be loaded from somewhere // TODO: if default style should be loaded from somewhere
// (i.e. from preferencies file) - it should be loaded here // (i.e. from preferencies file) - it should be loaded here
property var currentStyle: lightStyle property ColorScheme currentStyle: lightStyle
property string font_family: { property string font_family: {
switch (Qt.platform.os) { switch (Qt.platform.os) {
@ -281,15 +280,13 @@ QtObject {
property int caption_line_height: 16 property int caption_line_height: 16
property real caption_letter_spacing: 0.4 property real caption_letter_spacing: 0.4
property int fontWidth_100: Font.Thin property int fontWeight_100: Font.Thin
property int fontWidth_200: Font.Light property int fontWeight_200: Font.Light
property int fontWidth_300: Font.ExtraLight property int fontWeight_300: Font.ExtraLight
property int fontWidth_400: Font.Normal property int fontWeight_400: Font.Normal
property int fontWidth_500: Font.Medium property int fontWeight_500: Font.Medium
property int fontWidth_600: Font.DemiBold property int fontWeight_600: Font.DemiBold
property int fontWidth_700: Font.Bold property int fontWeight_700: Font.Bold
property int fontWidth_800: Font.ExtraBold property int fontWeight_800: Font.ExtraBold
property int fontWidth_900: Font.Black property int fontWeight_900: Font.Black
property var transparent: "#00000000"
} }

View File

@ -21,7 +21,7 @@ import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12 import QtQuick.Controls.impl 2.12
T.Switch { T.Switch {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property bool loading: false property bool loading: false
@ -57,9 +57,9 @@ T.Switch {
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
padding: 0 padding: 0
color: control.enabled || control.loading ? colorScheme.background_norm : colorScheme.background_strong color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong
border.width: control.enabled && !loading ? 1 : 0 border.width: control.enabled && !loading ? 1 : 0
border.color: control.hovered ? colorScheme.field_hover : colorScheme.field_norm border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm
Rectangle { Rectangle {
x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2))) x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
@ -72,22 +72,22 @@ T.Switch {
color: { color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return control.colorScheme.field_disabled
} }
if (control.checked) { if (control.checked) {
if (control.hovered) { if (control.hovered) {
return colorScheme.interaction_norm_hover return control.colorScheme.interaction_norm_hover
} }
return colorScheme.interaction_norm return control.colorScheme.interaction_norm
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.field_hover return control.colorScheme.field_hover
} }
return colorScheme.field_norm return control.colorScheme.field_norm
} }
ColorImage { ColorImage {
@ -114,7 +114,7 @@ T.Switch {
width: 18 width: 18
height: 18 height: 18
color: colorScheme.interaction_norm_hover color: control.colorScheme.interaction_norm_hover
source: "../icons/Loader_16.svg" source: "../icons/Loader_16.svg"
visible: control.loading visible: control.loading
@ -137,13 +137,13 @@ T.Switch {
text: control.text text: control.text
color: control.enabled || control.loading ? colorScheme.text_norm : colorScheme.text_disabled color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled
font.family: Style.font_family font.family: Style.font_family
font.weight: Style.fontWidth_400 font.weight: Style.fontWeight_400
font.pixelSize: 14 font.pixelSize: Style.body_font_size
lineHeight: 20 lineHeight: Style.body_line_height
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2 font.letterSpacing: Style.body_letter_spacing
} }
} }

View File

@ -23,7 +23,7 @@ import QtQuick.Templates 2.12 as T
Item { Item {
id: root id: root
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property alias background: control.background property alias background: control.background
property alias bottomInset: control.bottomInset property alias bottomInset: control.bottomInset
@ -104,61 +104,53 @@ Item {
radius: 4 radius: 4
visible: true visible: true
color: colorScheme.background_norm color: root.colorScheme.background_norm
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return root.colorScheme.field_disabled
} }
if (control.activeFocus) { if (control.activeFocus) {
return colorScheme.interaction_norm return root.colorScheme.interaction_norm
} }
if (root.error) { if (root.error) {
return colorScheme.signal_danger return root.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.field_hover return root.colorScheme.field_hover
} }
return colorScheme.field_norm return root.colorScheme.field_norm
} }
border.width: 1 border.width: 1
} }
Label { Label {
colorScheme: root.colorScheme
id: label id: label
anchors.top: root.top anchors.top: root.top
anchors.left: root.left anchors.left: root.left
anchors.bottomMargin: 4 anchors.bottomMargin: 4
color: root.enabled ? colorScheme.text_norm : colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
font.family: Style.font_family type: Label.LabelType.Body_semibold
font.weight: Style.fontWidth_600
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
} }
Label { Label {
colorScheme: root.colorScheme
id: hint id: hint
anchors.right: root.right anchors.right: root.right
anchors.bottom: controlView.top anchors.bottom: controlView.top
anchors.bottomMargin: 5 anchors.bottomMargin: 5
color: root.enabled ? colorScheme.text_weak : colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
font.family: Style.font_family type: Label.LabelType.Caption
font.weight: Style.fontWidth_400
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.4
} }
ColorImage { ColorImage {
@ -168,10 +160,11 @@ Item {
anchors.top: assistiveText.top anchors.top: assistiveText.top
anchors.bottom: assistiveText.bottom anchors.bottom: assistiveText.bottom
source: "../icons/ic-exclamation-circle-filled.svg" source: "../icons/ic-exclamation-circle-filled.svg"
color: colorScheme.signal_danger color: root.colorScheme.signal_danger
} }
Label { Label {
colorScheme: root.colorScheme
id: assistiveText id: assistiveText
anchors.left: root.error ? errorIcon.right : parent.left anchors.left: root.error ? errorIcon.right : parent.left
@ -181,22 +174,17 @@ Item {
color: { color: {
if (!root.enabled) { if (!root.enabled) {
return colorScheme.text_disabled return root.colorScheme.text_disabled
} }
if (root.error) { if (root.error) {
return colorScheme.signal_danger return root.colorScheme.signal_danger
} }
return colorScheme.text_weak return root.colorScheme.text_weak
} }
font.family: Style.font_family type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
font.weight: root.error ? Style.fontWidth_600 : Style.fontWidth_400
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.4
} }
ScrollView { ScrollView {
@ -222,8 +210,8 @@ Item {
padding: 8 padding: 8
leftPadding: 12 leftPadding: 12
color: control.enabled ? colorScheme.text_norm : colorScheme.text_disabled color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
placeholderTextColor: control.enabled ? colorScheme.text_hint : colorScheme.text_disabled placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
selectionColor: control.palette.highlight selectionColor: control.palette.highlight
selectedTextColor: control.palette.highlightedText selectedTextColor: control.palette.highlightedText
@ -231,7 +219,7 @@ Item {
cursorDelegate: Rectangle { cursorDelegate: Rectangle {
id: cursor id: cursor
width: 1 width: 1
color: colorScheme.interaction_norm color: root.colorScheme.interaction_norm
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
Connections { Connections {

View File

@ -21,10 +21,11 @@ import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12 import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T import QtQuick.Templates 2.12 as T
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import "."
Item { Item {
id: root id: root
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle property ColorScheme colorScheme
property alias background: control.background property alias background: control.background
property alias bottomInset: control.bottomInset property alias bottomInset: control.bottomInset
@ -91,7 +92,7 @@ Item {
property alias hint: hint.text property alias hint: hint.text
property alias assistiveText: assistiveText.text property alias assistiveText: assistiveText.text
property var echoMode: TextInput.Normal property int echoMode: TextInput.Normal
property bool error: false property bool error: false
@ -117,7 +118,7 @@ Item {
function selectAll() { control.selectAll() } function selectAll() { control.selectAll() }
function selectWord() { control.selectWord() } function selectWord() { control.selectWord() }
function undo() { control.undo() } function undo() { control.undo() }
function forceActiveFocus() {control.forceActiveFocus()} function forceActiveFocus() { control.forceActiveFocus() }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -127,22 +128,22 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
ProtonLabel { Label {
colorScheme: root.colorScheme
id: label id: label
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.enabled ? colorScheme.text_norm : colorScheme.text_disabled type: Label.LabelType.Body_semibold
font.weight: Style.fontWidth_600
state: "body"
} }
ProtonLabel { Label {
colorScheme: root.colorScheme
id: hint id: hint
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.enabled ? colorScheme.text_weak : colorScheme.text_disabled color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
state: "caption" type: Label.LabelType.Caption
} }
} }
@ -157,25 +158,25 @@ Item {
radius: 4 radius: 4
visible: true visible: true
color: colorScheme.background_norm color: root.colorScheme.background_norm
border.color: { border.color: {
if (!control.enabled) { if (!control.enabled) {
return colorScheme.field_disabled return root.colorScheme.field_disabled
} }
if (control.activeFocus) { if (control.activeFocus) {
return colorScheme.interaction_norm return root.colorScheme.interaction_norm
} }
if (root.error) { if (root.error) {
return colorScheme.signal_danger return root.colorScheme.signal_danger
} }
if (control.hovered) { if (control.hovered) {
return colorScheme.field_hover return root.colorScheme.field_hover
} }
return colorScheme.field_norm return root.colorScheme.field_norm
} }
border.width: 1 border.width: 1
@ -193,25 +194,27 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: implicitBackgroundWidth + leftInset + rightInset implicitWidth: implicitBackgroundWidth + leftInset + rightInset
|| Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding, contentHeight + topPadding + bottomPadding,
placeholder.implicitHeight + topPadding + bottomPadding) placeholder.implicitHeight + topPadding + bottomPadding)
padding: 8 padding: 8
leftPadding: 12 leftPadding: 12
color: control.enabled ? colorScheme.text_norm : colorScheme.text_disabled color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
selectionColor: control.palette.highlight selectionColor: control.palette.highlight
selectedTextColor: control.palette.highlightedText selectedTextColor: control.palette.highlightedText
placeholderTextColor: control.enabled ? colorScheme.text_hint : colorScheme.text_disabled placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
echoMode: eyeButton.checked ? TextInput.Normal : root.echoMode
cursorDelegate: Rectangle { cursorDelegate: Rectangle {
id: cursor id: cursor
width: 1 width: 1
color: colorScheme.interaction_norm color: root.colorScheme.interaction_norm
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
Connections { Connections {
@ -268,22 +271,16 @@ Item {
} }
Button { Button {
colorScheme: root.colorScheme
id: eyeButton id: eyeButton
Layout.fillHeight: true Layout.fillHeight: true
visible: root.echoMode === TextInput.Password visible: root.echoMode === TextInput.Password
icon.source: control.echoMode == TextInput.Password ? "../icons/ic-eye.svg" : "../icons/ic-eye-slash.svg"
icon.color: control.color icon.color: control.color
background: Rectangle{color: "#00000000"} background: Item { }
onClicked: { checkable: true
if (control.echoMode == TextInput.Password) { icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
control.echoMode = TextInput.Normal
} else {
control.echoMode = TextInput.Password
}
}
Component.onCompleted: control.echoMode = root.echoMode
} }
} }
} }
@ -292,17 +289,16 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
// FIXME: maybe somewhere in the future there will be an Icon component capable of setting color to the icon ColorImage {
// but before that moment we need to use IconLabel
IconLabel {
id: errorIcon id: errorIcon
visible: root.error && (assistiveText.text.length > 0) visible: root.error && (assistiveText.text.length > 0)
icon.source: "../icons/ic-exclamation-circle-filled.svg" source: "../icons/ic-exclamation-circle-filled.svg"
icon.color: colorScheme.signal_danger color: root.colorScheme.signal_danger
} }
ProtonLabel { Label {
colorScheme: root.colorScheme
id: assistiveText id: assistiveText
Layout.fillHeight: true Layout.fillHeight: true
@ -311,18 +307,17 @@ Item {
color: { color: {
if (!root.enabled) { if (!root.enabled) {
return colorScheme.text_disabled return root.colorScheme.text_disabled
} }
if (root.error) { if (root.error) {
return colorScheme.signal_danger return root.colorScheme.signal_danger
} }
return colorScheme.text_weak return root.colorScheme.text_weak
} }
font.weight: root.error ? Style.fontWidth_600 : Style.fontWidth_400 type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
state: "caption"
} }
} }
} }

View File

@ -1,13 +1,36 @@
# Copyright (c) 2021 Proton Technologies AG
#
# This file is part of ProtonMail Bridge.
#
# ProtonMail 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.
#
# ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
module QQtQuick.Controls.Proton module QQtQuick.Controls.Proton
depends QtQuick.Controls 2.12 depends QtQuick.Controls 2.12
singleton ProtonStyle 4.0 Style.qml singleton ProtonStyle 4.0 Style.qml
Banner 4.0 Banner.qml ColorScheme 4.0 ColorScheme.qml
ApplicationWindow 4.0 ApplicationWindow.qml
Button 4.0 Button.qml Button 4.0 Button.qml
CheckBox 4.0 CheckBox.qml CheckBox 4.0 CheckBox.qml
ProtonLabel 4.0 ProtonLabel.qml Dialog 4.0 Dialog.qml
RoundedRectangle 4.0 RoundedRectangle.qml Label 4.0 Label.qml
Menu 4.0 Menu.qml
MenuItem 4.0 MenuItem.qml
Popup 4.0 Popup.qml
RadioButton 4.0 RadioButton.qml RadioButton 4.0 RadioButton.qml
RoundedRectangle 4.0 RoundedRectangle.qml
Switch 4.0 Switch.qml Switch 4.0 Switch.qml
TextArea 4.0 TextArea.qml TextArea 4.0 TextArea.qml
TextField 4.0 TextField.qml TextField 4.0 TextField.qml

View File

@ -18,92 +18,107 @@
import QtQuick 2.13 import QtQuick 2.13
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12 import QtQuick.Controls.impl 2.12
import Proton 4.0 import Proton 4.0
RowLayout { Item {
id:root id:root
property var colorScheme property ColorScheme colorScheme
property var window property var backend
property var user: { "username": "janedoe@protonmail.com" } property var user
ColumnLayout { signal dismissed()
Layout.fillHeight: true
Layout.leftMargin: 80
Layout.rightMargin: 80
Layout.topMargin: 30
Layout.bottomMargin: 70
ProtonLabel { implicitHeight: children[0].implicitHeight
text: qsTr("Set up email client") implicitWidth: children[0].implicitWidth
font.weight: ProtonStyle.fontWidth_700
state: "heading"
}
ProtonLabel { RowLayout {
text: user.username anchors.fill: parent
color: root.colorScheme.text_weak spacing: 0
state: "lead"
}
ProtonLabel { ColumnLayout {
Layout.topMargin: 32 Layout.fillHeight: true
text: qsTr("Choose an email client") Layout.leftMargin: 80
font.weight: ProtonStyle.fontWidth_600 Layout.rightMargin: 80
state: "body" Layout.topMargin: 30
} Layout.bottomMargin: 70
spacing: 0
ListModel { Label {
id: clients colorScheme: root.colorScheme
ListElement{name : "Apple Mail" ; iconSource : "./icons/ic-apple-mail.svg" } text: qsTr("Set up email client")
ListElement{name : "Microsoft Outlook" ; iconSource : "./icons/ic-microsoft-outlook.svg" } type: Label.LabelType.Heading
ListElement{name : "Mozilla Thunderbird" ; iconSource : "./icons/ic-mozilla-thunderbird.svg" } }
ListElement{name : "Other" ; iconSource : "./icons/ic-other-mail-clients.svg" }
} Label {
colorScheme: root.colorScheme
text: user ? user.username : ""
color: root.colorScheme.text_weak
type: Label.LabelType.Lead
}
Label {
colorScheme: root.colorScheme
Layout.topMargin: 32
text: qsTr("Choose an email client")
type: Label.LabelType.Body_semibold
}
ListModel {
id: clients
ListElement{name : "Apple Mail" ; iconSource : "./icons/ic-apple-mail.svg" }
ListElement{name : "Microsoft Outlook" ; iconSource : "./icons/ic-microsoft-outlook.svg" }
ListElement{name : "Mozilla Thunderbird" ; iconSource : "./icons/ic-mozilla-thunderbird.svg" }
ListElement{name : "Other" ; iconSource : "./icons/ic-other-mail-clients.svg" }
}
Repeater { Repeater {
model: clients model: clients
ColumnLayout { ColumnLayout {
RowLayout { RowLayout {
Layout.topMargin: 12 Layout.topMargin: 12
Layout.bottomMargin: 12 Layout.bottomMargin: 12
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
IconLabel { ColorImage {
icon.source: model.iconSource source: model.iconSource
icon.height: 36 height: 36
}
Label {
colorScheme: root.colorScheme
Layout.leftMargin: 12
text: model.name
type: Label.LabelType.Body
}
} }
ProtonLabel { Rectangle {
Layout.leftMargin: 12 Layout.fillWidth: true
text: model.name Layout.preferredHeight: 1
state: "body" color: root.colorScheme.border_weak
} }
} }
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: root.colorScheme.border_weak
}
} }
}
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
Button { Button {
text: qsTr("Set up later") colorScheme: root.colorScheme
flat: true text: qsTr("Set up later")
flat: true
onClicked: { onClicked: {
root.window.showSetup = false user.setupGuideSeen = true
root.reset() root.dismissed()
}
} }
} }
} }

View File

@ -25,7 +25,7 @@ import Proton 4.0
Item { Item {
id: root id: root
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
function abort() { function abort() {
root.loginAbort(usernameTextField.text) root.loginAbort(usernameTextField.text)
@ -173,20 +173,22 @@ Item {
spacing: 0 spacing: 0
ProtonLabel { Label {
colorScheme: root.colorScheme
text: qsTr("Sign in") text: qsTr("Sign in")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16 Layout.topMargin: 16
font.weight: ProtonStyle.fontWidth_700 type: Label.LabelType.Title
} }
ProtonLabel { Label {
colorScheme: root.colorScheme
id: subTitle id: subTitle
text: qsTr("Enter your Proton Account details.") text: qsTr("Enter your Proton Account details.")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8 Layout.topMargin: 8
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
state: "body" type: Label.LabelType.Body
} }
RowLayout { RowLayout {
@ -201,17 +203,18 @@ Item {
source: "./icons/ic-exclamation-circle-filled.svg" source: "./icons/ic-exclamation-circle-filled.svg"
} }
ProtonLabel { Label {
colorScheme: root.colorScheme
id: errorLabel id: errorLabel
Layout.leftMargin: 4 Layout.leftMargin: 4
color: root.colorScheme.signal_danger color: root.colorScheme.signal_danger
font.weight: root.error ? ProtonStyle.fontWidth_600 : ProtonStyle.fontWidth_400 type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
state: "caption"
} }
} }
TextField { TextField {
colorScheme: root.colorScheme
id: usernameTextField id: usernameTextField
label: qsTr("Username or email") label: qsTr("Username or email")
@ -236,6 +239,7 @@ Item {
} }
TextField { TextField {
colorScheme: root.colorScheme
id: passwordTextField id: passwordTextField
label: qsTr("Password") label: qsTr("Password")
@ -259,6 +263,7 @@ Item {
} }
Button { Button {
colorScheme: root.colorScheme
id: signInButton id: signInButton
text: qsTr("Sign in") text: qsTr("Sign in")
@ -308,12 +313,13 @@ Item {
} }
} }
ProtonLabel { Label {
colorScheme: root.colorScheme
textFormat: Text.StyledText textFormat: Text.StyledText
text: putLink("https://protonmail.com/upgrade", qsTr("Create or upgrade your account")) text: link("https://protonmail.com/upgrade", qsTr("Create or upgrade your account"))
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 24 Layout.topMargin: 24
state: "body" type: Label.LabelType.Body
onLinkActivated: { onLinkActivated: {
Qt.openUrlExternally(link) Qt.openUrlExternally(link)
@ -335,16 +341,16 @@ Item {
spacing: 0 spacing: 0
ProtonLabel { Label {
colorScheme: root.colorScheme
text: qsTr("Two-factor authentication") text: qsTr("Two-factor authentication")
Layout.topMargin: 16 Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
font.weight: ProtonStyle.fontWidth_700 type: Label.LabelType.Heading
} }
TextField { TextField {
colorScheme: root.colorScheme
id: twoFactorPasswordTextField id: twoFactorPasswordTextField
label: qsTr("Two-factor authentication code") label: qsTr("Two-factor authentication code")
@ -360,6 +366,7 @@ Item {
} }
Button { Button {
colorScheme: root.colorScheme
id: twoFAButton id: twoFAButton
text: loading ? qsTr("Authenticating") : qsTr("Authenticate") text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
@ -410,19 +417,16 @@ Item {
spacing: 0 spacing: 0
ProtonLabel { Label {
colorScheme: root.colorScheme
text: qsTr("Unlock your mailbox") text: qsTr("Unlock your mailbox")
Layout.topMargin: 16 Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
font.weight: ProtonStyle.fontWidth_700 type: Label.LabelType.Heading
} }
TextField { TextField {
colorScheme: root.colorScheme
id: secondPasswordTextField id: secondPasswordTextField
label: qsTr("Mailbox password") label: qsTr("Mailbox password")
@ -439,6 +443,7 @@ Item {
} }
Button { Button {
colorScheme: root.colorScheme
id: secondPasswordButton id: secondPasswordButton
text: loading ? qsTr("Unlocking") : qsTr("Unlock") text: loading ? qsTr("Unlocking") : qsTr("Unlock")

View File

@ -22,20 +22,165 @@ import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12 import QtQuick.Controls.impl 2.12
import Proton 4.0 import Proton 4.0
import Notifications 1.0
RowLayout { Item {
id: layout id: root
spacing: 8
ColorImage { property var backend
id: image property var notifications
source: "./icons/ic-connected.svg" property ColorScheme colorScheme
color: ProtonStyle.currentStyle.signal_success
property int notificationWhitelist: NotificationFilter.FilterConsts.All
property int notificationBlacklist: NotificationFilter.FilterConsts.None
readonly property Notification activeNotification: notificationFilter.topmost
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
source: root.notifications.all
whitelist: root.notificationWhitelist
blacklist: root.notificationBlacklist
onTopmostChanged: {
if (!topmost) {
image.source = "./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.text
switch (topmost.type) {
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
break;
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
break;
}
}
} }
Label { RowLayout {
id: label anchors.fill: parent
text: "Connected" spacing: 8
color: ProtonStyle.currentStyle.signal_success anchors.margins: 12
ColorImage {
id: image
}
Label {
colorScheme: root.colorScheme
id: label
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
} }
state: "Connected"
states: [
State {
name: "Connected"
PropertyChanges {
target: image
source: "./icons/ic-connected.svg"
color: ProtonStyle.currentStyle.signal_success
}
PropertyChanges {
target: label
text: qsTr("Connected")
color: ProtonStyle.currentStyle.signal_success
}
},
State {
name: "No connection"
PropertyChanges {
target: image
source: "./icons/ic-no-connection.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("No connection")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Outdated"
PropertyChanges {
target: image
source: "./icons/ic-exclamation-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("Bridge is outdated")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Account changed"
PropertyChanges {
target: image
source: "./icons/ic-exclamation-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("The address list for your account has changed")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Auto update failed"
PropertyChanges {
target: image
source: "./icons/ic-info-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_info
}
PropertyChanges {
target: label
text: qsTr("Bridge couldnt update automatically")
color: ProtonStyle.currentStyle.signal_info
}
},
State {
name: "Update ready"
PropertyChanges {
target: image
source: "./icons/ic-info-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_info
}
PropertyChanges {
target: label
text: qsTr("Bridge update is ready")
color: ProtonStyle.currentStyle.signal_info
}
}
]
} }

View File

@ -0,0 +1,300 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Window 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import Proton 4.0
import ProtonBackend 1.0
import Notifications 1.0
// Because of https://bugreports.qt.io/browse/QTBUG-69777 and other bugs alike it is impossible
// to use Window with flags: Qt.Popup here since it won't close by it's own on click outside.
PopupWindow {
id: root
title: "ProtonMail Bridge"
height: contentLayout.implicitHeight
width: contentLayout.implicitWidth
minimumHeight: 201
minimumWidth: 448
property ColorScheme colorScheme: ProtonStyle.currentStyle
property var backend
property var notifications
color: "transparent"
signal showMainWindow()
signal showHelp()
signal showSettings()
signal quit()
ColumnLayout {
id: contentLayout
anchors.fill: parent
spacing: 0
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Item {
implicitHeight: 12
Layout.fillWidth: true
clip: true
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: parent.height * 2
radius: 10
color: {
if (!statusItem.activeNotification) {
return root.colorScheme.signal_success
}
switch (statusItem.activeNotification.type) {
case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger
case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning
case Notification.NotificationType.Success:
return root.colorScheme.signal_success
case Notification.NotificationType.Info:
return root.colorScheme.signal_info
}
}
}
}
Rectangle {
Layout.fillWidth: 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
color: colorScheme.background_norm
RowLayout {
anchors.fill: parent
anchors.topMargin: 8
anchors.bottomMargin: 8
anchors.leftMargin: 24
anchors.rightMargin: 24
spacing: 8
Status {
id: statusItem
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: root.colorScheme
backend: root.backend
notifications: root.notifications
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.Update | Notifications.Group.Configuration
}
Button {
colorScheme: root.colorScheme
secondary: true
visible: (statusItem.activeNotification && statusItem.activeNotification.action) ? true : false
action: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 ? statusItem.activeNotification.action[0] : null
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: root.colorScheme.background_norm
Rectangle {
anchors.fill: parent
anchors.leftMargin: 24
anchors.rightMargin: 24
color: root.colorScheme.border_norm
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumHeight: accountListView.count ?
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
Number.POSITIVE_INFINITY
color: root.colorScheme.background_norm
clip: true
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
ListView {
id: accountListView
model: root.backend.users
anchors.fill: parent
anchors.topMargin: 8
anchors.bottomMargin: 8
anchors.leftMargin: 24
anchors.rightMargin: 24
interactive: contentHeight > parent.height
snapMode: ListView.SnapToItem
delegate: Item {
width: ListView.view.width
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
RowLayout {
spacing: 0
anchors.fill: parent
AccountDelegate {
Layout.fillWidth: true
Layout.margins: 12
user: modelData
colorScheme: root.colorScheme
}
Button {
Layout.margins: 12
colorScheme: root.colorScheme
visible: true
text: "test"
}
}
}
}
}
Item {
Layout.fillWidth: 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
// background:
clip: true
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: parent.height * 2
radius: 10
color: root.colorScheme.background_weak
}
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 0
Button {
colorScheme: root.colorScheme
secondary: true
text: qsTr("Open ProtonBridge")
borderless: true
labelType: Label.LabelType.Caption_semibold
onClicked: {
root.showMainWindow()
root.visible = false
}
}
Item {
Layout.fillWidth: true
}
Button {
colorScheme: root.colorScheme
secondary: true
icon.source: "./icons/ic-three-dots-vertical.svg"
borderless: true
checkable: true
onClicked: {
menu.open()
}
Menu {
id: menu
colorScheme: root.colorScheme
modal: true
y: 0 - height
MenuItem {
colorScheme: root.colorScheme
text: qsTr("Help")
onClicked: {
root.showHelp()
root.visible = false
}
}
MenuItem {
colorScheme: root.colorScheme
text: qsTr("Settings")
onClicked: {
root.showSettings()
root.visible = false
}
}
MenuItem {
colorScheme: root.colorScheme
text: qsTr("Quit ProtonBridge")
onClicked: {
root.quit()
root.visible = false
}
}
onClosed: {
parent.checked = false
}
onOpened: {
parent.checked = true
}
}
}
}
}
}
}

View File

@ -0,0 +1,294 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
Item {
id: root
property ColorScheme colorScheme
property var backend
property var window
signal login(string username, string password)
signal login2FA(string username, string code)
signal login2Password(string username, string password)
signal loginAbort(string username)
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
color: root.colorScheme.background_norm
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
visible: signInItem.currentIndex == 0
GridLayout {
anchors.fill: parent
columnSpacing: 0
rowSpacing: 0
columns: 3
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - welcomeContentItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: welcomeContentItem.height
}
ColumnLayout {
id: welcomeContentItem
Layout.fillWidth: true
spacing: 0
Image {
source: "icons/img-welcome.svg"
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
}
Label {
colorScheme: root.colorScheme
text: qsTr("Welcome to\nProtonMail Bridge")
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: 16
horizontalAlignment: Text.AlignHCenter
type: Label.LabelType.Heading
}
Label {
colorScheme: root.colorScheme
id: longTextLabel
text: qsTr("Now you can securely access and manage ProtonMail 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
horizontalAlignment: Text.AlignHCenter
type: Label.LabelType.Body
}
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: welcomeContentItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
implicitWidth: children[0].implicitWidth
Image {
id: logoImage
source: "icons/product_logos.svg"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.topMargin: 48
anchors.bottomMargin: 48
}
}
}
}
Rectangle {
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
Layout.fillHeight: true
Layout.fillWidth: true
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.leftMargin: 80
anchors.rightMargin: 80
anchors.topMargin: 80
anchors.bottomMargin: 80
visible: signInItem.currentIndex != 0
secondary: true
text: qsTr("Back")
onClicked: {
signInItem.abort()
}
}
}
GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 0
rowSpacing: 0
columns: 3
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - signInItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: signInItem.height
}
SignIn {
id: signInItem
colorScheme: root.colorScheme
Layout.preferredWidth: 320
Layout.fillWidth: true
onLogin: {
root.login(username, password)
}
onLogin2FA: {
root.login2FA(username, code)
}
onLogin2Password: {
root.login2Password(username, password)
}
onLoginAbort: {
root.loginAbort(username)
}
user: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0) : undefined
backend: root.backend
window: root.window
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: signInItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Item {
Layout.fillHeight: true
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,297 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
RowLayout {
id: root
property var colorScheme: parent.colorScheme
property var backend
property var window
signal login(string username, string password)
signal login2FA(string username, string code)
signal login2Password(string username, string password)
signal loginAbort(string username)
spacing: 0
Rectangle {
color: root.colorScheme.background_norm
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
visible: signInItem.currentIndex == 0
GridLayout {
anchors.fill: parent
columnSpacing: 0
rowSpacing: 0
columns: 3
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - welcomeContentItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: welcomeContentItem.height
}
ColumnLayout {
id: welcomeContentItem
Layout.fillWidth: true
spacing: 0
Image {
source: "icons/img-welcome.svg"
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
}
Label {
text: qsTr("Welcome to\nProtonMail Bridge")
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: 16
color: root.colorScheme.text_norm
horizontalAlignment: Text.AlignHCenter
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWidth_700
font.pixelSize: 28
lineHeight: 36
lineHeightMode: Text.FixedHeight
}
Label {
id: longTextLabel
text: qsTr("Now you can securely access and manage ProtonMail 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
color: root.colorScheme.text_norm
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWidth_400
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
}
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: welcomeContentItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
implicitWidth: children[0].implicitWidth
Image {
id: logoImage
source: "icons/product_logos.svg"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.topMargin: 48
anchors.bottomMargin: 48
}
}
}
}
Rectangle {
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
Layout.fillHeight: true
Layout.fillWidth: true
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 {
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: 80
anchors.rightMargin: 80
anchors.topMargin: 80
anchors.bottomMargin: 80
visible: signInItem.currentIndex != 0
secondary: true
text: qsTr("Back")
onClicked: {
signInItem.abort()
}
}
}
GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 0
rowSpacing: 0
columns: 3
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - signInItem.height) / 4
}
}
// left margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: signInItem.height
}
SignIn {
id: signInItem
colorScheme: root.colorScheme
Layout.preferredWidth: 320
Layout.fillWidth: true
onLogin: {
root.login(username, password)
}
onLogin2FA: {
root.login2FA(username, code)
}
onLogin2Password: {
root.login2Password(username, password)
}
onLoginAbort: {
root.loginAbort(username)
}
user: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0) : undefined
backend: root.backend
window: root.window
}
// Right margin
Item {
Layout.minimumWidth: 48
Layout.maximumWidth: 80
Layout.fillWidth: true
Layout.preferredHeight: signInItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Item {
Layout.fillHeight: true
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

@ -0,0 +1,11 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.9729 8.06214C28.6889 3.51461 35.2762 3.51461 37.9923 8.06214L62.464 49.0356C65.2508 53.7015 61.8891 59.625 56.4543 59.625H7.51078C2.07602 59.625 -1.28569 53.7015 1.50106 49.0356L25.9729 8.06214Z" fill="url(#paint0_linear)"/>
<path d="M29.0775 24.3888C29.0864 22.7908 30.3843 21.5 31.9824 21.5C33.5805 21.5 34.8785 22.7908 34.8874 24.3888L34.9658 38.5C34.9749 40.1542 33.6366 41.5 31.9824 41.5C30.3283 41.5 28.9899 40.1542 28.9991 38.5L29.0775 24.3888Z" fill="white"/>
<path d="M34.9824 47.5C34.9824 49.1569 33.6393 50.5 31.9824 50.5C30.3256 50.5 28.9824 49.1569 28.9824 47.5C28.9824 45.8431 30.3256 44.5 31.9824 44.5C33.6393 44.5 34.9824 45.8431 34.9824 47.5Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear" x1="31.9827" y1="4.18421" x2="31.9827" y2="84.2072" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF7171"/>
<stop offset="1" stop-color="#E63757"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1013 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 13L2 14L6.60951 9.39049L9.42104 6.57896L10.7224 5.27756L12.8435 3.15654L15 1L14 0L11.4338 2.56619C10.3363 2.19528 9.1768 2 8 2C4.99776 2 2.10791 3.271 0.0743047 5.48656C-0.0273754 5.59626 -0.0246996 5.76752 0.0823321 5.87455L1.67175 7.4637C1.78146 7.57341 1.95806 7.57341 2.06777 7.4637C2.0865 7.44524 2.09988 7.42383 2.11058 7.39975C3.61705 5.74584 5.76036 4.7962 8 4.7962C8.37819 4.7962 8.75363 4.82329 9.12372 4.87628L7.56734 6.43266C5.90389 6.54608 4.35089 7.31026 3.24238 8.56633L3.2354 8.57044C3.22366 8.5773 3.21069 8.58487 3.19957 8.59577C3.08986 8.70547 3.08986 8.88208 3.19957 8.99152L4.10403 9.89597L1 13ZM7.99994 9.11969C7.9593 9.11969 7.91877 9.12034 7.87836 9.12164L10.1916 6.80845C11.1689 7.16857 12.052 7.76806 12.7548 8.56633C12.7709 8.57436 12.7869 8.58265 12.8003 8.59577C12.91 8.70547 12.91 8.88208 12.8003 8.99152L11.2724 10.5194C11.2457 10.5462 11.2162 10.5676 11.1815 10.5809C11.1467 10.5943 11.1119 10.6023 11.0744 10.6023H11.0584C11.0519 10.6023 11.0455 10.5995 11.0391 10.5966C11.0366 10.5954 11.0341 10.5943 11.0317 10.5934C11.0299 10.5927 11.0281 10.5921 11.0263 10.5916C11.0022 10.5892 10.9781 10.5836 10.9567 10.5729C10.9436 10.5685 10.9341 10.5606 10.9238 10.5521L10.9238 10.5521L10.9166 10.5462L10.9095 10.5421L10.9045 10.5395C10.8944 10.5342 10.884 10.5288 10.8764 10.5191C10.8734 10.5176 10.872 10.5153 10.8705 10.5125C10.8693 10.5105 10.868 10.5082 10.8657 10.5057C10.8644 10.5033 10.8625 10.502 10.8606 10.5007C10.8585 10.4994 10.8564 10.498 10.855 10.495C10.1647 9.62006 9.12377 9.11969 7.99994 9.11969ZM13.8894 7.39975C13.1883 6.63018 12.3493 6.01303 11.4269 5.5731L13.4895 3.51054C14.3862 4.0424 15.2091 4.70584 15.9257 5.48656C16.0274 5.59653 16.0247 5.76752 15.9177 5.87455L14.3282 7.4637C14.2747 7.51989 14.2025 7.54692 14.1302 7.54692C14.058 7.54692 13.9857 7.51989 13.9322 7.4637C13.9135 7.44524 13.9001 7.42383 13.8894 7.39975ZM7.99998 10.2626C7.17317 10.2626 6.50154 10.9371 6.50154 11.7637C6.50154 12.5903 7.17317 13.2621 7.99998 13.2621C8.8268 13.2621 9.49843 12.5905 9.49843 11.7637C9.49843 10.9369 8.8268 10.2626 7.99998 10.2626Z" fill="#D42F34"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,10 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="32" cy="32" r="30" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.6652 22.2252C46.6453 23.1977 46.6514 24.7806 45.6789 25.7607L29.8028 41.7606C29.3334 42.2337 28.6946 42.4997 28.0282 42.4997C27.3618 42.4998 26.723 42.2337 26.2536 41.7606L19.1297 34.5735C18.1572 33.5935 18.1633 32.0106 19.1434 31.038C20.1235 30.0655 21.7064 30.0717 22.6789 31.0517L28.0282 36.4504L42.1297 22.2389C43.1022 21.2588 44.6851 21.2527 45.6652 22.2252Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear" x1="32" y1="2" x2="32" y2="62" gradientUnits="userSpaceOnUse">
<stop stop-color="#5BCB6A"/>
<stop offset="1" stop-color="#1CB78F"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 796 B

View File

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="ic-three-dots-vertical">
<g id="icon">
<path d="M7 2C7 1.44772 7.44772 1 8 1C8.55228 1 9 1.44772 9 2C9 2.55228 8.55228 3 8 3C7.44772 3 7 2.55228 7 2Z" fill="#17181C"/>
<path d="M7 14C7 13.4477 7.44772 13 8 13C8.55228 13 9 13.4477 9 14C9 14.5523 8.55228 15 8 15C7.44772 15 7 14.5523 7 14Z" fill="#17181C"/>
<path d="M8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7Z" fill="#17181C"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 555 B

View File

@ -23,10 +23,11 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
// Primary buttons // Primary buttons
ButtonsColumn { ButtonsColumn {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@ -35,6 +36,7 @@ RowLayout {
// Secondary buttons // Secondary buttons
ButtonsColumn { ButtonsColumn {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@ -44,6 +46,7 @@ RowLayout {
// Secondary icons // Secondary icons
ButtonsColumn { ButtonsColumn {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@ -58,6 +61,7 @@ RowLayout {
// Icons // Icons
ButtonsColumn { ButtonsColumn {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true

View File

@ -22,7 +22,7 @@ import Proton 4.0
ColumnLayout { ColumnLayout {
id: root id: root
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
property string textNormal: "Button" property string textNormal: "Button"
property string iconNormal: "" property string iconNormal: ""
@ -33,6 +33,7 @@ ColumnLayout {
property bool secondary: false property bool secondary: false
Button { Button {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: implicitHeight Layout.minimumHeight: implicitHeight
@ -45,6 +46,7 @@ ColumnLayout {
Button { Button {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: implicitHeight Layout.minimumHeight: implicitHeight
@ -58,6 +60,7 @@ ColumnLayout {
} }
Button { Button {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: implicitHeight Layout.minimumHeight: implicitHeight

View File

@ -23,11 +23,11 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme id: root
property ColorScheme colorScheme
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
@ -61,7 +61,6 @@ RowLayout {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing

View File

@ -23,37 +23,42 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
error: true error: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
enabled: false enabled: false
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
error: true error: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
enabled: false enabled: false
} }
@ -61,38 +66,43 @@ RowLayout {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
checked: true checked: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
checked: true checked: true
error: true error: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "Radio" text: "Radio"
checked: true checked: true
enabled: false enabled: false
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
checked: true checked: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
checked: true checked: true
error: true error: true
} }
RadioButton { RadioButton {
colorScheme: root.colorScheme
text: "" text: ""
checked: true checked: true
enabled: false enabled: false

View File

@ -23,11 +23,10 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
@ -62,7 +61,6 @@ RowLayout {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing

View File

@ -22,42 +22,47 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
Rectangle { Rectangle {
property var colorScheme property ColorScheme colorScheme
color: colorScheme.background_norm color: colorScheme.background_norm
clip: true clip: true
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
property var colorScheme: parent.colorScheme
spacing: 5 spacing: 5
Buttons { Buttons {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }
TextFields { TextFields {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }
TextAreas { TextAreas {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }
CheckBoxes { CheckBoxes {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }
RadioButtons { RadioButtons {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }
Switches { Switches {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 20 Layout.margins: 20
} }

View File

@ -23,15 +23,16 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme id: root
property ColorScheme colorScheme
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
TextArea { TextArea {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 100 Layout.preferredHeight: 100
@ -42,6 +43,7 @@ RowLayout {
} }
TextArea { TextArea {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 100 Layout.preferredHeight: 100
@ -54,6 +56,7 @@ RowLayout {
TextArea { TextArea {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 100 Layout.preferredHeight: 100
@ -68,6 +71,7 @@ RowLayout {
TextArea { TextArea {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 100 Layout.preferredHeight: 100

View File

@ -23,16 +23,16 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
RowLayout { RowLayout {
property var colorScheme: parent.colorScheme property ColorScheme colorScheme
// Norm // Norm
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "Placeholder" placeholderText: "Placeholder"
@ -42,6 +42,7 @@ RowLayout {
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Value" text: "Value"
@ -53,6 +54,7 @@ RowLayout {
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
error: true error: true
@ -65,6 +67,7 @@ RowLayout {
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Value" text: "Value"
@ -80,11 +83,11 @@ RowLayout {
// Masked // Masked
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
echoMode: TextInput.Password echoMode: TextInput.Password
placeholderText: "Password" placeholderText: "Password"
@ -94,6 +97,7 @@ RowLayout {
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Password" text: "Password"
@ -105,6 +109,7 @@ RowLayout {
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Password" text: "Password"
error: true error: true
@ -117,6 +122,7 @@ RowLayout {
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
text: "Password" text: "Password"
enabled: false enabled: false
@ -132,50 +138,31 @@ RowLayout {
// Varia // Varia
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
property var colorScheme: parent.colorScheme
spacing: parent.spacing spacing: parent.spacing
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "Placeholder" placeholderText: "Placeholder"
label: "Label" label: "Label"
hint: "Hint" hint: "Hint"
Rectangle {
anchors.fill: parent
border.color: "red"
border.width: 1
z: parent.z - 1
}
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "Placeholder" placeholderText: "Placeholder"
assistiveText: "Assistive text" assistiveText: "Assistive text"
Rectangle {
anchors.fill: parent
border.color: "red"
border.width: 1
z: parent.z - 1
}
} }
TextField { TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "Placeholder" placeholderText: "Placeholder"
Rectangle {
anchors.fill: parent
border.color: "red"
border.width: 1
z: parent.z - 1
}
} }
} }
} }