forked from Silverfish/proton-bridge
GODT-22: Frontend-backend
- GODT-1246 Implement settings view.
- GODT-1257 GODT-1246: Account and Help view
- GODT-1298: Minimal working build (panics)
- GODT-1298: loading QML (needs Popup window)
- GODT-1298: WARN: Adding PopupWindow not possible!
In therecipe qt the `quickwidgets` classes are within `quick` module, but
forgot to add library and include paths into cgo flags. Therefore
compilation fails and it would be hard to patch therecipe in order to
fix it.
I am not sure if rewrite PopupWindow into go would make any difference,
therefore I decided to use normal QML Window without borders.
- GODT-1298: Rework status window, add backend props, slots and signals.
- GODT-1298: Users
- GODT-1298: Login
- GODT-1298: WIP Help and bug report
- GODT-1178: MacOS dock icon control
- GODT-1298: Help, bug report, update and events
- GODT-1298: Apple Mail config and Settings (without cache on disk)
This commit is contained in:
@ -27,12 +27,61 @@ Item {
|
||||
property ColorScheme colorScheme
|
||||
property var user
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
property var _spacing: 12
|
||||
property var _leftRightMargins: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return 12
|
||||
case AccountDelegate.LargeView: return 0
|
||||
}
|
||||
}
|
||||
property var _topBottomMargins: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return 10
|
||||
case AccountDelegate.LargeView: return 0
|
||||
}
|
||||
}
|
||||
|
||||
property color usedSpaceColor : {
|
||||
if (!root.enabled) return root.colorScheme.text_weak
|
||||
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
|
||||
if (root.usedFraction < .50) return root.colorScheme.signal_success
|
||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user.totalBytes ? root.user.usedBytes / root.user.totalBytes : 0
|
||||
property string totalSpace: root.spaceWithUnits(root.user.totalBytes)
|
||||
property string usedSpace: root.spaceWithUnits(root.user.usedBytes)
|
||||
|
||||
function spaceWithUnits(bytes){
|
||||
if (bytes*1 !== bytes ) return "0 kB"
|
||||
var units = ['B',"kB", "MB", "TB"];
|
||||
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
||||
|
||||
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
||||
}
|
||||
|
||||
signal clicked()
|
||||
|
||||
// width expected to be set by parent object
|
||||
implicitHeight : children[0].implicitHeight + 2*root._topBottomMargins
|
||||
|
||||
enum ViewType{
|
||||
SmallView, LargeView
|
||||
}
|
||||
property var type : AccountDelegate.SmallView
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
spacing: root._spacing
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
left: root.left
|
||||
right: root.rigth
|
||||
leftMargin : root._leftRightMargins
|
||||
rightMargin : root._leftRightMargins
|
||||
topMargin : root._topBottomMargins
|
||||
bottomMargin : root._topBottomMargins
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
@ -48,8 +97,19 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
text: root.user.avatarText.toUpperCase()
|
||||
type: Label.LabelType.Body
|
||||
color: root.colorScheme.text_invert
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
font.weight: Font.Normal
|
||||
color: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return root.colorScheme.text_norm
|
||||
case AccountDelegate.LargeView: return root.colorScheme.text_invert
|
||||
}
|
||||
}
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
@ -63,16 +123,78 @@ Item {
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: root.width - (
|
||||
root._spacing + avatar.width + 2*root._leftRightMargins
|
||||
)
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: user.username
|
||||
type: Label.LabelType.Body
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: user.captionText
|
||||
type: Label.LabelType.Caption
|
||||
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 : 0 }
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.usedSpace
|
||||
color: root.usedSpaceColor
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: " / " + root.totalSpace
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: root.type == AccountDelegate.LargeView
|
||||
|
||||
width: 140
|
||||
height: 4
|
||||
radius: 3
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
Rectangle {
|
||||
radius: 3
|
||||
color: root.usedSpaceColor
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
}
|
||||
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: root
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,34 +21,245 @@ import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
ScrollView {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var notifications
|
||||
property var user
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
clip: true
|
||||
contentWidth: pane.width
|
||||
contentHeight: pane.height
|
||||
|
||||
property int _leftRightMargins: 64
|
||||
property int _topBottomMargins: 68
|
||||
property int _spacing: 22
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
bottom: pane.bottom
|
||||
}
|
||||
color: root.colorScheme.background_weak
|
||||
width: root.width
|
||||
height: configuration.height + root._topBottomMargins
|
||||
}
|
||||
|
||||
signal showSignIn()
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
id: pane
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 277
|
||||
Layout.maximumHeight: 277
|
||||
width: root.width
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user.loggedIn
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user.loggedIn
|
||||
onClicked: root.user.logout()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
visible: root.user.loggedIn
|
||||
onClicked: root.user.remove()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: !root.user.loggedIn
|
||||
onClicked: root.parent.rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
description: "MISSING WIREFRAME" // TODO
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user.loggedIn
|
||||
visible: !root.user.splitMode
|
||||
onClicked: root.showSetupGuide(root.user,user.addresses[0])
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Split addresses allows you to configure multiple email addresses individually. Changing its mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user.splitMode
|
||||
visible: root.user.addresses.length > 1
|
||||
enabled: root.user.loggedIn
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user.loggedIn
|
||||
|
||||
visible: root.user.splitMode
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
Layout.fillWidth: true
|
||||
model: root.user.addresses
|
||||
|
||||
property var _topBottomMargins : 8
|
||||
property var _leftRightMargins : 16
|
||||
|
||||
background: RoundedRectangle {
|
||||
radiusTopLeft : 6
|
||||
radiusTopRight : 6
|
||||
radiusBottomLeft : addressSelector.down ? 0 : 6
|
||||
radiusBottomRight : addressSelector.down ? 0 : 6
|
||||
|
||||
height: addressSelector.contentItem.height
|
||||
//width: addressSelector.contentItem.width
|
||||
|
||||
fillColor : root.colorScheme.background_norm
|
||||
strokeColor : root.colorScheme.border_norm
|
||||
strokeWidth : 1
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: listItem
|
||||
width: root.width
|
||||
height: children[0].height + 4 + 2*addressSelector._topBottomMargins
|
||||
|
||||
Label {
|
||||
anchors {
|
||||
top : parent.top
|
||||
left : parent.left
|
||||
topMargin : addressSelector._topBottomMargins + 4
|
||||
leftMargin : addressSelector._leftRightMargins
|
||||
}
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
property bool isOver: false
|
||||
color: {
|
||||
if (listItem.isOver) return root.colorScheme.interaction_weak_hover
|
||||
if (addressSelector.highlightedIndex === index) return root.colorScheme.interaction_weak
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: listItem.isOver = true
|
||||
onExited: listItem.isOver = false
|
||||
onClicked : {
|
||||
addressSelector.currentIndex = index
|
||||
addressSelector.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
topPadding : addressSelector._topBottomMargins+4
|
||||
bottomPadding : addressSelector._topBottomMargins
|
||||
leftPadding : addressSelector._leftRightMargins
|
||||
rightPadding : addressSelector._leftRightMargins
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: addressSelector.displayText
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight: 1}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
spacing: root._spacing
|
||||
visible: root.user.loggedIn
|
||||
|
||||
color: root.colorScheme.background_weak
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Item {height: 1}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user.password
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user.password
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,9 +28,13 @@ Popup {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property Notification notification
|
||||
property var mainWindow
|
||||
|
||||
topMargin: 37
|
||||
leftMargin: (mainWindow.width - root.implicitWidth)/2
|
||||
|
||||
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
|
||||
implicitWidth: contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
|
||||
@ -74,13 +78,13 @@ Popup {
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
}
|
||||
@ -109,13 +113,13 @@ Popup {
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
}
|
||||
}
|
||||
@ -145,13 +149,13 @@ Popup {
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info_active
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success_active
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning_active
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger_active
|
||||
}
|
||||
}
|
||||
@ -183,22 +187,22 @@ Popup {
|
||||
var active
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
case Notification.NotificationType.Danger:
|
||||
norm = root.colorScheme.signal_danger
|
||||
hover = root.colorScheme.signal_danger_hover
|
||||
active = root.colorScheme.signal_danger_active
|
||||
|
||||
@ -25,12 +25,7 @@ import Notifications 1.0
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
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)
|
||||
property var backend: go
|
||||
|
||||
property Notifications _notifications: Notifications {
|
||||
id: notifications
|
||||
@ -45,19 +40,23 @@ QtObject {
|
||||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: notifications
|
||||
notifications: root._notifications
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
backend.login(username, password)
|
||||
}
|
||||
onLogin2FA: {
|
||||
root.login2FA(username, code)
|
||||
backend.login2FA(username, code)
|
||||
}
|
||||
onLogin2Password: {
|
||||
root.login2Password(username, password)
|
||||
backend.login2Password(username, password)
|
||||
}
|
||||
onLoginAbort: {
|
||||
root.loginAbort(username)
|
||||
backend.loginAbort(username)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
backend.dockIconVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,20 +65,45 @@ QtObject {
|
||||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: notifications
|
||||
notifications: root._notifications
|
||||
|
||||
property var x_center: 10
|
||||
property var x_min: 0
|
||||
property var x_max: 100
|
||||
property var y_center: 1000
|
||||
property var y_min: 0
|
||||
property var y_max: 10000
|
||||
|
||||
x: bound(x_center,x_min, x_max-statusWindow.width)
|
||||
y: bound(y_center,y_min, y_max-statusWindow.height)
|
||||
|
||||
|
||||
onShowMainWindow: {
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowHelp: {
|
||||
|
||||
mainWindow.showHelp()
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowSettings: {
|
||||
|
||||
mainWindow.showSettings()
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowSignIn: {
|
||||
mainWindow.showSignIn(username)
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onQuit: {
|
||||
backend.quit()
|
||||
}
|
||||
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
}
|
||||
}
|
||||
|
||||
property SystemTrayIcon _trayIcon: SystemTrayIcon {
|
||||
@ -88,103 +112,59 @@ QtObject {
|
||||
iconSource: "./icons/ic-systray.svg"
|
||||
onActivated: {
|
||||
function calcStatusWindowPosition(statusWidth, statusHeight) {
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
function isInInterval(num, lower_limit, upper_limit) {
|
||||
return lower_limit <= num && num <= upper_limit
|
||||
}
|
||||
// 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 iconWidth = geometry.width *1.2
|
||||
var iconHeight = geometry.height *1.2
|
||||
var iconCenter = Qt.point(geometry.x + (geometry.width / 2), geometry.y + (geometry.height / 2))
|
||||
|
||||
if (geometry.width == 0 && geometry.height == 0) {
|
||||
iconCenter = backend.getCursorPos()
|
||||
// fallback: simple guess, no data to estimate
|
||||
iconWidth = 25
|
||||
iconHeight = 25
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Find screen
|
||||
var screen = Qt.application.screens[0]
|
||||
|
||||
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)
|
||||
for (var i in Qt.application.screens) {
|
||||
screen = Qt.application.screens[i]
|
||||
if (
|
||||
isInInterval(iconCenter.x, screen.virtualX, screen.virtualX+screen.width) &&
|
||||
isInInterval(iconCenter.y, screen.virtualY, screen.virtualY+screen.heigh)
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Calculate allowed square where status window top left corner can be positioned
|
||||
statusWindow.x_center = iconCenter.x
|
||||
statusWindow.y_center = iconCenter.y
|
||||
statusWindow.x_min = screen.virtualX + iconWidth
|
||||
statusWindow.x_max = screen.virtualX + screen.width - iconWidth
|
||||
statusWindow.y_min = screen.virtualY + iconHeight
|
||||
statusWindow.y_max = screen.virtualY + screen.height - iconHeight
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case SystemTrayIcon.Unknown:
|
||||
case SystemTrayIcon.Unknown:
|
||||
break;
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:!statusWindow.visible
|
||||
if (!statusWindow.visible) {
|
||||
var point = calcStatusWindowPosition(statusWindow.width, statusWindow.height)
|
||||
statusWindow.x = point.x
|
||||
statusWindow.y = point.y
|
||||
}
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:
|
||||
calcStatusWindowPosition()
|
||||
statusWindow.visible = !statusWindow.visible
|
||||
break
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
mainWindow.visible = !mainWindow.visible
|
||||
break;
|
||||
default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,6 +236,53 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Login Finished"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginFinished()
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "used:"
|
||||
text: user && user.usedBytes ? user.usedBytes : 0
|
||||
validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024}
|
||||
onEditingFinished: {
|
||||
user.usedBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "total:"
|
||||
text: user && user.totalBytes ? user.totalBytes : 0
|
||||
validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024}
|
||||
onEditingFinished: {
|
||||
user.totalBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Split mode"}
|
||||
Toggle { colorScheme: root.colorScheme; checked: user ? user.splitMode : false; onClicked: {user.splitMode = !user.splitMode}}
|
||||
Button { colorScheme: root.colorScheme; text: "Toggle Finished"; onClicked: {user.toggleSplitModeFinished()}}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
colorScheme: root.colorScheme
|
||||
text: user && user.addresses ? user.addresses.join("\n") : "user@protonmail.com"
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: {
|
||||
user.addresses = text.split("\n")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
|
||||
@ -33,8 +33,10 @@ import Notifications 1.0
|
||||
Window {
|
||||
id: root
|
||||
|
||||
width: 640
|
||||
height: 480
|
||||
x: 10
|
||||
y: 10
|
||||
width: 800
|
||||
height: 600
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.darkStyle
|
||||
|
||||
@ -103,12 +105,21 @@ Window {
|
||||
QtObject {
|
||||
property string username: ""
|
||||
property bool loggedIn: false
|
||||
property bool splitMode: false
|
||||
|
||||
property bool setupGuideSeen: true
|
||||
|
||||
property string captionText: "50.3 MB / 20 GB"
|
||||
property var usedBytes: 5350*1024*1024
|
||||
property var totalBytes: 20*1024*1024*1024
|
||||
property string avatarText: "jd"
|
||||
|
||||
property string password: "SMj975NnEYYsqu55GGmlpv"
|
||||
property var addresses: [
|
||||
"janedoe@protonmail.com",
|
||||
"jane@pm.me",
|
||||
"jdoe@pm.me"
|
||||
]
|
||||
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
@ -130,6 +141,30 @@ Window {
|
||||
root.log("<- User (" + username + "): " + msg)
|
||||
}
|
||||
|
||||
function toggleSplitMode(makeActive) {
|
||||
userSignal("toggle split mode "+makeActive)
|
||||
}
|
||||
signal toggleSplitModeFinished()
|
||||
|
||||
function configureAppleMail(address){
|
||||
userSignal("confugure apple mail "+address)
|
||||
}
|
||||
|
||||
function logout(){
|
||||
userSignal("logout")
|
||||
loggedIn = false
|
||||
}
|
||||
function remove(){
|
||||
console.log("remove this", users.count)
|
||||
for (var i=0; i<users.count; i++) {
|
||||
if (users.get(i) === this) {
|
||||
users.remove(i,1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
userSignal("loginUsernamePasswordError")
|
||||
}
|
||||
@ -193,6 +228,17 @@ Window {
|
||||
newLoginUser.login2PasswordRequested.connect(root.login2PasswordRequested)
|
||||
newLoginUser.login2PasswordError.connect(root.login2PasswordError)
|
||||
newLoginUser.login2PasswordErrorAbort.connect(root.login2PasswordErrorAbort)
|
||||
|
||||
|
||||
// add one user on start
|
||||
var haveUserOnStart = false
|
||||
if (haveUserOnStart) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -216,6 +262,10 @@ Window {
|
||||
TabButton {
|
||||
text: "Log"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Settings signals"
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
@ -284,6 +334,7 @@ Window {
|
||||
enabled: bridge === undefined || bridge === null
|
||||
onClicked: {
|
||||
bridge = bridgeComponent.createObject()
|
||||
if (true) bridge._mainWindow.show()
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,9 +419,8 @@ Window {
|
||||
spacing: 5
|
||||
|
||||
Switch {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Internet connection"
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
onCheckedChanged: {
|
||||
checked ? root.internetOn() : root.internetOff()
|
||||
@ -378,115 +428,124 @@ Window {
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update manual ready"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualReady("3.14.1592")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update manual done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualRestartNeeded()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update manual error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualError()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update force"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForce("3.14.1592")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update force error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForceError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update silent done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent error"
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update solent error"
|
||||
onClicked: {
|
||||
root.updateSilentError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update is latest version"
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Bug report send OK"
|
||||
onClicked: {
|
||||
root.updateIsLatestVersion()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send OK"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Bug report send error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Cache anavailable"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheAnavailable()
|
||||
root.cacheUnavailable()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Cache can't move"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheCantMove()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache location change success"
|
||||
onClicked: {
|
||||
root.cacheLocationChangeSuccess()
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Disk full"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.diskFull()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
colorScheme: root.colorScheme
|
||||
id: logTextArea
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
@ -496,20 +555,90 @@ Window {
|
||||
textFormat: TextEdit.RichText
|
||||
//readOnly: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: settingsTab
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Automatic updates:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutomaticUpdateOn; onClicked: root.isAutomaticUpdateOn = !root.isAutomaticUpdateOn}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Autostart:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutostartOn; onClicked: root.isAutostartOn = !root.isAutostartOn}
|
||||
Button {colorScheme: root.colorScheme; text: "Toggle finished"; onClicked: root.toggleAutostartFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Beta:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isBetaEnabled; onClicked: root.isBetaEnabled = !root.isBetaEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "IMAP"
|
||||
text: root.portIMAP
|
||||
onEditingFinished: root.portIMAP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "SMTP"
|
||||
text: root.portSMTP
|
||||
onEditingFinished: root.portSMTP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changePortFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "SMTP using SSL:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.useSSLforSMTP; onClicked: root.useSSLforSMTP = !root.useSSLforSMTP}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Local cache:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDiskCacheEnabled; onClicked: root.isDiskCacheEnabled = !root.isDiskCacheEnabled}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "Path"
|
||||
text: root.diskCachePath
|
||||
implicitWidth: 160
|
||||
onEditingFinished: root.diskCachePath = this.text
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished:"; onClicked: root.changeLocalCacheFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Reset:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.resetFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Check update:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.checkUpdatesFinished()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Bridge bridge
|
||||
|
||||
property string goos: "linux"
|
||||
|
||||
property bool dockIconVisible: false
|
||||
|
||||
// this signals are used only when trying to login with new user (i.e. not in users model)
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
signal loginUsernamePasswordError(string errorMsg)
|
||||
signal loginFreeUserError(string errorMsg)
|
||||
signal loginConnectionError(string errorMsg)
|
||||
signal login2FARequested()
|
||||
signal login2FAError()
|
||||
signal login2FAErrorAbort()
|
||||
signal login2FAError(string errorMsg)
|
||||
signal login2FAErrorAbort(string errorMsg)
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError()
|
||||
signal login2PasswordErrorAbort()
|
||||
signal login2PasswordError(string errorMsg)
|
||||
signal login2PasswordErrorAbort(string errorMsg)
|
||||
signal loginFinished()
|
||||
|
||||
signal internetOff()
|
||||
signal internetOn()
|
||||
@ -521,14 +650,140 @@ Window {
|
||||
signal updateForceError()
|
||||
signal updateSilentRestartNeeded()
|
||||
signal updateSilentError()
|
||||
signal updateIsLatestVersion()
|
||||
function checkUpdates(){
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
property string diskCachePath: "/home/bridge"
|
||||
signal cacheUnavailable()
|
||||
signal cacheCantMove()
|
||||
signal cacheLocationChangeSuccess()
|
||||
signal diskFull()
|
||||
function changeLocalCache(enableDiskCache, diskCachePath) {
|
||||
console.debug("-> disk cache", enableDiskCache, diskCachePath)
|
||||
}
|
||||
signal changeLocalCacheFinished()
|
||||
|
||||
|
||||
// Settings
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
root.isAutomaticUpdateOn = makeItActive
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
function toggleAutostart(makeItActive) {
|
||||
console.debug("-> autostart", makeItActive, root.isAutomaticUpdateOn)
|
||||
}
|
||||
signal toggleAutostartFinished()
|
||||
|
||||
property bool isBetaEnabled : false
|
||||
function toggleBeta(makeItActive){
|
||||
console.debug("-> beta", makeItActive, root.isBetaEnabled)
|
||||
root.isBetaEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isDoHEnabled : true
|
||||
function toggleDoH(makeItActive){
|
||||
console.debug("-> DoH", makeItActive, root.isDoHEnabled)
|
||||
root.isDoHEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool useSSLforSMTP: false
|
||||
function toggleUseSSLforSMTP(makeItActive){
|
||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
||||
}
|
||||
signal toggleUseSSLFinished()
|
||||
|
||||
property string hostname: "127.0.0.1"
|
||||
property int portIMAP: 1143
|
||||
property int portSMTP: 1025
|
||||
function changePorts(imapPort, smtpPort){
|
||||
console.debug("-> ports", imapPort, smtpPort)
|
||||
}
|
||||
function isPortFree(port){
|
||||
if (port == portIMAP) return false
|
||||
if (port == portSMTP) return false
|
||||
if (port == 12345) return false
|
||||
return true
|
||||
}
|
||||
signal changePortFinished()
|
||||
signal portIssueIMAP()
|
||||
signal portIssueSMTP()
|
||||
|
||||
function triggerReset() {
|
||||
console.debug("-> trigger reset")
|
||||
}
|
||||
signal resetFinished()
|
||||
|
||||
property string logsPath: "/home/cuto" // StandardPaths.locate(StandardPaths.DesktopLocation)
|
||||
property string version: "v2.0.X"
|
||||
property string licensePath: "/home/cuto" // StandardPaths.locate(StandardPaths.DesktopLocation)
|
||||
property string releaseNotesLink: "https://protonmail.com/download/bridge/early_releases.html"
|
||||
|
||||
property string currentEmailClient: "" // "Apple Mail 14.0"
|
||||
function updateCurrentMailClient(){
|
||||
currentEmailClient = "Apple Mail 14.0"
|
||||
}
|
||||
|
||||
function reportBug(description,address,emailClient,includeLogs){
|
||||
console.log("report bug")
|
||||
console.log(" description",description)
|
||||
console.log(" address",address)
|
||||
console.log(" emailClient",emailClient)
|
||||
console.log(" includeLogs",includeLogs)
|
||||
}
|
||||
signal reportBugFinished()
|
||||
signal bugReportSendSuccess()
|
||||
signal bugReportSendError()
|
||||
|
||||
signal cacheAnavailable()
|
||||
signal cacheCantMove()
|
||||
property var availableKeychain: ["gnome-keyring", "pass"]
|
||||
property string selectedKeychain
|
||||
function selectKeychain(wantedKeychain){
|
||||
selectedKeychain = wantedKeychain
|
||||
}
|
||||
signal hasNoKeychain()
|
||||
|
||||
signal noActiveKeyForRecipient(string email)
|
||||
signal showMainWindow()
|
||||
|
||||
signal addressChanged(string address)
|
||||
signal addressChangedLogout(string address)
|
||||
signal userDisconnected(string username)
|
||||
signal apiCertIssue()
|
||||
|
||||
|
||||
|
||||
function login(username, password) {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
function login2FA(username, code) {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
function login2Password(username, password) {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
function loginAbort(username) {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
|
||||
signal diskFull()
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.debug("<- loginUsernamePasswordError")
|
||||
@ -557,6 +812,9 @@ Window {
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.debug("<- login2PasswordErrorAbort")
|
||||
}
|
||||
onLoginFinished: {
|
||||
console.debug("<- loginFinished")
|
||||
}
|
||||
|
||||
onInternetOff: {
|
||||
console.debug("<- internetOff")
|
||||
@ -571,30 +829,6 @@ Window {
|
||||
Bridge {
|
||||
backend: root
|
||||
|
||||
onLogin: {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
onLogin2FA: {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
onLogin2Password: {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
onLoginAbort: {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
167
internal/frontend/qml/BugReportView.qml
Normal file
167
internal/frontend/qml/BugReportView.qml
Normal file
@ -0,0 +1,167 @@
|
||||
// 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 Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property var selectedAddress
|
||||
|
||||
Label {
|
||||
text: qsTr("Report a problem")
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Heading
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
id: description
|
||||
property int _minChars: 150
|
||||
property bool _inputOK: description.text.length>=description._minChars
|
||||
|
||||
label: qsTr("Description")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 100
|
||||
hint: description.text.length + "/800"
|
||||
placeholderText: qsTr("Tell us what went wrong or isn't working (min. 150 characters).")
|
||||
onEditingFinished: {
|
||||
if (!description._inputOK) {
|
||||
description.error = true
|
||||
description.assistiveText = qsTr("Enter a problem description (min. 150 characters)")
|
||||
} else {
|
||||
description.error = false
|
||||
description.assistiveText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
property bool _inputOK: root.isValidEmail(address.text)
|
||||
|
||||
label: qsTr("Your contact email")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
|
||||
|
||||
onEditingFinished: {
|
||||
if (!address._inputOK) {
|
||||
address.error = true
|
||||
address.assistiveText = qsTr("Enter valid email address")
|
||||
} else {
|
||||
address.assistiveText = ""
|
||||
address.error = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: emailClient
|
||||
property bool _inputOK: emailClient.text.length > 0
|
||||
|
||||
label: qsTr("Your email client (including version)")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. Apple Mail 14.0")
|
||||
onEditingFinished: {
|
||||
if (!emailClient._inputOK) {
|
||||
emailClient.assistiveText = qsTr("Enter an email client name and version")
|
||||
emailClient.error = true
|
||||
} else {
|
||||
emailClient.assistiveText = ""
|
||||
emailClient.error = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: includeLogs
|
||||
text: qsTr("Include my recent logs")
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
}
|
||||
Button {
|
||||
Layout.leftMargin: 12
|
||||
text: qsTr("View logs")
|
||||
secondary: true
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: Qt.openUrlExternally("file://"+root.backend.logsPath)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: {
|
||||
var address = "bridge@protonmail.com"
|
||||
var mailTo = `<a href="mailto://${address}">${address}</a>`
|
||||
return qsTr("These reports are not end-to-end encrypted. In case of sensitive information, contact us at %1.").arg(mailTo)
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
type: Label.Caption
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
|
||||
Button {
|
||||
id: sendButton
|
||||
text: qsTr("Send")
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.submit()
|
||||
enabled: description._inputOK && address._inputOK && emailClient._inputOK
|
||||
|
||||
Connections {target: root.backend; onReportBugFinished: sendButton.loading = false }
|
||||
}
|
||||
|
||||
function setDefaultValue() {
|
||||
description.text = ""
|
||||
address.text = root.selectedAddress
|
||||
emailClient.text = root.backend.currentEmailClient
|
||||
includeLogs.checked = true
|
||||
}
|
||||
|
||||
function isValidEmail(text){
|
||||
var reEmail = /\w+@\w+\.\w+/
|
||||
return reEmail.test(text)
|
||||
}
|
||||
|
||||
function submit() {
|
||||
sendButton.loading = true
|
||||
root.backend.reportBug(
|
||||
description.text,
|
||||
address.text,
|
||||
emailClient.text,
|
||||
includeLogs.checked
|
||||
)
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValue()
|
||||
|
||||
|
||||
onBack: {
|
||||
root.setDefaultValue()
|
||||
root.parent.showHelpView()
|
||||
}
|
||||
}
|
||||
73
internal/frontend/qml/Configuration.qml
Normal file
73
internal/frontend/qml/Configuration.qml
Normal file
@ -0,0 +1,73 @@
|
||||
// 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
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property string title
|
||||
property string hostname
|
||||
property string port
|
||||
property string username
|
||||
property string password
|
||||
property string security
|
||||
|
||||
implicitWidth: 304
|
||||
implicitHeight: content.height + 2*root._margin
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
radius: 9
|
||||
|
||||
property int _margin: 24
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: root.width - 2*root._margin
|
||||
anchors{
|
||||
top: root.top
|
||||
left: root.left
|
||||
leftMargin : root._margin
|
||||
rightMargin : root._margin
|
||||
topMargin : root._margin
|
||||
bottomMargin : root._margin
|
||||
}
|
||||
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.title
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Item{}
|
||||
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Password") ; value: root.password }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Security") ; value: root.security }
|
||||
}
|
||||
}
|
||||
|
||||
81
internal/frontend/qml/ConfigurationItem.qml
Normal file
81
internal/frontend/qml/ConfigurationItem.qml
Normal file
@ -0,0 +1,81 @@
|
||||
// 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.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var colorScheme
|
||||
property string label
|
||||
property string value
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
@ -26,12 +26,26 @@ Item {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property var noUser: QtObject {
|
||||
property var avatarText: ""
|
||||
property var username: ""
|
||||
property var password: ""
|
||||
property var usedBytes: 1
|
||||
property var totalBytes: 1
|
||||
property var loggedIn: false
|
||||
property var splitMode: false
|
||||
property var addresses: []
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@ -91,6 +105,8 @@ Item {
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-question-circle.svg"
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
|
||||
Button {
|
||||
@ -109,10 +125,14 @@ Item {
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-cog-wheel.svg"
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Item {implicitHeight:10}
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
@ -122,14 +142,20 @@ Item {
|
||||
|
||||
ListView {
|
||||
id: accounts
|
||||
|
||||
property var _topBottomMargins: 24
|
||||
property var _leftRightMargins: 16
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
Layout.leftMargin: accounts._leftRightMargins
|
||||
Layout.rightMargin: accounts._leftRightMargins
|
||||
Layout.topMargin: accounts._topBottomMargins
|
||||
Layout.bottomMargin: accounts._topBottomMargins
|
||||
|
||||
spacing: 12
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
header: Rectangle {
|
||||
height: headerLabel.height+16
|
||||
@ -142,11 +168,28 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: leftBar.colorScheme.interaction_default_active
|
||||
radius: 4
|
||||
}
|
||||
|
||||
model: root.backend.users
|
||||
delegate: AccountDelegate{
|
||||
width: leftBar.width - 2*accounts._leftRightMargins
|
||||
|
||||
id: accountDelegate
|
||||
colorScheme: leftBar.colorScheme
|
||||
user: modelData
|
||||
user: root.backend.users.get(index)
|
||||
onClicked: {
|
||||
var user = root.backend.users.get(index)
|
||||
accounts.currentIndex = index
|
||||
if (user.loggedIn) {
|
||||
rightContent.showAccount()
|
||||
} else {
|
||||
signIn.username = user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,15 +224,16 @@ Item {
|
||||
|
||||
icon.source: "./icons/ic-plus.svg"
|
||||
|
||||
onClicked: root.showSignIn()
|
||||
onClicked: {
|
||||
signIn.username = ""
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rightPlane
|
||||
|
||||
Rectangle { // right content background
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
@ -199,14 +243,44 @@ Item {
|
||||
id: rightContent
|
||||
anchors.fill: parent
|
||||
|
||||
AccountView {
|
||||
AccountView { // 0
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
user: {
|
||||
if (accounts.currentIndex < 0) return root.noUser
|
||||
if (root.backend.users.count == 0) return root.noUser
|
||||
return root.backend.users.get(accounts.currentIndex)
|
||||
}
|
||||
onShowSignIn: {
|
||||
signIn.username = this.user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
onShowSetupGuide: {
|
||||
root.showSetupGuide(user,address)
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
GridLayout { // 1
|
||||
columns: 2
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
Layout.leftMargin: 18
|
||||
Layout.topMargin: 10
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: rightContent.showAccount()
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
|
||||
SignIn {
|
||||
id: signIn
|
||||
Layout.topMargin: 68
|
||||
Layout.leftMargin: 80
|
||||
Layout.leftMargin: 80 - backButton.width - 18
|
||||
Layout.rightMargin: 80
|
||||
Layout.bottomMargin: 68
|
||||
Layout.preferredWidth: 320
|
||||
@ -214,21 +288,70 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
user: (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) ? root.backend.users.get(0) : undefined
|
||||
backend: root.backend
|
||||
|
||||
onLogin : { root.login ( username , password ) }
|
||||
onLogin2FA : { root.login2FA ( username , code ) }
|
||||
onLogin2Password : { root.login2Password ( username , password ) }
|
||||
onLoginAbort : { root.loginAbort ( username ) }
|
||||
onLogin : { root.backend.login ( username , password ) }
|
||||
onLogin2FA : { root.backend.login2FA ( username , code ) }
|
||||
onLogin2Password : { root.backend.login2Password ( username , password ) }
|
||||
onLoginAbort : { root.backend.loginAbort ( username ) }
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettings { // 2
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
}
|
||||
|
||||
PortSettings { // 3
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
SMTPSettings { // 4
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
LocalCacheSettings { // 5
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
}
|
||||
|
||||
HelpView { // 6
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
BugReportView { // 7
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
selectedAddress: {
|
||||
if (accounts.currentIndex < 0) return ""
|
||||
if (root.backend.users.count == 0) return ""
|
||||
return root.backend.users.get(accounts.currentIndex).addresses[0]
|
||||
}
|
||||
}
|
||||
|
||||
function showAccount () { rightContent.currentIndex = 0 }
|
||||
function showSignIn () { rightContent.currentIndex = 1 }
|
||||
function showGeneralSettings () { rightContent.currentIndex = 2 }
|
||||
function showPortSettings () { rightContent.currentIndex = 3 }
|
||||
function showSMTPSettings () { rightContent.currentIndex = 4 }
|
||||
function showLocalCacheSettings () { rightContent.currentIndex = 5 }
|
||||
function showHelpView () { rightContent.currentIndex = 6 }
|
||||
function showBugReport () { rightContent.currentIndex = 7 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showSignIn() {
|
||||
rightContent.currentIndex = 1
|
||||
function showLocalCacheSettings(){rightContent.showLocalCacheSettings() }
|
||||
function showSettings(){rightContent.showGeneralSettings() }
|
||||
function showHelp(){rightContent.showHelpView() }
|
||||
function showSignIn(username){
|
||||
signIn.username = username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
168
internal/frontend/qml/GeneralSettings.qml
Normal file
168
internal/frontend/qml/GeneralSettings.qml
Normal file
@ -0,0 +1,168 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property bool _isAdvancedShown: false
|
||||
property var notifications
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Settings")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Automatic updates")
|
||||
description: qsTr("Bridge will automatically update in the background.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autostart
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Automatically start Bridge")
|
||||
description: qsTr("The app will autostart everytime you reset your device.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutostartOn
|
||||
onClicked: {
|
||||
autostart.loading = true
|
||||
root.backend.toggleAutostart(!autoUpdate.checked)
|
||||
}
|
||||
Connections{
|
||||
target: root.backend
|
||||
onToggleAutostartFinished: {
|
||||
autostart.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: beta
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Enable Beta access")
|
||||
description: qsTr("Be the first one to see new features.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isBetaEnabled
|
||||
onClicked: {
|
||||
if (!beta.checked) {
|
||||
root.notifications.askEnableBeta()
|
||||
} else {
|
||||
root.notifications.askDisableBeta()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColorImage {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
source: root._isAdvancedShown ? "icons/ic-chevron-up.svg" : "icons/ic-chevron-down.svg"
|
||||
color: root.colorScheme.interaction_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: advSettLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Advanced settings")
|
||||
color: root.colorScheme.interaction_norm
|
||||
type: Label.Body
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: doh
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Alternative routing")
|
||||
description: qsTr("If Proton’s servers are blocked in your location, alternative network routing will be used to reach Proton.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isDoHEnabled
|
||||
onClicked: root.backend.toggleDoH(!doh.checked)
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: ports
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Choose which ports are used by default.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showPortSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: smtp
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showSMTPSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: cache
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
actionText: qsTr("Configure")
|
||||
description: qsTr("Configure Bridge's local cache settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showLocalCacheSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reset
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Reset Bridge")
|
||||
actionText: qsTr("Reset")
|
||||
description: qsTr("Remove all accounts, clear cached data, and restore the original settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.notifications.askResetBridge()
|
||||
}
|
||||
}
|
||||
|
||||
onBack: root.parent.showAccount()
|
||||
}
|
||||
110
internal/frontend/qml/HelpView.qml
Normal file
110
internal/frontend/qml/HelpView.qml
Normal file
@ -0,0 +1,110 @@
|
||||
// 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 Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Help")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: setupPage
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Installation and setup")
|
||||
actionText: qsTr("Go to help topics")
|
||||
actionIcon: "./icons/ic-external-link.svg"
|
||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||
type: SettingsItem.PrimaryButton
|
||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/bridge/install")}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: checkUpdates
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Updates")
|
||||
actionText: qsTr("Check now")
|
||||
description: qsTr("Check that you're using the latest version of Bridge. To stay up to date, enable auto-updates in settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
checkUpdates.loading = true
|
||||
root.backend.checkUpdates()
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: logs
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Logs")
|
||||
actionText: qsTr("View logs")
|
||||
description: qsTr("Open and review logs to troubleshoot.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {Qt.openUrlExternally(root.backend.logsPath)}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reportBug
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Report a problem")
|
||||
actionText: qsTr("Report a problem")
|
||||
description: qsTr("Something not working as expected? Let us know.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.backend.updateCurrentMailClient()
|
||||
root.parent.showBugReport()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Caption
|
||||
color: root.colorScheme.text_weak
|
||||
textFormat: Text.RichText
|
||||
linkColor: root.colorScheme.interaction_norm_active
|
||||
|
||||
text: {
|
||||
var version = root.backend.version
|
||||
var license = qsTr("License")
|
||||
var licensePath = root.backend.licensePath
|
||||
var release= qsTr("Release notes")
|
||||
var releaseNotesLink = root.backend.releaseNotesLink
|
||||
return `<p style="text-align:center;">Proton Mail Bridge v${version}<br>
|
||||
© 2021 Proton Technologies AG<br>
|
||||
<a style="color: ${linkColor};" href="${licensePath}">${license}</a>
|
||||
<a style="color: ${linkColor};" href="${releaseNotesLink}">${release}</a>
|
||||
</p>`
|
||||
}
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showAccount()
|
||||
}
|
||||
}
|
||||
146
internal/frontend/qml/LocalCacheSettings.qml
Normal file
146
internal/frontend/qml/LocalCacheSettings.qml
Normal file
@ -0,0 +1,146 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property var notifications
|
||||
property bool _diskCacheEnabled: true
|
||||
property string _diskCachePath: "/home"
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Bridge caches your encrypted messages localy to optimise the communication with the local client. Disabling this feature might have a nevative impact on performance.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Enable local cache")
|
||||
description: "When enabled messages are stored on disk." // TODO: wrong text in wireframe
|
||||
type: SettingsItem.Toggle
|
||||
checked: root._diskCacheEnabled
|
||||
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Current cache location")
|
||||
actionText: qsTr("Change location")
|
||||
description: root._diskCachePath
|
||||
type: SettingsItem.Button
|
||||
enabled: root._diskCacheEnabled
|
||||
onClicked: {
|
||||
pathDialog.open()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: pathDialog
|
||||
title: qsTr("Select cache location")
|
||||
folder: shortcuts.home
|
||||
onAccepted: root.sanitizePath(pathDialog.fileUrl.toString())
|
||||
selectFolder: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: (
|
||||
root.backend.diskCachePath != root._diskCachePath ||
|
||||
root.backend.isDiskCacheEnabled != root._diskCacheEnabled
|
||||
)
|
||||
onClicked: {
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangeLocalCacheFinished: {
|
||||
submitButton.loading = false
|
||||
root.setDefaultValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function submit(){
|
||||
console.log("submit")
|
||||
if (!root._diskCacheEnabled && root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askDisableLocalCache()
|
||||
return
|
||||
}
|
||||
|
||||
if (root._diskCacheEnabled && !root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askEnableLocalCache(root._diskCachePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Not asking, only changing path
|
||||
submitButton.loading = true
|
||||
root.backend.changeLocalCache(root.backend.isDiskCacheEnabled, root._diskCachePath)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
root._diskCacheEnabled = root.backend.isDiskCacheEnabled
|
||||
root._diskCachePath = root.backend.diskCachePath
|
||||
}
|
||||
|
||||
function sanitizePath(path) {
|
||||
var pattern = "file://"
|
||||
if (root.backend.goos=="windows") pattern+="/"
|
||||
root._diskCachePath = path.replace(pattern, "")
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
||||
@ -62,7 +62,7 @@ ApplicationWindow {
|
||||
return
|
||||
}
|
||||
|
||||
root.showSetup(user)
|
||||
root.showSetup(user,user.addresses[0])
|
||||
}
|
||||
|
||||
onRowsAboutToBeRemoved: {
|
||||
@ -78,15 +78,6 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function showSetup(user) {
|
||||
setupGuide.user = user
|
||||
if (setupGuide.user) {
|
||||
contentLayout._showSetup = true
|
||||
} else {
|
||||
contentLayout._showSetup = false
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: contentLayout
|
||||
|
||||
@ -111,12 +102,18 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ContentWrapper {
|
||||
id: contentWrapper
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
onShowSetupGuide: {
|
||||
root.showSetup(user,address)
|
||||
}
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
}
|
||||
@ -161,7 +158,7 @@ ApplicationWindow {
|
||||
Layout.fillWidth: true
|
||||
|
||||
onDismissed: {
|
||||
root.showSetup(null)
|
||||
root.showSetup(null,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,5 +166,25 @@ ApplicationWindow {
|
||||
NotificationPopups {
|
||||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
mainWindow: root
|
||||
}
|
||||
|
||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||
function showSettings() { contentWrapper.showSettings() }
|
||||
function showHelp() { contentWrapper.showHelp() }
|
||||
|
||||
function showSignIn(username) {
|
||||
if (contentLayout.currentIndex == 1) return
|
||||
contentWrapper.showSignIn(username)
|
||||
}
|
||||
|
||||
function showSetup(user, address) {
|
||||
setupGuide.user = user
|
||||
setupGuide.address = address
|
||||
if (setupGuide.user) {
|
||||
contentLayout._showSetup = true
|
||||
} else {
|
||||
contentLayout._showSetup = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,13 +55,12 @@ Dialog {
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
// TODO: Add info icon?
|
||||
return ""
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-success.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-alert.svg"
|
||||
}
|
||||
}
|
||||
@ -110,6 +109,8 @@ Dialog {
|
||||
action: modelData
|
||||
|
||||
secondary: index > 0
|
||||
|
||||
loading: notification.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ Item {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var notifications
|
||||
property var mainWindow
|
||||
|
||||
property int notificationWhitelist: NotificationFilter.FilterConsts.All
|
||||
property int notificationBlacklist: NotificationFilter.FilterConsts.None
|
||||
@ -42,6 +43,7 @@ Item {
|
||||
Banner {
|
||||
colorScheme: root.colorScheme
|
||||
notification: bannerNotificationFilter.topmost
|
||||
mainWindow: root.mainWindow
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
@ -66,17 +68,17 @@ Item {
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.bugReportSendSuccess
|
||||
notification: root.notifications.disableBeta
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.bugReportSendError
|
||||
notification: root.notifications.enableBeta
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.cacheAnavailable
|
||||
notification: root.notifications.cacheUnavailable
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
@ -88,4 +90,24 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.diskFull
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableSplitMode
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.disableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.resetBridge
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ QtObject {
|
||||
|
||||
property bool dismissed: false
|
||||
property bool active: false
|
||||
property bool loading: false
|
||||
readonly property var occurred: active ? new Date() : undefined
|
||||
|
||||
property var data
|
||||
|
||||
@ -29,6 +29,13 @@ QtObject {
|
||||
property StatusWindow frontendStatus
|
||||
property SystemTrayIcon frontendTray
|
||||
|
||||
signal askDisableBeta()
|
||||
signal askEnableBeta()
|
||||
signal askEnableSplitMode(var user)
|
||||
signal askDisableLocalCache()
|
||||
signal askEnableLocalCache(var path)
|
||||
signal askResetBridge()
|
||||
|
||||
enum Group {
|
||||
Connection = 1,
|
||||
Update = 2,
|
||||
@ -48,12 +55,20 @@ QtObject {
|
||||
root.updateForceError,
|
||||
root.updateSilentRestartNeeded,
|
||||
root.updateSilentError,
|
||||
root.updateIsLatestVersion,
|
||||
root.disableBeta,
|
||||
root.enableBeta,
|
||||
root.bugReportSendSuccess,
|
||||
root.bugReportSendError,
|
||||
root.cacheAnavailable,
|
||||
root.cacheUnavailable,
|
||||
root.cacheCantMove,
|
||||
root.accountChanged,
|
||||
root.diskFull
|
||||
root.diskFull,
|
||||
root.cacheLocationChangeSuccess,
|
||||
root.enableSplitMode,
|
||||
root.disableLocalCache,
|
||||
root.enableLocalCache,
|
||||
root.resetBridge
|
||||
]
|
||||
|
||||
// Connection
|
||||
@ -93,10 +108,18 @@ QtObject {
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update")
|
||||
text: qsTr("Install update")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: call update from backend
|
||||
root.backend.installUpdate()
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
},
|
||||
@ -104,7 +127,6 @@ QtObject {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: start timer here
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
}
|
||||
@ -128,14 +150,14 @@ QtObject {
|
||||
text: qsTr("Restart Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.backend.restart()
|
||||
root.updateManualRestartNeeded.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification updateManualError: Notification {
|
||||
text: qsTr("Bridge couldn’t update")
|
||||
text: qsTr("Bridge couldn’t update. Please update manually.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update
|
||||
@ -147,19 +169,28 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
action: Action {
|
||||
text: qsTr("Update manually")
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.updateManualError.active = false
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateManualError.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.updateManualReady.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")
|
||||
description: qsTr("This version of Bridge is no longer supported, please update.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
@ -175,18 +206,26 @@ QtObject {
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update")
|
||||
text: qsTr("Install update")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: trigger update here
|
||||
root.backend.installUpdate()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quite Bridge")
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: quit Bridge here
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateForce.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
|
||||
onTriggered: {
|
||||
root.backend.quit()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
}
|
||||
@ -195,7 +234,7 @@ QtObject {
|
||||
|
||||
property Notification updateForceError: Notification {
|
||||
text: qsTr("Bridge coudn’t update")
|
||||
description: qsTr("You must update manually. Go to: https:/protonmail.com/bridge/download")
|
||||
description: qsTr("You must update manually.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
@ -213,15 +252,15 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: trigger update here
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateForceError.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quite Bridge")
|
||||
text: qsTr("Quit Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: quit Bridge here
|
||||
root.backend.quit()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
}
|
||||
@ -245,7 +284,7 @@ QtObject {
|
||||
text: qsTr("Restart Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.backend.restart()
|
||||
root.updateSilentRestartNeeded.active = false
|
||||
}
|
||||
}
|
||||
@ -268,18 +307,105 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateSilentError.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification updateIsLatestVersion: Notification {
|
||||
text: qsTr("Bridge is up to date")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Update
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onUpdateIsLatestVersion: {
|
||||
root.updateIsLatestVersion.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: Action {
|
||||
text: qsTr("Ok")
|
||||
|
||||
onTriggered: {
|
||||
root.updateIsLatestVersion.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification disableBeta: Notification {
|
||||
text: qsTr("Disable beta access?")
|
||||
description: qsTr("This resets Bridge to the current release and will restart the app. Your preferences, cached data, and email client configurations will be cleared. ")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskDisableBeta: {
|
||||
root.disableBeta.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.disableBeta.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Disable and restart")
|
||||
onTriggered: {
|
||||
root.backend.toggleBeta(false)
|
||||
root.disableBeta.loading = true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableBeta: Notification {
|
||||
text: qsTr("Enable beta access?")
|
||||
description: qsTr("Bridge will update to the latest beta version according to your update preferences. Disabling beta access later on will reset Bridge and require you to reconfigure your client.")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableBeta: {
|
||||
root.enableBeta.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Enable")
|
||||
onTriggered: {
|
||||
root.backend.toggleBeta(true)
|
||||
root.enableBeta.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onTriggered: {
|
||||
root.enableBeta.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
// Bug reports
|
||||
property Notification bugReportSendSuccess: Notification {
|
||||
text: qsTr("Bug report sent")
|
||||
description: qsTr("We’ve received your report, thank you! Our team will get back to you as soon as we can.")
|
||||
text: qsTr("Thank you for the report. We'll get back to you as soon as we can.")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Success
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
@ -302,10 +428,10 @@ QtObject {
|
||||
}
|
||||
|
||||
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
|
||||
text: qsTr("Report could not be sent. Try again or email us directly.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
@ -323,7 +449,7 @@ QtObject {
|
||||
}
|
||||
|
||||
// Cache
|
||||
property Notification cacheAnavailable: Notification {
|
||||
property Notification cacheUnavailable: Notification {
|
||||
text: qsTr("Cache location is unavailable")
|
||||
description: qsTr("Check the directory or change it in your settings.")
|
||||
type: Notification.NotificationType.Warning
|
||||
@ -331,8 +457,8 @@ QtObject {
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheAnavailable: {
|
||||
root.cacheAnavailable.active = true
|
||||
onCacheUnavailable: {
|
||||
root.cacheUnavailable.active = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,13 +466,15 @@ QtObject {
|
||||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
onTriggered: {
|
||||
root.cacheAnavailable.active = false
|
||||
root.backend.quit()
|
||||
root.cacheUnavailable.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Change location")
|
||||
onTriggered: {
|
||||
root.cacheAnavailable.active = false
|
||||
root.cacheUnavailable.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -376,6 +504,31 @@ QtObject {
|
||||
text: qsTr("Change location")
|
||||
onTriggered: {
|
||||
root.cacheCantMove.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification cacheLocationChangeSuccess: Notification {
|
||||
text: qsTr("Cache location successfully changed")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Success
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheLocationChangeSuccess: {
|
||||
console.log("notify location changed succesfully")
|
||||
root.cacheLocationChangeSuccess.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Ok")
|
||||
onTriggered: {
|
||||
root.cacheLocationChangeSuccess.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -414,6 +567,7 @@ QtObject {
|
||||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
onTriggered: {
|
||||
root.backend.quit()
|
||||
root.diskFull.active = false
|
||||
}
|
||||
},
|
||||
@ -421,6 +575,171 @@ QtObject {
|
||||
text: qsTr("Settings")
|
||||
onTriggered: {
|
||||
root.diskFull.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableSplitMode: Notification {
|
||||
text: qsTr("Enable split mode?")
|
||||
description: qsTr("Changing between split and combined address mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var user
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableSplitMode: {
|
||||
root.enableSplitMode.user = user
|
||||
root.enableSplitMode.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null
|
||||
onToggleSplitModeFinished: {
|
||||
root.enableSplitMode.active = false
|
||||
root.enableSplitMode.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.enableSplitMode.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Enable split mode")
|
||||
onTriggered: {
|
||||
root.enableSplitMode.loading = true
|
||||
root.enableSplitMode.user.toggleSplitMode(true)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification disableLocalCache: Notification {
|
||||
text: qsTr("Disable local cache?")
|
||||
description: qsTr("This action will clear your local cache, including locally stored messages. Bridge will restart.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskDisableLocalCache: {
|
||||
root.disableLocalCache.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onChangeLocalCacheFinished: {
|
||||
root.disableLocalCache.active = false
|
||||
root.disableLocalCache.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.disableLocalCache.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Disable and restart")
|
||||
onTriggered: {
|
||||
root.disableLocalCache.loading = true
|
||||
root.backend.changeLocalCache(false, root.backend.diskCachePath)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableLocalCache: Notification {
|
||||
text: qsTr("Enable local cache?")
|
||||
description: qsTr("Bridge will restart.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var path
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableLocalCache: {
|
||||
root.enableLocalCache.active = true
|
||||
root.enableLocalCache.path = path
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onChangeLocalCacheFinished: {
|
||||
root.enableLocalCache.active = false
|
||||
root.enableLocalCache.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Enable and restart")
|
||||
onTriggered: {
|
||||
root.enableLocalCache.loading = true
|
||||
root.backend.changeLocalCache(true, root.enableLocalCache.path)
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.enableLocalCache.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification resetBridge: Notification {
|
||||
text: qsTr("Reset Bridge?")
|
||||
description: qsTr("This will clear your accounts, preferences, and cached data. You will need to reconfigure your email client. Bridge will automatically restart")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var user
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskResetBridge: {
|
||||
root.resetBridge.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onResetFinished: {
|
||||
root.resetBridge.active = false
|
||||
root.resetBridge.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.resetBridge.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Reset and restart")
|
||||
onTriggered: {
|
||||
root.resetBridge.loading = true
|
||||
root.backend.triggerReset()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
154
internal/frontend/qml/PortSettings.qml
Normal file
154
internal/frontend/qml/PortSettings.qml
Normal file
@ -0,0 +1,154 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property bool _valuesOK: !imapField.error && !smtpField.error
|
||||
property bool _valuesChanged: (
|
||||
imapField.text*1 != root.backend.portIMAP ||
|
||||
smtpField.text*1 != root.backend.portSMTP
|
||||
)
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of your email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 16
|
||||
|
||||
TextField {
|
||||
id: imapField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("IMAP port")
|
||||
Layout.preferredWidth: 160
|
||||
onEditingFinished: root.validate(imapField)
|
||||
}
|
||||
TextField {
|
||||
id: smtpField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("SMTP port")
|
||||
Layout.preferredWidth: 160
|
||||
onEditingFinished: root.validate(smtpField)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: root._valuesOK && root._valuesChanged
|
||||
onClicked: {
|
||||
submitButton.loading = true
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangePortFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function validate(field) {
|
||||
var num = field.text*1
|
||||
if (! (num > 1 && num < 65536) ) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Invalid port number.")
|
||||
return
|
||||
}
|
||||
|
||||
if (imapField.text == smtpField.text) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Port numbers must be different.")
|
||||
return
|
||||
}
|
||||
|
||||
field.error = false
|
||||
field.assistiveText = ""
|
||||
}
|
||||
|
||||
function isPortFree(field) {
|
||||
field.error = false
|
||||
field.assistiveText = ""
|
||||
|
||||
var num = field.text*1
|
||||
if (num == root.backend.portIMAP) return true
|
||||
if (num == root.backend.portSMTP) return true
|
||||
if (!root.backend.isPortFree(num)) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Port occupied.")
|
||||
submitButton.loading = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function submit(){
|
||||
submitButton.loading = true
|
||||
if (!isPortFree(imapField)) return
|
||||
if (!isPortFree(smtpField)) return
|
||||
root.backend.changePorts(imapField.text, smtpField.text)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
imapField.text = backend.portIMAP
|
||||
smtpField.text = backend.portSMTP
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
||||
@ -246,9 +246,25 @@ T.Button {
|
||||
}
|
||||
}
|
||||
|
||||
border.color: control.colorScheme.border_norm
|
||||
border.color: {
|
||||
return control.colorScheme.border_norm
|
||||
}
|
||||
border.width: secondary && !borderless ? 1 : 0
|
||||
|
||||
opacity: control.enabled || control.loading ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!control.colorScheme) {
|
||||
console.trace()
|
||||
var next = root
|
||||
for (var i = 0; i<1000; i++) {
|
||||
console.log(i, next, "colorscheme", next.colorScheme)
|
||||
next = next.parent
|
||||
if (!next) break
|
||||
}
|
||||
console.error("ColorScheme not defined")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ import QtQuick 2.8
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
color: Style.transparent
|
||||
color: "transparent"
|
||||
|
||||
property color fillColor : Style.currentStyle.background_norm
|
||||
property color strokeColor : Style.currentStyle.background_strong
|
||||
|
||||
@ -20,6 +20,7 @@ import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@ -86,9 +87,10 @@ Item {
|
||||
property alias wrapMode: control.wrapMode
|
||||
|
||||
implicitWidth: background.width
|
||||
implicitHeight: control.implicitHeight +
|
||||
Math.max(label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin, hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin) +
|
||||
assistiveText.implicitHeight
|
||||
implicitHeight: control.implicitHeight + Math.max(
|
||||
label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin,
|
||||
hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin
|
||||
) + assistiveText.implicitHeight
|
||||
|
||||
property alias label: label.text
|
||||
property alias hint: hint.text
|
||||
@ -96,6 +98,8 @@ Item {
|
||||
|
||||
property bool error: false
|
||||
|
||||
signal editingFinished()
|
||||
|
||||
// Backgroud is moved away from within control as it will be clipped with scrollview
|
||||
Rectangle {
|
||||
id: background
|
||||
@ -200,12 +204,16 @@ Item {
|
||||
T.TextArea {
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding)
|
||||
implicitWidth: Math.max(
|
||||
contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding
|
||||
)
|
||||
implicitHeight: Math.max(
|
||||
contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding
|
||||
)
|
||||
|
||||
padding: 8
|
||||
leftPadding: 12
|
||||
@ -216,6 +224,8 @@ Item {
|
||||
selectionColor: control.palette.highlight
|
||||
selectedTextColor: control.palette.highlightedText
|
||||
|
||||
onEditingFinished: root.editingFinished()
|
||||
|
||||
cursorDelegate: Rectangle {
|
||||
id: cursor
|
||||
width: 1
|
||||
|
||||
107
internal/frontend/qml/Proton/Toggle.qml
Normal file
107
internal/frontend/qml/Proton/Toggle.qml
Normal file
@ -0,0 +1,107 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
RowLayout{
|
||||
id: root
|
||||
property var colorScheme
|
||||
property bool checked
|
||||
property bool disabled
|
||||
property bool hovered
|
||||
property bool loading
|
||||
|
||||
signal clicked
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
implicitWidth: 40
|
||||
implicitHeight: 24
|
||||
|
||||
radius: 20
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root.disabled) return root.colorScheme.background_strong
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
border {
|
||||
width: 1
|
||||
color: (root.disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: indicator.verticalCenter
|
||||
anchors.left: indicator.left
|
||||
anchors.leftMargin: root.checked ? 16 : 0
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root.disabled) return root.colorScheme.field_disabled
|
||||
|
||||
if (root.checked) {
|
||||
if (root.hovered) return root.colorScheme.interaction_norm_hover
|
||||
return root.colorScheme.interaction_norm
|
||||
} else {
|
||||
if (root.hovered) return root.colorScheme.field_hover
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/ic-check.svg"
|
||||
color: root.colorScheme.background_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
visible: root.checked
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/Loader_16.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
visible: root.loading
|
||||
|
||||
RotationAnimation {
|
||||
target: loader
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
from: 0
|
||||
to: 360
|
||||
direction: RotationAnimation.Clockwise
|
||||
running: root.loading
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: indicator
|
||||
hoverEnabled: true
|
||||
onEntered: {root.hovered = true }
|
||||
onExited: {root.hovered = false }
|
||||
onClicked: { root.clicked();}
|
||||
onPressed: {root.hovered = true }
|
||||
onReleased: { root.hovered = containsMouse }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,3 +34,4 @@ RoundedRectangle 4.0 RoundedRectangle.qml
|
||||
Switch 4.0 Switch.qml
|
||||
TextArea 4.0 TextArea.qml
|
||||
TextField 4.0 TextField.qml
|
||||
Toggle 4.0 Toggle.qml
|
||||
|
||||
120
internal/frontend/qml/SMTPSettings.qml
Normal file
120
internal/frontend/qml/SMTPSettings.qml
Normal file
@ -0,0 +1,120 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
ButtonGroup{ id: protocolSelection }
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection security")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: sslButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("SSL")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: starttlsButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("STARTLS")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
onClicked: {
|
||||
submitButton.loading = true
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onToggleUseSSLFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function submit(){
|
||||
submitButton.loading = true
|
||||
root.backend.toggleUseSSLforSMTP(sslButton.checked)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
sslButton.checked = root.backend.useSSLforSMTP
|
||||
starttlsButton.checked = !root.backend.useSSLforSMTP
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
||||
105
internal/frontend/qml/SettingsItem.qml
Normal file
105
internal/frontend/qml/SettingsItem.qml
Normal file
@ -0,0 +1,105 @@
|
||||
// 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 Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
property var colorScheme
|
||||
|
||||
property string text: "Text"
|
||||
property string actionText: "Action"
|
||||
property string actionIcon: ""
|
||||
property string description: "Lorem ipsum dolor sit amet"
|
||||
property var type: SettingsItem.ActionType.Toggle
|
||||
|
||||
property bool checked: true
|
||||
property bool disabled: false
|
||||
property bool loading: false
|
||||
|
||||
signal clicked
|
||||
|
||||
spacing: 20
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.parent.Layout.maximumWidth
|
||||
|
||||
enum ActionType {
|
||||
Toggle = 1, Button = 2, PrimaryButton = 3
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
id:mainLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: root.text
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.minimumWidth: mainLabel.width
|
||||
Layout.maximumWidth: root.Layout.maximumWidth - root.spacing - (
|
||||
toggle.visible ? toggle.width : button.width
|
||||
)
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
colorScheme: root.colorScheme
|
||||
text: root.description
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Toggle {
|
||||
id: toggle
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type == SettingsItem.ActionType.Toggle
|
||||
|
||||
checked: root.checked
|
||||
loading: root.loading
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type == SettingsItem.Button || root.type == SettingsItem.PrimaryButton
|
||||
text: root.actionText + (root.actionIcon != "" ? " " : "")
|
||||
loading: root.loading
|
||||
icon.source: root.actionIcon
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
secondary: root.type != SettingsItem.PrimaryButton
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: colorScheme.border_weak
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
71
internal/frontend/qml/SettingsView.qml
Normal file
71
internal/frontend/qml/SettingsView.qml
Normal file
@ -0,0 +1,71 @@
|
||||
// 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.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ScrollView {
|
||||
id: root
|
||||
|
||||
property var colorScheme
|
||||
property var backend
|
||||
default property alias items: content.children
|
||||
|
||||
signal back()
|
||||
|
||||
property int _leftRightMargins: 64
|
||||
property int _topBottomMargins: 68
|
||||
property int _spacing: 22
|
||||
|
||||
clip: true
|
||||
contentWidth: pane.width
|
||||
contentHeight: pane.height
|
||||
|
||||
RowLayout{
|
||||
id: pane
|
||||
width: root.width
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: root._spacing
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: 10
|
||||
leftMargin: 18
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.back()
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
}
|
||||
@ -30,12 +30,14 @@ Item {
|
||||
property var backend
|
||||
|
||||
property var user
|
||||
property string address
|
||||
|
||||
signal dismissed()
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@ -56,7 +58,7 @@ Item {
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: user ? user.username : ""
|
||||
text: address
|
||||
color: root.colorScheme.text_weak
|
||||
type: Label.LabelType.Lead
|
||||
}
|
||||
@ -80,30 +82,50 @@ Item {
|
||||
Repeater {
|
||||
model: clients
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Rectangle {
|
||||
implicitWidth: clientRow.width
|
||||
implicitHeight: clientRow.height
|
||||
|
||||
ColorImage {
|
||||
source: model.iconSource
|
||||
height: 36
|
||||
ColumnLayout {
|
||||
id: clientRow
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ColorImage {
|
||||
source: model.iconSource
|
||||
height: 36
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.leftMargin: 12
|
||||
text: model.name
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.leftMargin: 12
|
||||
text: model.name
|
||||
type: Label.LabelType.Body
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (model.name != "Apple Mail") {
|
||||
console.log(" TODO configure ", model.name)
|
||||
return
|
||||
}
|
||||
root.user.configureAppleMail(root.address)
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,18 +42,9 @@ Item {
|
||||
property var backend
|
||||
property var window
|
||||
|
||||
// in case of adding new account this property should be undefined
|
||||
property var user
|
||||
property alias username: usernameTextField.text
|
||||
state: "Page 1"
|
||||
|
||||
onUserChanged: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
passwordTextField.text = ""
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
}
|
||||
|
||||
onLoginAbort: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
@ -78,15 +69,15 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: user !== undefined ? user : root.backend
|
||||
target: root.backend
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError")
|
||||
console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError")
|
||||
|
||||
stackLayout.loginFailed()
|
||||
errorLabel.text = qsTr("Your email and/or password are incorrect")
|
||||
|
||||
if (errorMsg!="") errorLabel.text = errorMsg
|
||||
else errorLabel.text = qsTr("Your email and/or password are incorrect")
|
||||
}
|
||||
|
||||
onLoginFreeUserError: {
|
||||
@ -152,6 +143,14 @@ Item {
|
||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
onLoginFinished: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
passwordTextField.text = ""
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@ -218,8 +217,6 @@ Item {
|
||||
id: usernameTextField
|
||||
label: qsTr("Username or email")
|
||||
|
||||
text: user !== undefined ? user.username : ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
@ -304,12 +301,7 @@ Item {
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login(usernameTextField.text, passwordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login(usernameTextField.text, passwordTextField.text)
|
||||
root.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,12 +386,7 @@ Item {
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login2FA(usernameTextField.text, twoFactorPasswordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login2FA(usernameTextField.text, twoFactorPasswordTextField.text)
|
||||
root.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -471,12 +458,7 @@ Item {
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login2Password(usernameTextField.text, secondPasswordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login2Password(usernameTextField.text, secondPasswordTextField.text)
|
||||
root.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,20 +22,16 @@ 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 {
|
||||
Window {
|
||||
id: root
|
||||
title: "ProtonMail Bridge"
|
||||
|
||||
height: contentLayout.implicitHeight
|
||||
width: contentLayout.implicitWidth
|
||||
|
||||
minimumHeight: 201
|
||||
minimumWidth: 448
|
||||
flags: Qt.FramelessWindowHint
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
@ -47,15 +43,19 @@ PopupWindow {
|
||||
signal showMainWindow()
|
||||
signal showHelp()
|
||||
signal showSettings()
|
||||
signal showSignIn(string username)
|
||||
signal quit()
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
Layout.minimumHeight: 201
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
Layout.minimumWidth: 448
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
@ -76,13 +76,13 @@ PopupWindow {
|
||||
}
|
||||
|
||||
switch (statusItem.activeNotification.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
}
|
||||
}
|
||||
@ -149,8 +149,8 @@ PopupWindow {
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.maximumHeight: accountListView.count ?
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
clip: true
|
||||
@ -171,13 +171,17 @@ PopupWindow {
|
||||
|
||||
interactive: contentHeight > parent.height
|
||||
snapMode: ListView.SnapToItem
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
delegate: Item {
|
||||
id: viewItem
|
||||
width: ListView.view.width
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var user: root.backend.users.get(index)
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
@ -187,15 +191,19 @@ PopupWindow {
|
||||
|
||||
Layout.margins: 12
|
||||
|
||||
user: modelData
|
||||
user: viewItem.user
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.margins: 12
|
||||
colorScheme: root.colorScheme
|
||||
visible: true
|
||||
text: "test"
|
||||
visible: !viewItem.user.loggedIn
|
||||
text: qsTr("Sign in")
|
||||
onClicked: {
|
||||
root.showSignIn(viewItem.username)
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,4 +305,8 @@ PopupWindow {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) root.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,7 +239,7 @@ Item {
|
||||
root.loginAbort(username)
|
||||
}
|
||||
|
||||
user: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0) : undefined
|
||||
username: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0).username : ""
|
||||
backend: root.backend
|
||||
window: root.window
|
||||
}
|
||||
|
||||
5
internal/frontend/qml/icons/ic-chevron-down.svg
Normal file
5
internal/frontend/qml/icons/ic-chevron-down.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-down">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M2.3 6.30001L8 12L13.7 6.30001L13 5.60001L8 10.58L3 5.60001L2.3 6.30001Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
5
internal/frontend/qml/icons/ic-chevron-up.svg
Normal file
5
internal/frontend/qml/icons/ic-chevron-up.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-up">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M13.7 9.7L7.99999 4L2.29999 9.7L2.99999 10.4L7.99999 5.42L13 10.4L13.7 9.7Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
4
internal/frontend/qml/icons/ic-copy.svg
Normal file
4
internal/frontend/qml/icons/ic-copy.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 2V0H12V10H9V9H11V1H5V2H4Z" fill="#262A33"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 3V13H8V3H0ZM7 4V12H1V4H7Z" fill="#262A33"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 255 B |
3
internal/frontend/qml/icons/ic-external-link.svg
Normal file
3
internal/frontend/qml/icons/ic-external-link.svg
Normal 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="M14 2L13.2427 2L13.2427 1.99998L13.2426 2L13 2L9 2V3H12.2426L5.76613 9.47651L6.47324 10.1836L13 3.65686V7H14V3V2ZM2 2H5V3H3L3 13L13 13V11H14V14H13H3H2V13V3V2Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 327 B |
12
internal/frontend/qml/icons/ic-info.svg
Normal file
12
internal/frontend/qml/icons/ic-info.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<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 d="M32 18C30.3431 18 29 19.3431 29 21C29 22.6569 30.3431 24 32 24C33.6569 24 35 22.6569 35 21C35 19.3431 33.6569 18 32 18Z" fill="white"/>
|
||||
<path d="M30 28C28.8954 28 28 28.8954 28 30C28 31.1046 28.8954 32 30 32V42C28.8954 42 28 42.8954 28 44C28 45.1046 28.8954 46 30 46H34C35.1046 46 36 45.1046 36 44C36 42.8954 35.1046 42 34 42V30C34 28.8954 33.1046 28 32 28H30Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="32" y1="62" x2="14.4192" y2="7.69125" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F6DE6"/>
|
||||
<stop offset="0.483234" stop-color="#63A1FE"/>
|
||||
<stop offset="1" stop-color="#82D2FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 807 B |
5
internal/frontend/qml/icons/ic-trash.svg
Normal file
5
internal/frontend/qml/icons/ic-trash.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 5H6V10H5V5Z" fill="#262A33"/>
|
||||
<path d="M8 5H9V10H8V5Z" fill="#262A33"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2H14V3H12V13H2V3H0V2H4V0H10V2ZM9 1H5V2H9V1ZM11 12H3V3H11V12Z" fill="#262A33"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 319 B |
Reference in New Issue
Block a user