forked from Silverfish/proton-bridge
GODT-1346: GODT-1340 GODT-1315 QML changes
GODT-1365: Create ComboBox component GODT-1338: GODT-1343 Help view buttons GODT-1340: Not crashing, user list updating in main thread. GODT-1345: adding panic handlers
This commit is contained in:
@ -99,10 +99,10 @@ func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the temporary file is deleted.
|
// Make sure the temporary file is deleted.
|
||||||
go (func() {
|
go func() {
|
||||||
<-time.After(10 * time.Minute)
|
<-time.After(10 * time.Minute)
|
||||||
_ = os.RemoveAll(dir)
|
_ = os.RemoveAll(dir)
|
||||||
})()
|
}()
|
||||||
|
|
||||||
// Make sure the file is only readable for the current user.
|
// Make sure the file is only readable for the current user.
|
||||||
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
|
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
|
||||||
|
|||||||
@ -48,9 +48,9 @@ Item {
|
|||||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||||
return root.colorScheme.signal_danger
|
return root.colorScheme.signal_danger
|
||||||
}
|
}
|
||||||
property real usedFraction: root.user.totalBytes ? root.user.usedBytes / root.user.totalBytes : 0
|
property real usedFraction: root.user && root.user.totalBytes ? Math.abs(root.user.usedBytes / root.user.totalBytes) : 0
|
||||||
property string totalSpace: root.spaceWithUnits(root.user.totalBytes)
|
property string totalSpace: root.spaceWithUnits(root.user ? root.user.totalBytes : 0)
|
||||||
property string usedSpace: root.spaceWithUnits(root.user.usedBytes)
|
property string usedSpace: root.spaceWithUnits(root.user ? root.user.usedBytes : 0)
|
||||||
|
|
||||||
function spaceWithUnits(bytes){
|
function spaceWithUnits(bytes){
|
||||||
if (bytes*1 !== bytes ) return "0 kB"
|
if (bytes*1 !== bytes ) return "0 kB"
|
||||||
@ -96,7 +96,7 @@ Item {
|
|||||||
Label {
|
Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
text: root.user.avatarText.toUpperCase()
|
text: root.user ? root.user.avatarText.toUpperCase(): ""
|
||||||
type: {
|
type: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case AccountDelegate.SmallView: return Label.Body
|
case AccountDelegate.SmallView: return Label.Body
|
||||||
@ -128,7 +128,7 @@ Item {
|
|||||||
)
|
)
|
||||||
|
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: user.username
|
text: root.user ? user.username : ""
|
||||||
type: {
|
type: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case AccountDelegate.SmallView: return Label.Body
|
case AccountDelegate.SmallView: return Label.Body
|
||||||
@ -143,7 +143,7 @@ Item {
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
Label {
|
Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: user.loggedIn ? root.usedSpace : qsTr("Signed out")
|
text: root.user && root.user.loggedIn ? root.usedSpace : qsTr("Signed out")
|
||||||
color: root.usedSpaceColor
|
color: root.usedSpaceColor
|
||||||
type: {
|
type: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
@ -155,7 +155,7 @@ Item {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: user.loggedIn ? " / " + root.totalSpace : ""
|
text: root.user && root.user.loggedIn ? " / " + root.totalSpace : ""
|
||||||
color: root.colorScheme.text_weak
|
color: root.colorScheme.text_weak
|
||||||
type: {
|
type: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
@ -168,7 +168,7 @@ Item {
|
|||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: root.type == AccountDelegate.LargeView && user.loggedIn
|
visible: root.user ? root.type == AccountDelegate.LargeView : false
|
||||||
width: 140
|
width: 140
|
||||||
height: 4
|
height: 4
|
||||||
radius: 3
|
radius: 3
|
||||||
@ -177,6 +177,7 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
radius: 3
|
radius: 3
|
||||||
color: root.usedSpaceColor
|
color: root.usedSpaceColor
|
||||||
|
visible: root.user ? parent.visible && root.user.loggedIn : false
|
||||||
anchors {
|
anchors {
|
||||||
top : parent.top
|
top : parent.top
|
||||||
bottom : parent.bottom
|
bottom : parent.bottom
|
||||||
|
|||||||
@ -21,44 +21,51 @@ import QtQuick.Controls 2.12
|
|||||||
|
|
||||||
import Proton 4.0
|
import Proton 4.0
|
||||||
|
|
||||||
ScrollView {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
property var backend
|
property var backend
|
||||||
property var notifications
|
property var notifications
|
||||||
property var user
|
property var user
|
||||||
|
|
||||||
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 showSignIn()
|
||||||
signal showSetupGuide(var user, string address)
|
signal showSetupGuide(var user, string address)
|
||||||
|
|
||||||
ColumnLayout {
|
property int _leftMargin: 64
|
||||||
id: pane
|
property int _rightMargin: 64
|
||||||
|
property int _topMargin: 32
|
||||||
|
property int _detailsTopMargin: 25
|
||||||
|
property int _bottomMargin: 12
|
||||||
|
property int _spacing: 20
|
||||||
|
property int _lineWidth: 1
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
width: root.width
|
width: root.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: topRectangle
|
||||||
|
color: root.colorScheme.background_norm
|
||||||
|
|
||||||
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: root._spacing
|
spacing: root._spacing
|
||||||
Layout.topMargin: root._topBottomMargins
|
|
||||||
Layout.leftMargin: root._leftRightMargins
|
anchors.fill: parent
|
||||||
Layout.rightMargin: root._leftRightMargins
|
anchors.leftMargin: root._leftMargin
|
||||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
anchors.rightMargin: root._rightMargin
|
||||||
|
anchors.topMargin: root._topMargin
|
||||||
|
anchors.bottomMargin: root._bottomMargin
|
||||||
|
|
||||||
|
|
||||||
RowLayout { // account delegate with action buttons
|
RowLayout { // account delegate with action buttons
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -68,7 +75,7 @@ ScrollView {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
user: root.user
|
user: root.user
|
||||||
type: AccountDelegate.LargeView
|
type: AccountDelegate.LargeView
|
||||||
enabled: root.user.loggedIn
|
enabled: root.user ? root.user.loggedIn : false
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@ -76,8 +83,11 @@ ScrollView {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Sign out")
|
text: qsTr("Sign out")
|
||||||
secondary: true
|
secondary: true
|
||||||
visible: root.user.loggedIn
|
visible: root.user ? root.user.loggedIn : false
|
||||||
onClicked: root.user.logout()
|
onClicked: {
|
||||||
|
if (!root.user) return
|
||||||
|
root.user.logout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@ -85,9 +95,11 @@ ScrollView {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Sign in")
|
text: qsTr("Sign in")
|
||||||
secondary: true
|
secondary: true
|
||||||
visible: !root.user.loggedIn
|
visible: root.user ? !root.user.loggedIn : false
|
||||||
enabled: !root.user.loggedIn
|
onClicked: {
|
||||||
onClicked: root.parent.rightContent.showSignIn()
|
if (!root.user) return
|
||||||
|
root.parent.rightContent.showSignIn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@ -95,15 +107,16 @@ ScrollView {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
icon.source: "icons/ic-trash.svg"
|
icon.source: "icons/ic-trash.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
visible: true
|
onClicked: {
|
||||||
enabled: true
|
if (!root.user) return
|
||||||
onClicked: root.user.remove()
|
root.user.remove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 1
|
height: root._lineWidth
|
||||||
color: root.colorScheme.border_weak
|
color: root.colorScheme.border_weak
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +124,17 @@ ScrollView {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Email clients")
|
text: qsTr("Email clients")
|
||||||
actionText: qsTr("Configure")
|
actionText: qsTr("Configure")
|
||||||
description: "MISSING WIREFRAME" // TODO
|
description: qsTr("Proton Mail Bridge works with email clients that support IMAP/SMPT to send and receive messages. Using the mailbox details below, you can (re)configure your client at any point.")
|
||||||
type: SettingsItem.Button
|
type: SettingsItem.Button
|
||||||
enabled: root.user.loggedIn
|
enabled: root.user ? root.user.loggedIn : false
|
||||||
visible: !root.user.splitMode
|
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
|
||||||
onClicked: root.showSetupGuide(root.user,user.addresses[0])
|
showSeparator: splitMode.visible
|
||||||
|
onClicked: {
|
||||||
|
if (!root.user) return
|
||||||
|
root.showSetupGuide(root.user, user.addresses[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -124,9 +143,10 @@ ScrollView {
|
|||||||
text: qsTr("Split addresses")
|
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.")
|
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
|
type: SettingsItem.Toggle
|
||||||
checked: root.user.splitMode
|
checked: root.user ? root.user.splitMode : false
|
||||||
visible: root.user.addresses.length > 1
|
visible: root.user ? root.user.addresses.length > 1 : false
|
||||||
enabled: root.user.loggedIn
|
enabled: root.user ? root.user.loggedIn : false
|
||||||
|
showSeparator: addressSelector.visible
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!splitMode.checked){
|
if (!splitMode.checked){
|
||||||
root.notifications.askEnableSplitMode(user)
|
root.notifications.askEnableSplitMode(user)
|
||||||
@ -134,109 +154,57 @@ ScrollView {
|
|||||||
root.user.toggleSplitMode(!splitMode.checked)
|
root.user.toggleSplitMode(!splitMode.checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
enabled: root.user.loggedIn
|
enabled: root.user ? root.user.loggedIn : false
|
||||||
|
visible: root.user ? root.user.splitMode : false
|
||||||
visible: root.user.splitMode
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: addressSelector
|
id: addressSelector
|
||||||
|
colorScheme: root.colorScheme
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
model: root.user.addresses
|
model: root.user ? root.user.addresses : null
|
||||||
|
|
||||||
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 {
|
Button {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Configure")
|
text: qsTr("Configure")
|
||||||
secondary: true
|
secondary: true
|
||||||
onClicked: root.showSetupGuide(root.user, addressSelector.displayText)
|
onClicked: {
|
||||||
|
if (!root.user) return
|
||||||
|
root.showSetupGuide(root.user, addressSelector.displayText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {implicitHeight: 1}
|
Rectangle {
|
||||||
}
|
color: root.colorScheme.background_weak
|
||||||
|
|
||||||
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: configuration
|
id: configuration
|
||||||
Layout.bottomMargin: root._topBottomMargins
|
|
||||||
Layout.leftMargin: root._leftRightMargins
|
anchors.fill: parent
|
||||||
Layout.rightMargin: root._leftRightMargins
|
anchors.leftMargin: root._leftMargin
|
||||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
anchors.rightMargin: root._rightMargin
|
||||||
|
anchors.topMargin: root._detailsTopMargin
|
||||||
|
anchors.bottomMargin: root._spacing
|
||||||
|
|
||||||
spacing: root._spacing
|
spacing: root._spacing
|
||||||
visible: root.user.loggedIn
|
visible: root.user ? root.user.loggedIn : false
|
||||||
|
|
||||||
property string currentAddress: addressSelector.displayText
|
property string currentAddress: addressSelector.displayText
|
||||||
|
|
||||||
Item {height: 1}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Mailbox details")
|
text: qsTr("Mailbox details")
|
||||||
@ -249,7 +217,7 @@ ScrollView {
|
|||||||
hostname: root.backend.hostname
|
hostname: root.backend.hostname
|
||||||
port: root.backend.portIMAP.toString()
|
port: root.backend.portIMAP.toString()
|
||||||
username: configuration.currentAddress
|
username: configuration.currentAddress
|
||||||
password: root.user.password
|
password: root.user ? root.user.password : ""
|
||||||
security: "STARTTLS"
|
security: "STARTTLS"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,9 +227,11 @@ ScrollView {
|
|||||||
hostname : root.backend.hostname
|
hostname : root.backend.hostname
|
||||||
port : root.backend.portSMTP.toString()
|
port : root.backend.portSMTP.toString()
|
||||||
username : configuration.currentAddress
|
username : configuration.currentAddress
|
||||||
password : root.user.password
|
password : root.user ? root.user.password : ""
|
||||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,19 +42,6 @@ QtObject {
|
|||||||
backend: root.backend
|
backend: root.backend
|
||||||
notifications: root._notifications
|
notifications: root._notifications
|
||||||
|
|
||||||
onLogin: {
|
|
||||||
backend.login(username, password)
|
|
||||||
}
|
|
||||||
onLogin2FA: {
|
|
||||||
backend.login2FA(username, code)
|
|
||||||
}
|
|
||||||
onLogin2Password: {
|
|
||||||
backend.login2Password(username, password)
|
|
||||||
}
|
|
||||||
onLoginAbort: {
|
|
||||||
backend.loginAbort(username)
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
backend.dockIconVisible = visible
|
backend.dockIconVisible = visible
|
||||||
}
|
}
|
||||||
@ -167,12 +154,10 @@ QtObject {
|
|||||||
break;
|
break;
|
||||||
case SystemTrayIcon.Context:
|
case SystemTrayIcon.Context:
|
||||||
case SystemTrayIcon.Trigger:
|
case SystemTrayIcon.Trigger:
|
||||||
calcStatusWindowPosition()
|
|
||||||
toggleWindow(statusWindow)
|
|
||||||
break
|
|
||||||
case SystemTrayIcon.DoubleClick:
|
case SystemTrayIcon.DoubleClick:
|
||||||
case SystemTrayIcon.MiddleClick:
|
case SystemTrayIcon.MiddleClick:
|
||||||
toggleWindow(mainWindow)
|
calcStatusWindowPosition()
|
||||||
|
toggleWindow(statusWindow)
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -181,13 +166,31 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (root.backend.users.count === 0) {
|
if (!root.backend) {
|
||||||
|
console.log("backend not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.backend.users) {
|
||||||
|
console.log("users not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = root.backend.users.count
|
||||||
|
var u = root.backend.users.get(0)
|
||||||
|
// DEBUG
|
||||||
|
if (c != 0) {
|
||||||
|
console.log("users non zero", c)
|
||||||
|
console.log("first user", u )
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c === 0) {
|
||||||
mainWindow.showAndRise()
|
mainWindow.showAndRise()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) {
|
if (u) {
|
||||||
|
if (c === 1 && u.loggedIn === false) {
|
||||||
mainWindow.showAndRise()
|
mainWindow.showAndRise()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (root.backend.showOnStartup) {
|
if (root.backend.showOnStartup) {
|
||||||
mainWindow.showAndRise()
|
mainWindow.showAndRise()
|
||||||
|
|||||||
@ -116,10 +116,10 @@ ColumnLayout {
|
|||||||
Button {
|
Button {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "name/pass error"
|
text: "name/pass error"
|
||||||
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.loginUsernamePasswordError()
|
root.backend.loginUsernamePasswordError("")
|
||||||
user.resetLoginRequests()
|
user.resetLoginRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,9 +127,9 @@ ColumnLayout {
|
|||||||
Button {
|
Button {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "free user error"
|
text: "free user error"
|
||||||
enabled: user !== undefined && user.isLoginRequested
|
enabled: user !== undefined //&& user.isLoginRequested
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.loginFreeUserError()
|
root.backend.loginFreeUserError("")
|
||||||
user.resetLoginRequests()
|
user.resetLoginRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,9 +137,9 @@ ColumnLayout {
|
|||||||
Button {
|
Button {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "connection error"
|
text: "connection error"
|
||||||
enabled: user !== undefined && user.isLoginRequested
|
enabled: user !== undefined //&& user.isLoginRequested
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.loginConnectionError()
|
root.backend.loginConnectionError("")
|
||||||
user.resetLoginRequests()
|
user.resetLoginRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,9 +160,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "request"
|
text: "request"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2FARequested()
|
root.backend.login2FARequested()
|
||||||
user.isLogin2FARequested = true
|
user.isLogin2FARequested = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,9 +171,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "error"
|
text: "error"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2FAError()
|
root.backend.login2FAError("")
|
||||||
user.isLogin2FAProvided = false
|
user.isLogin2FAProvided = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,9 +182,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "Abort"
|
text: "Abort"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2FAErrorAbort()
|
root.backend.login2FAErrorAbort("")
|
||||||
user.resetLoginRequests()
|
user.resetLoginRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,9 +205,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "request"
|
text: "request"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2PasswordRequested()
|
root.backend.login2PasswordRequested("")
|
||||||
user.isLogin2PasswordRequested = true
|
user.isLogin2PasswordRequested = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,9 +216,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "error"
|
text: "error"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2PasswordError()
|
root.backend.login2PasswordError("")
|
||||||
|
|
||||||
user.isLogin2PasswordProvided = false
|
user.isLogin2PasswordProvided = false
|
||||||
}
|
}
|
||||||
@ -228,9 +228,9 @@ ColumnLayout {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: "Abort"
|
text: "Abort"
|
||||||
|
|
||||||
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
user.login2PasswordErrorAbort()
|
root.backend.login2PasswordErrorAbort("")
|
||||||
user.resetLoginRequests()
|
user.resetLoginRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import QtQuick.Controls.impl 2.12
|
|||||||
|
|
||||||
import Proton 4.0
|
import Proton 4.0
|
||||||
|
|
||||||
ColumnLayout {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
@ -30,6 +30,12 @@ ColumnLayout {
|
|||||||
property string label
|
property string label
|
||||||
property string value
|
property string value
|
||||||
|
|
||||||
|
implicitHeight: children[0].implicitHeight
|
||||||
|
implicitWidth: children[0].implicitWidth
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: root.width
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
@ -74,10 +80,10 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 1
|
height: 1
|
||||||
color: root.colorScheme.border_norm
|
color: root.colorScheme.border_norm
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,24 +28,8 @@ Item {
|
|||||||
property var backend
|
property var backend
|
||||||
property var notifications
|
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)
|
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 {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@ -183,6 +167,7 @@ Item {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
var user = root.backend.users.get(index)
|
var user = root.backend.users.get(index)
|
||||||
accounts.currentIndex = index
|
accounts.currentIndex = index
|
||||||
|
if (!user) return
|
||||||
if (user.loggedIn) {
|
if (user.loggedIn) {
|
||||||
rightContent.showAccount()
|
rightContent.showAccount()
|
||||||
} else {
|
} else {
|
||||||
@ -248,8 +233,8 @@ Item {
|
|||||||
backend: root.backend
|
backend: root.backend
|
||||||
notifications: root.notifications
|
notifications: root.notifications
|
||||||
user: {
|
user: {
|
||||||
if (accounts.currentIndex < 0) return root.noUser
|
if (accounts.currentIndex < 0) return undefined
|
||||||
if (root.backend.users.count == 0) return root.noUser
|
if (root.backend.users.count == 0) return undefined
|
||||||
return root.backend.users.get(accounts.currentIndex)
|
return root.backend.users.get(accounts.currentIndex)
|
||||||
}
|
}
|
||||||
onShowSignIn: {
|
onShowSignIn: {
|
||||||
@ -261,7 +246,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridLayout { // 1
|
GridLayout { // 1 Sign In
|
||||||
columns: 2
|
columns: 2
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@ -271,7 +256,10 @@ Item {
|
|||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
onClicked: rightContent.showAccount()
|
onClicked: {
|
||||||
|
signIn.abort()
|
||||||
|
rightContent.showAccount()
|
||||||
|
}
|
||||||
icon.source: "icons/ic-arrow-left.svg"
|
icon.source: "icons/ic-arrow-left.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
horizontalPadding: 8
|
horizontalPadding: 8
|
||||||
@ -289,11 +277,6 @@ Item {
|
|||||||
|
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
backend: root.backend
|
backend: root.backend
|
||||||
|
|
||||||
onLogin : { root.backend.login ( username , password ) }
|
|
||||||
onLogin2FA : { root.backend.login2FA ( username , code ) }
|
|
||||||
onLogin2Password : { root.backend.login2Password ( username , password ) }
|
|
||||||
onLoginAbort : { root.backend.loginAbort ( username ) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +313,9 @@ Item {
|
|||||||
selectedAddress: {
|
selectedAddress: {
|
||||||
if (accounts.currentIndex < 0) return ""
|
if (accounts.currentIndex < 0) return ""
|
||||||
if (root.backend.users.count == 0) return ""
|
if (root.backend.users.count == 0) return ""
|
||||||
return root.backend.users.get(accounts.currentIndex).addresses[0]
|
var user = root.backend.users.get(accounts.currentIndex)
|
||||||
|
if (!user) return ""
|
||||||
|
return user.addresses[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,6 +327,12 @@ Item {
|
|||||||
function showLocalCacheSettings () { rightContent.currentIndex = 5 }
|
function showLocalCacheSettings () { rightContent.currentIndex = 5 }
|
||||||
function showHelpView () { rightContent.currentIndex = 6 }
|
function showHelpView () { rightContent.currentIndex = 6 }
|
||||||
function showBugReport () { rightContent.currentIndex = 7 }
|
function showBugReport () { rightContent.currentIndex = 7 }
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
|
||||||
|
onLoginFinished: rightContent.showAccount()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,5 +344,4 @@ Item {
|
|||||||
signIn.username = username
|
signIn.username = username
|
||||||
rightContent.showSignIn()
|
rightContent.showSignIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,8 @@ SettingsView {
|
|||||||
type: SettingsItem.Toggle
|
type: SettingsItem.Toggle
|
||||||
checked: root.backend.isAutomaticUpdateOn
|
checked: root.backend.isAutomaticUpdateOn
|
||||||
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -62,6 +64,8 @@ SettingsView {
|
|||||||
autostart.loading = false
|
autostart.loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -78,6 +82,8 @@ SettingsView {
|
|||||||
root.notifications.askDisableBeta()
|
root.notifications.askDisableBeta()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@ -117,6 +123,8 @@ SettingsView {
|
|||||||
type: SettingsItem.Toggle
|
type: SettingsItem.Toggle
|
||||||
checked: root.backend.isDoHEnabled
|
checked: root.backend.isDoHEnabled
|
||||||
onClicked: root.backend.toggleDoH(!doh.checked)
|
onClicked: root.backend.toggleDoH(!doh.checked)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -128,6 +136,8 @@ SettingsView {
|
|||||||
description: qsTr("Choose which ports are used by default.")
|
description: qsTr("Choose which ports are used by default.")
|
||||||
type: SettingsItem.Button
|
type: SettingsItem.Button
|
||||||
onClicked: root.parent.showPortSettings()
|
onClicked: root.parent.showPortSettings()
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -139,6 +149,8 @@ SettingsView {
|
|||||||
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
||||||
type: SettingsItem.Button
|
type: SettingsItem.Button
|
||||||
onClicked: root.parent.showSMTPSettings()
|
onClicked: root.parent.showSMTPSettings()
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -150,6 +162,8 @@ SettingsView {
|
|||||||
description: qsTr("Configure Bridge's local cache settings.")
|
description: qsTr("Configure Bridge's local cache settings.")
|
||||||
type: SettingsItem.Button
|
type: SettingsItem.Button
|
||||||
onClicked: root.parent.showLocalCacheSettings()
|
onClicked: root.parent.showLocalCacheSettings()
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -163,6 +177,8 @@ SettingsView {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
root.notifications.askResetBridge()
|
root.notifications.askResetBridge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
onBack: root.parent.showAccount()
|
onBack: root.parent.showAccount()
|
||||||
|
|||||||
@ -39,7 +39,9 @@ SettingsView {
|
|||||||
actionIcon: "./icons/ic-external-link.svg"
|
actionIcon: "./icons/ic-external-link.svg"
|
||||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||||
type: SettingsItem.PrimaryButton
|
type: SettingsItem.PrimaryButton
|
||||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/bridge/install")}
|
onClicked: {Qt.openUrlExternally("https://protonmail.com/support/categories/bridge/")}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -55,6 +57,8 @@ SettingsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -64,7 +68,9 @@ SettingsView {
|
|||||||
actionText: qsTr("View logs")
|
actionText: qsTr("View logs")
|
||||||
description: qsTr("Open and review logs to troubleshoot.")
|
description: qsTr("Open and review logs to troubleshoot.")
|
||||||
type: SettingsItem.Button
|
type: SettingsItem.Button
|
||||||
onClicked: {Qt.openUrlExternally(root.backend.logsPath)}
|
onClicked: {Qt.openUrlExternally("file://"+root.backend.logsPath)}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -78,6 +84,8 @@ SettingsView {
|
|||||||
root.backend.updateCurrentMailClient()
|
root.backend.updateCurrentMailClient()
|
||||||
root.parent.showBugReport()
|
root.parent.showBugReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@ -91,7 +99,7 @@ SettingsView {
|
|||||||
text: {
|
text: {
|
||||||
var version = root.backend.version
|
var version = root.backend.version
|
||||||
var license = qsTr("License")
|
var license = qsTr("License")
|
||||||
var licensePath = root.backend.licensePath
|
var licensePath = "file://"+root.backend.licensePath
|
||||||
var release= qsTr("Release notes")
|
var release= qsTr("Release notes")
|
||||||
var releaseNotesLink = root.backend.releaseNotesLink
|
var releaseNotesLink = root.backend.releaseNotesLink
|
||||||
return `<p style="text-align:center;">Proton Mail Bridge v${version}<br>
|
return `<p style="text-align:center;">Proton Mail Bridge v${version}<br>
|
||||||
|
|||||||
@ -54,6 +54,8 @@ SettingsView {
|
|||||||
type: SettingsItem.Toggle
|
type: SettingsItem.Toggle
|
||||||
checked: root._diskCacheEnabled
|
checked: root._diskCacheEnabled
|
||||||
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
@ -67,6 +69,8 @@ SettingsView {
|
|||||||
pathDialog.open()
|
pathDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
FileDialog {
|
FileDialog {
|
||||||
id: pathDialog
|
id: pathDialog
|
||||||
title: qsTr("Select cache location")
|
title: qsTr("Select cache location")
|
||||||
|
|||||||
@ -41,11 +41,6 @@ ApplicationWindow {
|
|||||||
property var backend
|
property var backend
|
||||||
property var notifications
|
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)
|
|
||||||
|
|
||||||
// show Setup Guide on every new user
|
// show Setup Guide on every new user
|
||||||
Connections {
|
Connections {
|
||||||
target: root.backend.users
|
target: root.backend.users
|
||||||
@ -98,7 +93,15 @@ ApplicationWindow {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backend.users.count === 1 && backend.users.get(0).loggedIn === false) {
|
var u = backend.users.get(0)
|
||||||
|
|
||||||
|
if (!u) {
|
||||||
|
console.trace()
|
||||||
|
console.log("empty user")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend.users.count === 1 && u.loggedIn === false) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,19 +124,6 @@ ApplicationWindow {
|
|||||||
onShowSetupGuide: {
|
onShowSetupGuide: {
|
||||||
root.showSetup(user,address)
|
root.showSetup(user,address)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLogin: {
|
|
||||||
root.login(username, password)
|
|
||||||
}
|
|
||||||
onLogin2FA: {
|
|
||||||
root.login2FA(username, code)
|
|
||||||
}
|
|
||||||
onLogin2Password: {
|
|
||||||
root.login2Password(username, password)
|
|
||||||
}
|
|
||||||
onLoginAbort: {
|
|
||||||
root.loginAbort(username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WelcomeGuide {
|
WelcomeGuide {
|
||||||
@ -142,19 +132,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
onLogin: {
|
|
||||||
root.login(username, password)
|
|
||||||
}
|
|
||||||
onLogin2FA: {
|
|
||||||
root.login2FA(username, code)
|
|
||||||
}
|
|
||||||
onLogin2Password: {
|
|
||||||
root.login2Password(username, password)
|
|
||||||
}
|
|
||||||
onLoginAbort: {
|
|
||||||
root.loginAbort(username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupGuide {
|
SetupGuide {
|
||||||
|
|||||||
@ -56,6 +56,8 @@ QtObject {
|
|||||||
root.updateSilentRestartNeeded,
|
root.updateSilentRestartNeeded,
|
||||||
root.updateSilentError,
|
root.updateSilentError,
|
||||||
root.updateIsLatestVersion,
|
root.updateIsLatestVersion,
|
||||||
|
root.loginConnectionError,
|
||||||
|
root.onlyPaidUsers,
|
||||||
root.disableBeta,
|
root.disableBeta,
|
||||||
root.enableBeta,
|
root.enableBeta,
|
||||||
root.bugReportSendSuccess,
|
root.bugReportSendSuccess,
|
||||||
@ -119,7 +121,7 @@ QtObject {
|
|||||||
text: qsTr("Update manually")
|
text: qsTr("Update manually")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateManualReady.active = false
|
root.updateManualReady.active = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -174,7 +176,7 @@ QtObject {
|
|||||||
text: qsTr("Update manually")
|
text: qsTr("Update manually")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateManualError.active = false
|
root.updateManualError.active = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -217,7 +219,7 @@ QtObject {
|
|||||||
text: qsTr("Update manually")
|
text: qsTr("Update manually")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateForce.active = false
|
root.updateForce.active = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -252,7 +254,7 @@ QtObject {
|
|||||||
text: qsTr("Update manually")
|
text: qsTr("Update manually")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateForceError.active = false
|
root.updateForceError.active = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -307,7 +309,7 @@ QtObject {
|
|||||||
text: qsTr("Update manually")
|
text: qsTr("Update manually")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateSilentError.active = false
|
root.updateSilentError.active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,6 +401,52 @@ QtObject {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// login
|
||||||
|
property Notification loginConnectionError: Notification {
|
||||||
|
text: qsTr("Bridge is not able to contact the server, please check your internet connection.")
|
||||||
|
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||||
|
type: Notification.NotificationType.Danger
|
||||||
|
group: Notifications.Group.Configuration
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
onLoginConnectionError: {
|
||||||
|
root.loginConnectionError.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: [
|
||||||
|
Action {
|
||||||
|
text: qsTr("OK")
|
||||||
|
onTriggered: {
|
||||||
|
root.loginConnectionError.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
property Notification onlyPaidUsers: Notification {
|
||||||
|
text: qsTr("Bridge is exclusive to our paid plans. Upgrade your account to use Bridge.")
|
||||||
|
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||||
|
type: Notification.NotificationType.Danger
|
||||||
|
group: Notifications.Group.Configuration
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
onLoginFreeUserError: {
|
||||||
|
root.onlyPaidUsers.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: [
|
||||||
|
Action {
|
||||||
|
text: qsTr("OK")
|
||||||
|
onTriggered: {
|
||||||
|
root.onlyPaidUsers.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// Bug reports
|
// Bug reports
|
||||||
property Notification bugReportSendSuccess: Notification {
|
property Notification bugReportSendSuccess: Notification {
|
||||||
@ -420,9 +468,6 @@ QtObject {
|
|||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.bugReportSendSuccess.active = false
|
root.bugReportSendSuccess.active = false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Action {
|
|
||||||
text: "test"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,6 @@ import QtQuick.Controls 2.12
|
|||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
import QtQuick.Templates 2.12 as T
|
import QtQuick.Templates 2.12 as T
|
||||||
|
|
||||||
import "."
|
|
||||||
|
|
||||||
T.ApplicationWindow {
|
T.ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import QtQuick 2.12
|
|||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
import QtQuick.Templates 2.12 as T
|
import QtQuick.Templates 2.12 as T
|
||||||
import "."
|
|
||||||
|
import "." as Proton
|
||||||
|
|
||||||
T.Button {
|
T.Button {
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
@ -32,7 +33,7 @@ T.Button {
|
|||||||
|
|
||||||
property bool borderless: false
|
property bool borderless: false
|
||||||
|
|
||||||
property int labelType: Label.LabelType.Body
|
property int labelType: Proton.Label.LabelType.Body
|
||||||
|
|
||||||
// TODO: store previous enabled state and restore it?
|
// TODO: store previous enabled state and restore it?
|
||||||
// For now assuming that only enabled buttons could have loading state
|
// For now assuming that only enabled buttons could have loading state
|
||||||
@ -104,7 +105,7 @@ T.Button {
|
|||||||
return control.display === AbstractButton.TextUnderIcon ? textImplicitHeight + iconImplicitHeight + spacing : Math.max(textImplicitHeight, iconImplicitHeight)
|
return control.display === AbstractButton.TextUnderIcon ? textImplicitHeight + iconImplicitHeight + spacing : Math.max(textImplicitHeight, iconImplicitHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: label
|
id: label
|
||||||
anchors.left: labelIcon.left
|
anchors.left: labelIcon.left
|
||||||
|
|||||||
184
internal/frontend/qml/Proton/ComboBox.qml
Normal file
184
internal/frontend/qml/Proton/ComboBox.qml
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Window 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Controls.impl 2.12
|
||||||
|
import QtQuick.Templates 2.12 as T
|
||||||
|
|
||||||
|
T.ComboBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
|
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||||
|
implicitContentWidth + leftPadding + rightPadding)
|
||||||
|
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||||
|
implicitContentHeight + topPadding + bottomPadding,
|
||||||
|
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||||
|
|
||||||
|
leftPadding: 12 + (!root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing)
|
||||||
|
rightPadding: 12 + (root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing)
|
||||||
|
|
||||||
|
topPadding: 5
|
||||||
|
bottomPadding: 5
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
font.family: Style.font_family
|
||||||
|
font.weight: Style.fontWeight_400
|
||||||
|
font.pixelSize: Style.body_font_size
|
||||||
|
font.letterSpacing: Style.body_letter_spacing
|
||||||
|
|
||||||
|
contentItem: T.TextField {
|
||||||
|
padding: 5
|
||||||
|
|
||||||
|
text: root.editable ? root.editText : root.displayText
|
||||||
|
font: root.font
|
||||||
|
|
||||||
|
enabled: root.editable
|
||||||
|
autoScroll: root.editable
|
||||||
|
readOnly: root.down
|
||||||
|
inputMethodHints: root.inputMethodHints
|
||||||
|
validator: root.validator
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
|
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||||
|
selectionColor: root.colorScheme.interaction_norm
|
||||||
|
selectedTextColor: root.colorScheme.text_invert
|
||||||
|
placeholderTextColor: root.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
visible: root.enabled && root.editable && !root.flat
|
||||||
|
border.color: {
|
||||||
|
if (root.activeFocus) {
|
||||||
|
return root.colorScheme.interaction_norm
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.hovered) {
|
||||||
|
return root.colorScheme.field_hover
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.colorScheme.field_norm
|
||||||
|
}
|
||||||
|
border.width: 1
|
||||||
|
color: root.colorScheme.background_norm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: 140
|
||||||
|
implicitHeight: 36
|
||||||
|
radius: 4
|
||||||
|
color: {
|
||||||
|
if (root.down) {
|
||||||
|
return root.colorScheme.interaction_default_active
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.enabled && root.hovered) {
|
||||||
|
return root.colorScheme.interaction_default_hover
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.enabled) {
|
||||||
|
return root.colorScheme.interaction_default
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.colorScheme.background_norm
|
||||||
|
}
|
||||||
|
|
||||||
|
border.color: root.colorScheme.border_norm
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
indicator: ColorImage {
|
||||||
|
x: root.mirrored ? 12 : root.width - width - 12
|
||||||
|
y: root.topPadding + (root.availableHeight - height) / 2
|
||||||
|
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||||
|
source: popup.visible ? "../icons/ic-chevron-up.svg" : "../icons/ic-chevron-down.svg"
|
||||||
|
|
||||||
|
sourceSize.width: 16
|
||||||
|
sourceSize.height: 16
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: parent.width
|
||||||
|
text: root.textRole ? (Array.isArray(root.model) ? modelData[root.textRole] : model[root.textRole]) : modelData
|
||||||
|
|
||||||
|
palette.text: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||||
|
font: root.font
|
||||||
|
|
||||||
|
hoverEnabled: root.hoverEnabled
|
||||||
|
|
||||||
|
// we use highlighted to indicate currently selected delegate
|
||||||
|
highlighted: root.currentIndex === index
|
||||||
|
palette.highlightedText: root.enabled ? root.colorScheme.text_invert : root.colorScheme.text_disabled
|
||||||
|
|
||||||
|
background: PaddedRectangle {
|
||||||
|
radius: 4
|
||||||
|
color: {
|
||||||
|
if (parent.down) {
|
||||||
|
return root.colorScheme.interaction_default_active
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.highlighted) {
|
||||||
|
return root.colorScheme.interaction_norm
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.hovered) {
|
||||||
|
return root.colorScheme.interaction_default_hover
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.colorScheme.interaction_default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popup: T.Popup {
|
||||||
|
y: root.height
|
||||||
|
width: root.width
|
||||||
|
height: Math.min(contentItem.implicitHeight, root.Window.height - topMargin - bottomMargin)
|
||||||
|
topMargin: 8
|
||||||
|
bottomMargin: 8
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
|
||||||
|
implicitHeight: contentHeight
|
||||||
|
model: root.delegateModel
|
||||||
|
currentIndex: root.highlightedIndex
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
T.ScrollIndicator.vertical: ScrollIndicator { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: root.colorScheme.background_norm
|
||||||
|
radius: 10
|
||||||
|
border.color: root.colorScheme.border_weak
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,8 +21,6 @@ import QtQuick.Templates 2.12 as T
|
|||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
|
|
||||||
import "."
|
|
||||||
|
|
||||||
T.Dialog {
|
T.Dialog {
|
||||||
id: root
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import QtQuick 2.13
|
|||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
import QtQuick.Templates 2.12 as T
|
import QtQuick.Templates 2.12 as T
|
||||||
import "."
|
|
||||||
|
import "." as Proton
|
||||||
|
|
||||||
T.Label {
|
T.Label {
|
||||||
id: root
|
id: root
|
||||||
@ -46,7 +47,7 @@ T.Label {
|
|||||||
// weight 700, size 12, height 16, spacing 0.4
|
// weight 700, size 12, height 16, spacing 0.4
|
||||||
Caption_bold
|
Caption_bold
|
||||||
}
|
}
|
||||||
property int type: Label.LabelType.Body
|
property int type: Proton.Label.LabelType.Body
|
||||||
|
|
||||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||||
palette.link: root.colorScheme.interaction_norm
|
palette.link: root.colorScheme.interaction_norm
|
||||||
@ -56,78 +57,78 @@ T.Label {
|
|||||||
|
|
||||||
font.weight: {
|
font.weight: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case Label.LabelType.Heading:
|
case Proton.Label.LabelType.Heading:
|
||||||
return Style.fontWeight_700
|
return Style.fontWeight_700
|
||||||
case Label.LabelType.Title:
|
case Proton.Label.LabelType.Title:
|
||||||
return Style.fontWeight_700
|
return Style.fontWeight_700
|
||||||
case Label.LabelType.Lead:
|
case Proton.Label.LabelType.Lead:
|
||||||
return Style.fontWeight_400
|
return Style.fontWeight_400
|
||||||
case Label.LabelType.Body:
|
case Proton.Label.LabelType.Body:
|
||||||
return Style.fontWeight_400
|
return Style.fontWeight_400
|
||||||
case Label.LabelType.Body_semibold:
|
case Proton.Label.LabelType.Body_semibold:
|
||||||
return Style.fontWeight_600
|
return Style.fontWeight_600
|
||||||
case Label.LabelType.Body_bold:
|
case Proton.Label.LabelType.Body_bold:
|
||||||
return Style.fontWeight_700
|
return Style.fontWeight_700
|
||||||
case Label.LabelType.Caption:
|
case Proton.Label.LabelType.Caption:
|
||||||
return Style.fontWeight_400
|
return Style.fontWeight_400
|
||||||
case Label.LabelType.Caption_semibold:
|
case Proton.Label.LabelType.Caption_semibold:
|
||||||
return Style.fontWeight_600
|
return Style.fontWeight_600
|
||||||
case Label.LabelType.Caption_bold:
|
case Proton.Label.LabelType.Caption_bold:
|
||||||
return Style.fontWeight_700
|
return Style.fontWeight_700
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font.pixelSize: {
|
font.pixelSize: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case Label.LabelType.Heading:
|
case Proton.Label.LabelType.Heading:
|
||||||
return Style.heading_font_size
|
return Style.heading_font_size
|
||||||
case Label.LabelType.Title:
|
case Proton.Label.LabelType.Title:
|
||||||
return Style.title_font_size
|
return Style.title_font_size
|
||||||
case Label.LabelType.Lead:
|
case Proton.Label.LabelType.Lead:
|
||||||
return Style.lead_font_size
|
return Style.lead_font_size
|
||||||
case Label.LabelType.Body:
|
case Proton.Label.LabelType.Body:
|
||||||
case Label.LabelType.Body_semibold:
|
case Proton.Label.LabelType.Body_semibold:
|
||||||
case Label.LabelType.Body_bold:
|
case Proton.Label.LabelType.Body_bold:
|
||||||
return Style.body_font_size
|
return Style.body_font_size
|
||||||
case Label.LabelType.Caption:
|
case Proton.Label.LabelType.Caption:
|
||||||
case Label.LabelType.Caption_semibold:
|
case Proton.Label.LabelType.Caption_semibold:
|
||||||
case Label.LabelType.Caption_bold:
|
case Proton.Label.LabelType.Caption_bold:
|
||||||
return Style.caption_font_size
|
return Style.caption_font_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lineHeight: {
|
lineHeight: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case Label.LabelType.Heading:
|
case Proton.Label.LabelType.Heading:
|
||||||
return Style.heading_line_height
|
return Style.heading_line_height
|
||||||
case Label.LabelType.Title:
|
case Proton.Label.LabelType.Title:
|
||||||
return Style.title_line_height
|
return Style.title_line_height
|
||||||
case Label.LabelType.Lead:
|
case Proton.Label.LabelType.Lead:
|
||||||
return Style.lead_line_height
|
return Style.lead_line_height
|
||||||
case Label.LabelType.Body:
|
case Proton.Label.LabelType.Body:
|
||||||
case Label.LabelType.Body_semibold:
|
case Proton.Label.LabelType.Body_semibold:
|
||||||
case Label.LabelType.Body_bold:
|
case Proton.Label.LabelType.Body_bold:
|
||||||
return Style.body_line_height
|
return Style.body_line_height
|
||||||
case Label.LabelType.Caption:
|
case Proton.Label.LabelType.Caption:
|
||||||
case Label.LabelType.Caption_semibold:
|
case Proton.Label.LabelType.Caption_semibold:
|
||||||
case Label.LabelType.Caption_bold:
|
case Proton.Label.LabelType.Caption_bold:
|
||||||
return Style.caption_line_height
|
return Style.caption_line_height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font.letterSpacing: {
|
font.letterSpacing: {
|
||||||
switch (root.type) {
|
switch (root.type) {
|
||||||
case Label.LabelType.Heading:
|
case Proton.Label.LabelType.Heading:
|
||||||
case Label.LabelType.Title:
|
case Proton.Label.LabelType.Title:
|
||||||
case Label.LabelType.Lead:
|
case Proton.Label.LabelType.Lead:
|
||||||
return 0
|
return 0
|
||||||
case Label.LabelType.Body:
|
case Proton.Label.LabelType.Body:
|
||||||
case Label.LabelType.Body_semibold:
|
case Proton.Label.LabelType.Body_semibold:
|
||||||
case Label.LabelType.Body_bold:
|
case Proton.Label.LabelType.Body_bold:
|
||||||
return Style.body_letter_spacing
|
return Style.body_letter_spacing
|
||||||
case Label.LabelType.Caption:
|
case Proton.Label.LabelType.Caption:
|
||||||
case Label.LabelType.Caption_semibold:
|
case Proton.Label.LabelType.Caption_semibold:
|
||||||
case Label.LabelType.Caption_bold:
|
case Proton.Label.LabelType.Caption_bold:
|
||||||
return Style.caption_letter_spacing
|
return Style.caption_letter_spacing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,8 @@ import QtQuick 2.12
|
|||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
import QtQuick.Templates 2.12 as T
|
import QtQuick.Templates 2.12 as T
|
||||||
import "."
|
|
||||||
|
import "." as Proton
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@ -131,7 +132,7 @@ Item {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
@ -141,10 +142,10 @@ Item {
|
|||||||
|
|
||||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||||
|
|
||||||
type: Label.LabelType.Body_semibold
|
type: Proton.Label.LabelType.Body_semibold
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: hint
|
id: hint
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ Item {
|
|||||||
|
|
||||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||||
|
|
||||||
type: Label.LabelType.Caption
|
type: Proton.Label.LabelType.Caption
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorImage {
|
ColorImage {
|
||||||
@ -168,7 +169,7 @@ Item {
|
|||||||
color: root.colorScheme.signal_danger
|
color: root.colorScheme.signal_danger
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: assistiveText
|
id: assistiveText
|
||||||
|
|
||||||
@ -189,7 +190,7 @@ Item {
|
|||||||
return root.colorScheme.text_weak
|
return root.colorScheme.text_weak
|
||||||
}
|
}
|
||||||
|
|
||||||
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
|
type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|||||||
@ -21,7 +21,8 @@ import QtQuick.Controls 2.12
|
|||||||
import QtQuick.Controls.impl 2.12
|
import QtQuick.Controls.impl 2.12
|
||||||
import QtQuick.Templates 2.12 as T
|
import QtQuick.Templates 2.12 as T
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import "."
|
|
||||||
|
import "." as Proton
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@ -128,22 +129,22 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: label
|
id: label
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
type: Label.LabelType.Body_semibold
|
type: Proton.Label.LabelType.Body_semibold
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: hint
|
id: hint
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
type: Label.LabelType.Caption
|
type: Proton.Label.LabelType.Caption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +271,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Proton.Button {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: eyeButton
|
id: eyeButton
|
||||||
|
|
||||||
@ -299,7 +300,7 @@ Item {
|
|||||||
sourceSize.height: assistiveText.height
|
sourceSize.height: assistiveText.height
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Proton.Label {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
id: assistiveText
|
id: assistiveText
|
||||||
|
|
||||||
@ -319,7 +320,7 @@ Item {
|
|||||||
return root.colorScheme.text_weak
|
return root.colorScheme.text_weak
|
||||||
}
|
}
|
||||||
|
|
||||||
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
|
type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,16 +20,20 @@ import QtQuick.Layouts 1.12
|
|||||||
import QtQuick.Controls 2.13
|
import QtQuick.Controls 2.13
|
||||||
import QtQuick.Controls.impl 2.13
|
import QtQuick.Controls.impl 2.13
|
||||||
|
|
||||||
RowLayout{
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property var colorScheme
|
property var colorScheme
|
||||||
property bool checked
|
property bool checked
|
||||||
property bool disabled
|
|
||||||
property bool hovered
|
property bool hovered
|
||||||
property bool loading
|
property bool loading
|
||||||
|
|
||||||
signal clicked
|
signal clicked
|
||||||
|
|
||||||
|
property bool _disabled: !enabled
|
||||||
|
|
||||||
|
implicitHeight: children[0].implicitHeight
|
||||||
|
implicitWidth: children[0].implicitWidth
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: indicator
|
id: indicator
|
||||||
implicitWidth: 40
|
implicitWidth: 40
|
||||||
@ -38,12 +42,12 @@ RowLayout{
|
|||||||
radius: 20
|
radius: 20
|
||||||
color: {
|
color: {
|
||||||
if (root.loading) return "transparent"
|
if (root.loading) return "transparent"
|
||||||
if (root.disabled) return root.colorScheme.background_strong
|
if (root._disabled) return root.colorScheme.background_strong
|
||||||
return root.colorScheme.background_norm
|
return root.colorScheme.background_norm
|
||||||
}
|
}
|
||||||
border {
|
border {
|
||||||
width: 1
|
width: 1
|
||||||
color: (root.disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@ -55,7 +59,7 @@ RowLayout{
|
|||||||
radius: 12
|
radius: 12
|
||||||
color: {
|
color: {
|
||||||
if (root.loading) return "transparent"
|
if (root.loading) return "transparent"
|
||||||
if (root.disabled) return root.colorScheme.field_disabled
|
if (root._disabled) return root.colorScheme.field_disabled
|
||||||
|
|
||||||
if (root.checked) {
|
if (root.checked) {
|
||||||
if (root.hovered) return root.colorScheme.interaction_norm_hover
|
if (root.hovered) return root.colorScheme.interaction_norm_hover
|
||||||
@ -101,7 +105,7 @@ RowLayout{
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {root.hovered = true }
|
onEntered: {root.hovered = true }
|
||||||
onExited: {root.hovered = false }
|
onExited: {root.hovered = false }
|
||||||
onClicked: { root.clicked();}
|
onClicked: { if (root.enabled) root.clicked();}
|
||||||
onPressed: {root.hovered = true }
|
onPressed: {root.hovered = true }
|
||||||
onReleased: { root.hovered = containsMouse }
|
onReleased: { root.hovered = containsMouse }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ ColorScheme 4.0 ColorScheme.qml
|
|||||||
ApplicationWindow 4.0 ApplicationWindow.qml
|
ApplicationWindow 4.0 ApplicationWindow.qml
|
||||||
Button 4.0 Button.qml
|
Button 4.0 Button.qml
|
||||||
CheckBox 4.0 CheckBox.qml
|
CheckBox 4.0 CheckBox.qml
|
||||||
|
ComboBox 4.0 ComboBox.qml
|
||||||
Dialog 4.0 Dialog.qml
|
Dialog 4.0 Dialog.qml
|
||||||
Label 4.0 Label.qml
|
Label 4.0 Label.qml
|
||||||
Menu 4.0 Menu.qml
|
Menu 4.0 Menu.qml
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import QtQuick.Controls 2.12
|
|||||||
|
|
||||||
import Proton 4.0
|
import Proton 4.0
|
||||||
|
|
||||||
ColumnLayout {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property var colorScheme
|
property var colorScheme
|
||||||
|
|
||||||
@ -32,36 +32,45 @@ ColumnLayout {
|
|||||||
property var type: SettingsItem.ActionType.Toggle
|
property var type: SettingsItem.ActionType.Toggle
|
||||||
|
|
||||||
property bool checked: true
|
property bool checked: true
|
||||||
property bool disabled: false
|
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
|
property bool showSeparator: true
|
||||||
|
|
||||||
|
property var _bottomMargin: 20
|
||||||
|
property var _lineWidth: 1
|
||||||
|
property var _toggleTopMargin: 6
|
||||||
|
|
||||||
signal clicked
|
signal clicked
|
||||||
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: root.parent.Layout.maximumWidth
|
|
||||||
|
|
||||||
enum ActionType {
|
enum ActionType {
|
||||||
Toggle = 1, Button = 2, PrimaryButton = 3
|
Toggle = 1, Button = 2, PrimaryButton = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: root._bottomMargin
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id:mainLabel
|
id: mainLabel
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: root.text
|
text: root.text
|
||||||
type: Label.Body_semibold
|
type: Label.Body_semibold
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.minimumWidth: mainLabel.width
|
Layout.fillHeight: true
|
||||||
Layout.maximumWidth: root.Layout.maximumWidth - root.spacing - (
|
Layout.fillWidth: true
|
||||||
toggle.visible ? toggle.width : button.width
|
|
||||||
)
|
Layout.preferredWidth: parent.width
|
||||||
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
@ -70,15 +79,12 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle {
|
Toggle {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.topMargin: root._toggleTopMargin
|
||||||
id: toggle
|
id: toggle
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
visible: root.type == SettingsItem.ActionType.Toggle
|
visible: root.type === SettingsItem.ActionType.Toggle
|
||||||
|
|
||||||
checked: root.checked
|
checked: root.checked
|
||||||
loading: root.loading
|
loading: root.loading
|
||||||
@ -86,20 +92,25 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
id: button
|
id: button
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
visible: root.type == SettingsItem.Button || root.type == SettingsItem.PrimaryButton
|
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||||
text: root.actionText + (root.actionIcon != "" ? " " : "")
|
text: root.actionText + (root.actionIcon != "" ? " " : "")
|
||||||
loading: root.loading
|
loading: root.loading
|
||||||
icon.source: root.actionIcon
|
icon.source: root.actionIcon
|
||||||
onClicked: { if (!root.loading) root.clicked() }
|
onClicked: { if (!root.loading) root.clicked() }
|
||||||
secondary: root.type != SettingsItem.PrimaryButton
|
secondary: root.type !== SettingsItem.PrimaryButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
anchors.left: root.left
|
||||||
|
anchors.right: root.right
|
||||||
|
anchors.bottom: root.bottom
|
||||||
color: colorScheme.border_weak
|
color: colorScheme.border_weak
|
||||||
height: 1
|
height: root._lineWidth
|
||||||
|
visible: root.showSeparator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import QtQuick.Controls.impl 2.13
|
|||||||
|
|
||||||
import Proton 4.0
|
import Proton 4.0
|
||||||
|
|
||||||
ScrollView {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var colorScheme
|
property var colorScheme
|
||||||
@ -31,36 +31,45 @@ ScrollView {
|
|||||||
|
|
||||||
signal back()
|
signal back()
|
||||||
|
|
||||||
property int _leftRightMargins: 64
|
property int _leftMargin: 64
|
||||||
property int _topBottomMargins: 68
|
property int _rightMargin: 64
|
||||||
property int _spacing: 22
|
property int _topMargin: 32
|
||||||
|
property int _bottomMargin: 32
|
||||||
|
property int _spacing: 20
|
||||||
|
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
clip: true
|
clip: true
|
||||||
contentWidth: pane.width
|
|
||||||
contentHeight: pane.height
|
|
||||||
|
|
||||||
RowLayout{
|
width:root.width
|
||||||
id: pane
|
height:root.height
|
||||||
width: root.width
|
|
||||||
|
contentWidth: content.width
|
||||||
|
contentHeight: content.height
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
spacing: root._spacing
|
spacing: root._spacing
|
||||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
width: root.width - (root._leftMargin + root._rightMargin)
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: root._topBottomMargins
|
anchors{
|
||||||
Layout.bottomMargin: root._topBottomMargins
|
top: parent.top
|
||||||
Layout.leftMargin: root._leftRightMargins
|
left: parent.left
|
||||||
Layout.rightMargin: root._leftRightMargins
|
topMargin: root._topMargin
|
||||||
|
bottomMargin: root._bottomMargin
|
||||||
|
leftMargin: root._leftMargin
|
||||||
|
rightMargin: root._rightMargin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
id: backButton
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
topMargin: 10
|
topMargin: root._topMargin
|
||||||
leftMargin: 18
|
leftMargin: (root._leftMargin-backButton.width) / 2
|
||||||
}
|
}
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
onClicked: root.back()
|
onClicked: root.back()
|
||||||
|
|||||||
@ -28,7 +28,6 @@ Item {
|
|||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
property var backend
|
property var backend
|
||||||
|
|
||||||
property var user
|
property var user
|
||||||
property string address
|
property string address
|
||||||
|
|
||||||
@ -124,7 +123,9 @@ Item {
|
|||||||
console.log(" TODO configure ", model.name)
|
console.log(" TODO configure ", model.name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (user) {
|
||||||
root.user.configureAppleMail(root.address)
|
root.user.configureAppleMail(root.address)
|
||||||
|
}
|
||||||
root.dismissed()
|
root.dismissed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +140,9 @@ Item {
|
|||||||
flat: true
|
flat: true
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (user) {
|
||||||
user.setupGuideSeen = true
|
user.setupGuideSeen = true
|
||||||
|
}
|
||||||
root.dismissed()
|
root.dismissed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,31 +27,27 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
function abort() {
|
function reset() {
|
||||||
root.loginAbort(usernameTextField.text)
|
stackLayout.currentIndex = 0
|
||||||
|
loginNormalLayout.reset()
|
||||||
|
login2FALayout.reset()
|
||||||
|
login2PasswordLayout.reset()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signal login(string username, string password)
|
function abort() {
|
||||||
signal login2FA(string username, string code)
|
root.reset()
|
||||||
signal login2Password(string username, string password)
|
root.backend.loginAbort(usernameTextField.text)
|
||||||
signal loginAbort(string username)
|
}
|
||||||
|
|
||||||
implicitHeight: children[0].implicitHeight
|
implicitHeight: children[0].implicitHeight
|
||||||
implicitWidth: children[0].implicitWidth
|
implicitWidth: children[0].implicitWidth
|
||||||
|
|
||||||
property var backend
|
property var backend
|
||||||
property var window
|
|
||||||
|
|
||||||
property alias username: usernameTextField.text
|
property alias username: usernameTextField.text
|
||||||
state: "Page 1"
|
state: "Page 1"
|
||||||
|
|
||||||
onLoginAbort: {
|
|
||||||
stackLayout.currentIndex = 0
|
|
||||||
loginNormalLayout.reset()
|
|
||||||
login2FALayout.reset()
|
|
||||||
login2PasswordLayout.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
property alias currentIndex: stackLayout.currentIndex
|
property alias currentIndex: stackLayout.currentIndex
|
||||||
|
|
||||||
StackLayout {
|
StackLayout {
|
||||||
@ -83,18 +79,16 @@ Item {
|
|||||||
onLoginFreeUserError: {
|
onLoginFreeUserError: {
|
||||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError")
|
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError")
|
||||||
stackLayout.loginFailed()
|
stackLayout.loginFailed()
|
||||||
window.notifyOnlyPaidUsers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoginConnectionError: {
|
onLoginConnectionError: {
|
||||||
if (stackLayout.currentIndex == 0 ) {
|
if (stackLayout.currentIndex == 0 ) {
|
||||||
stackLayout.loginFailed()
|
stackLayout.loginFailed()
|
||||||
}
|
}
|
||||||
window.notifyConnectionLostWhileLogin()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLogin2FARequested: {
|
onLogin2FARequested: {
|
||||||
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested")
|
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested")
|
||||||
|
|
||||||
stackLayout.currentIndex = 1
|
stackLayout.currentIndex = 1
|
||||||
}
|
}
|
||||||
onLogin2FAError: {
|
onLogin2FAError: {
|
||||||
@ -108,19 +102,12 @@ Item {
|
|||||||
}
|
}
|
||||||
onLogin2FAErrorAbort: {
|
onLogin2FAErrorAbort: {
|
||||||
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort")
|
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort")
|
||||||
|
root.reset()
|
||||||
stackLayout.currentIndex = 0
|
|
||||||
loginNormalLayout.reset()
|
|
||||||
login2FALayout.reset()
|
|
||||||
login2PasswordLayout.reset()
|
|
||||||
|
|
||||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||||
passwordTextField.text = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLogin2PasswordRequested: {
|
onLogin2PasswordRequested: {
|
||||||
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested")
|
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested")
|
||||||
|
|
||||||
stackLayout.currentIndex = 2
|
stackLayout.currentIndex = 2
|
||||||
}
|
}
|
||||||
onLogin2PasswordError: {
|
onLogin2PasswordError: {
|
||||||
@ -134,22 +121,13 @@ Item {
|
|||||||
}
|
}
|
||||||
onLogin2PasswordErrorAbort: {
|
onLogin2PasswordErrorAbort: {
|
||||||
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort")
|
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort")
|
||||||
|
root.reset()
|
||||||
stackLayout.currentIndex = 0
|
|
||||||
loginNormalLayout.reset()
|
|
||||||
login2FALayout.reset()
|
|
||||||
login2PasswordLayout.reset()
|
|
||||||
|
|
||||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||||
passwordTextField.text = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoginFinished: {
|
onLoginFinished: {
|
||||||
stackLayout.currentIndex = 0
|
stackLayout.currentIndex = 0
|
||||||
loginNormalLayout.reset()
|
root.reset()
|
||||||
passwordTextField.text = ""
|
|
||||||
login2FALayout.reset()
|
|
||||||
login2PasswordLayout.reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +146,7 @@ Item {
|
|||||||
passwordTextField.enabled = true
|
passwordTextField.enabled = true
|
||||||
passwordTextField.error = false
|
passwordTextField.error = false
|
||||||
passwordTextField.assistiveText = ""
|
passwordTextField.assistiveText = ""
|
||||||
|
passwordTextField.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@ -303,7 +282,7 @@ Item {
|
|||||||
enabled = false
|
enabled = false
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
root.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
|
root.backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,6 +310,7 @@ Item {
|
|||||||
twoFactorPasswordTextField.enabled = true
|
twoFactorPasswordTextField.enabled = true
|
||||||
twoFactorPasswordTextField.error = false
|
twoFactorPasswordTextField.error = false
|
||||||
twoFactorPasswordTextField.assistiveText = ""
|
twoFactorPasswordTextField.assistiveText = ""
|
||||||
|
twoFactorPasswordTextField.text=""
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@ -388,7 +368,7 @@ Item {
|
|||||||
enabled = false
|
enabled = false
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
root.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
|
root.backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -402,6 +382,7 @@ Item {
|
|||||||
secondPasswordTextField.enabled = true
|
secondPasswordTextField.enabled = true
|
||||||
secondPasswordTextField.error = false
|
secondPasswordTextField.error = false
|
||||||
secondPasswordTextField.assistiveText = ""
|
secondPasswordTextField.assistiveText = ""
|
||||||
|
secondPasswordTextField.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@ -460,7 +441,7 @@ Item {
|
|||||||
enabled = false
|
enabled = false
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
root.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
|
root.backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ Item {
|
|||||||
NotificationFilter {
|
NotificationFilter {
|
||||||
id: notificationFilter
|
id: notificationFilter
|
||||||
|
|
||||||
source: root.notifications.all
|
source: root.notifications ? root.notifications.all : undefined
|
||||||
whitelist: root.notificationWhitelist
|
whitelist: root.notificationWhitelist
|
||||||
blacklist: root.notificationBlacklist
|
blacklist: root.notificationBlacklist
|
||||||
|
|
||||||
|
|||||||
@ -198,7 +198,7 @@ Window {
|
|||||||
Button {
|
Button {
|
||||||
Layout.margins: 12
|
Layout.margins: 12
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
visible: !viewItem.user.loggedIn
|
visible: viewItem.user ? !viewItem.user.loggedIn : false
|
||||||
text: qsTr("Sign in")
|
text: qsTr("Sign in")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.showSignIn(viewItem.username)
|
root.showSignIn(viewItem.username)
|
||||||
|
|||||||
@ -28,12 +28,6 @@ Item {
|
|||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
property var backend
|
property var backend
|
||||||
property var window
|
|
||||||
|
|
||||||
signal login(string username, string password)
|
|
||||||
signal login2FA(string username, string code)
|
|
||||||
signal login2Password(string username, string password)
|
|
||||||
signal loginAbort(string username)
|
|
||||||
|
|
||||||
implicitHeight: children[0].implicitHeight
|
implicitHeight: children[0].implicitHeight
|
||||||
implicitWidth: children[0].implicitWidth
|
implicitWidth: children[0].implicitWidth
|
||||||
@ -230,22 +224,14 @@ Item {
|
|||||||
Layout.preferredWidth: 320
|
Layout.preferredWidth: 320
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
onLogin: {
|
username: {
|
||||||
root.login(username, password)
|
if (root.backend.users.count !== 1) return ""
|
||||||
|
var user = root.backend.users.get(0)
|
||||||
|
if (user) return ""
|
||||||
|
if (user.loggedIn) return ""
|
||||||
|
return user.username
|
||||||
}
|
}
|
||||||
onLogin2FA: {
|
|
||||||
root.login2FA(username, code)
|
|
||||||
}
|
|
||||||
onLogin2Password: {
|
|
||||||
root.login2Password(username, password)
|
|
||||||
}
|
|
||||||
onLoginAbort: {
|
|
||||||
root.loginAbort(username)
|
|
||||||
}
|
|
||||||
|
|
||||||
username: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0).username : ""
|
|
||||||
backend: root.backend
|
backend: root.backend
|
||||||
window: root.window
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right margin
|
// Right margin
|
||||||
|
|||||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
// Primary buttons
|
// Primary buttons
|
||||||
|
|||||||
@ -18,7 +18,8 @@
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import Proton 4.0
|
|
||||||
|
import "../Proton"
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
@ -33,29 +33,35 @@ RowLayout {
|
|||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
error: true
|
error: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
error: true
|
error: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,34 +73,40 @@ RowLayout {
|
|||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
checked: true
|
checked: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
checked: true
|
checked: true
|
||||||
error: true
|
error: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Checkbox"
|
text: "Checkbox"
|
||||||
checked: true
|
checked: true
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
error: true
|
error: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
internal/frontend/qml/tests/ComboBoxes.qml
Normal file
100
internal/frontend/qml/tests/ComboBoxes.qml
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// 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.Window 2.13
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
import "../Proton"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
editable: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
editable: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
LayoutMirroring.enabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
editable: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
LayoutMirroring.enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
enabled: false
|
||||||
|
LayoutMirroring.enabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ["First", "Second", "Third"]
|
||||||
|
editable: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
enabled: false
|
||||||
|
LayoutMirroring.enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|||||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@ -32,30 +33,36 @@ RowLayout {
|
|||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
loading: true
|
loading: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
loading: true
|
loading: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,35 +74,41 @@ RowLayout {
|
|||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
checked: true
|
checked: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
checked: true
|
checked: true
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: "Toggle"
|
text: "Toggle"
|
||||||
checked: true
|
checked: true
|
||||||
loading: true
|
loading: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
enabled: false
|
enabled: false
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
text: ""
|
text: ""
|
||||||
checked: true
|
checked: true
|
||||||
loading: true
|
loading: true
|
||||||
|
colorScheme: root.colorScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
internal/frontend/qml/tests/Test.qml
Normal file
30
internal/frontend/qml/tests/Test.qml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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.Window 2.13
|
||||||
|
|
||||||
|
import "../Proton"
|
||||||
|
|
||||||
|
Window {
|
||||||
|
width: 800
|
||||||
|
height: 600
|
||||||
|
visible: true
|
||||||
|
TestComponents {
|
||||||
|
anchors.fill: parent
|
||||||
|
colorScheme: ProtonStyle.currentStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,16 +19,25 @@ import QtQuick 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
color: colorScheme.background_norm
|
color: colorScheme.background_norm
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
ColumnLayout {
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.margins: 20
|
||||||
|
|
||||||
|
width: root.width
|
||||||
|
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
Buttons {
|
Buttons {
|
||||||
@ -37,24 +46,18 @@ Rectangle {
|
|||||||
Layout.margins: 20
|
Layout.margins: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFields {
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 20
|
|
||||||
}
|
|
||||||
|
|
||||||
TextAreas {
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 20
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBoxes {
|
CheckBoxes {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 20
|
Layout.margins: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComboBoxes {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 20
|
||||||
|
}
|
||||||
|
|
||||||
RadioButtons {
|
RadioButtons {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -66,5 +69,18 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 20
|
Layout.margins: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextAreas {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFields {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
import Proton 4.0
|
import "../Proton"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
|
|
||||||
// Norm
|
// Norm
|
||||||
@ -148,6 +149,23 @@ RowLayout {
|
|||||||
placeholderText: "Placeholder"
|
placeholderText: "Placeholder"
|
||||||
label: "Label"
|
label: "Label"
|
||||||
hint: "Hint"
|
hint: "Hint"
|
||||||
|
assistiveText: "Assistive text"
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
placeholderText: "Placeholder"
|
||||||
|
label: "Label"
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
placeholderText: "Placeholder"
|
||||||
|
hint: "Hint"
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
@ -157,12 +175,5 @@ RowLayout {
|
|||||||
placeholderText: "Placeholder"
|
placeholderText: "Placeholder"
|
||||||
assistiveText: "Assistive text"
|
assistiveText: "Assistive text"
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
placeholderText: "Placeholder"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,6 @@ type FrontendQt struct {
|
|||||||
newVersionInfo updater.VersionInfo
|
newVersionInfo updater.VersionInfo
|
||||||
|
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
usersMtx sync.Mutex
|
|
||||||
initializing sync.WaitGroup
|
initializing sync.WaitGroup
|
||||||
initializationDone sync.Once
|
initializationDone sync.Once
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,11 @@ func (f *FrontendQt) watchEvents() {
|
|||||||
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
|
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
|
||||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||||
|
|
||||||
|
// This loop is executed outside main Qt application thread. In order
|
||||||
|
// to make sure that all signals are propagated correctly to QML we
|
||||||
|
// must call QMLBackend signals to apply any changes to GUI. The
|
||||||
|
// signals will make sure the changes are executed in main Qt app
|
||||||
|
// thread.
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case errorDetails := <-errorCh:
|
case errorDetails := <-errorCh:
|
||||||
@ -77,7 +82,7 @@ func (f *FrontendQt) watchEvents() {
|
|||||||
case <-updateApplicationCh:
|
case <-updateApplicationCh:
|
||||||
f.updateForce()
|
f.updateForce()
|
||||||
case userID := <-userChangedCh:
|
case userID := <-userChangedCh:
|
||||||
f.userChanged(userID)
|
f.qml.UserChanged(userID)
|
||||||
case <-certIssue:
|
case <-certIssue:
|
||||||
f.qml.ApiCertIssue()
|
f.qml.ApiCertIssue()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ func (f *FrontendQt) initiateQtApplication() error {
|
|||||||
// QML Engine and path
|
// QML Engine and path
|
||||||
f.engine = qml.NewQQmlApplicationEngine(f.app)
|
f.engine = qml.NewQQmlApplicationEngine(f.app)
|
||||||
|
|
||||||
f.qml = NewQMLBackend(nil)
|
f.qml = NewQMLBackend(f.engine)
|
||||||
f.qml.setup(f)
|
f.qml.setup(f)
|
||||||
f.engine.RootContext().SetContextProperty("go", f.qml)
|
f.engine.RootContext().SetContextProperty("go", f.qml)
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ func (f *FrontendQt) checkUpdates() error {
|
|||||||
|
|
||||||
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||||
checkingUpdates.Lock()
|
checkingUpdates.Lock()
|
||||||
defer checkingUpdates.Lock()
|
defer checkingUpdates.Unlock()
|
||||||
defer f.qml.CheckUpdatesFinished()
|
defer f.qml.CheckUpdatesFinished()
|
||||||
|
|
||||||
if err := f.checkUpdates(); err != nil {
|
if err := f.checkUpdates(); err != nil {
|
||||||
@ -68,7 +68,7 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
|||||||
|
|
||||||
func (f *FrontendQt) updateForce() {
|
func (f *FrontendQt) updateForce() {
|
||||||
checkingUpdates.Lock()
|
checkingUpdates.Lock()
|
||||||
defer checkingUpdates.Lock()
|
defer checkingUpdates.Unlock()
|
||||||
|
|
||||||
version := ""
|
version := ""
|
||||||
if err := f.checkUpdates(); err == nil {
|
if err := f.checkUpdates(); err == nil {
|
||||||
|
|||||||
@ -22,79 +22,10 @@ package qt
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *FrontendQt) loadUsers() {
|
|
||||||
f.usersMtx.Lock()
|
|
||||||
defer f.usersMtx.Unlock()
|
|
||||||
|
|
||||||
f.qml.Users().clear()
|
|
||||||
|
|
||||||
for _, user := range f.bridge.GetUsers() {
|
|
||||||
f.qml.Users().addUser(newQMLUserFromBacked(f, user))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no active accounts.
|
|
||||||
if f.qml.Users().Count() == 0 {
|
|
||||||
f.log.Info("No active accounts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FrontendQt) userChanged(userID string) {
|
|
||||||
f.usersMtx.Lock()
|
|
||||||
defer f.usersMtx.Unlock()
|
|
||||||
|
|
||||||
fUsers := f.qml.Users()
|
|
||||||
|
|
||||||
index := fUsers.indexByID(userID)
|
|
||||||
user, err := f.bridge.GetUser(userID)
|
|
||||||
|
|
||||||
if user == nil || err != nil {
|
|
||||||
if index >= 0 { // delete existing user
|
|
||||||
fUsers.removeUser(index)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if index < 0 { // add non-existing user
|
|
||||||
fUsers.addUser(newQMLUserFromBacked(f, user))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// update exiting user
|
|
||||||
fUsers.users[index].update(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newQMLUserFromBacked(f *FrontendQt, user types.User) *QMLUser {
|
|
||||||
qu := NewQMLUser(nil)
|
|
||||||
qu.ID = user.ID()
|
|
||||||
|
|
||||||
qu.update(user)
|
|
||||||
|
|
||||||
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
|
|
||||||
go func() {
|
|
||||||
defer qu.ToggleSplitModeFinished()
|
|
||||||
if activateSplitMode == user.IsCombinedAddressMode() {
|
|
||||||
user.SwitchAddressMode()
|
|
||||||
}
|
|
||||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
qu.ConnectLogout(func() {
|
|
||||||
qu.SetLoggedIn(false)
|
|
||||||
go user.Logout()
|
|
||||||
})
|
|
||||||
|
|
||||||
qu.ConnectConfigureAppleMail(func(address string) {
|
|
||||||
go f.configureAppleMail(qu.ID, address)
|
|
||||||
})
|
|
||||||
|
|
||||||
return qu
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FrontendQt) login(username, password string) {
|
func (f *FrontendQt) login(username, password string) {
|
||||||
var err error
|
var err error
|
||||||
f.password, err = base64.StdEncoding.DecodeString(password)
|
f.password, err = base64.StdEncoding.DecodeString(password)
|
||||||
@ -107,6 +38,7 @@ func (f *FrontendQt) login(username, password string) {
|
|||||||
|
|
||||||
f.authClient, f.auth, err = f.bridge.Login(username, f.password)
|
f.authClient, f.auth, err = f.bridge.Login(username, f.password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// TODO login free user error
|
||||||
f.qml.LoginUsernamePasswordError(err.Error())
|
f.qml.LoginUsernamePasswordError(err.Error())
|
||||||
f.loginClean()
|
f.loginClean()
|
||||||
return
|
return
|
||||||
@ -185,29 +117,24 @@ func (f *FrontendQt) login2Password(username, mboxPassword string) {
|
|||||||
func (f *FrontendQt) finishLogin() {
|
func (f *FrontendQt) finishLogin() {
|
||||||
defer f.loginClean()
|
defer f.loginClean()
|
||||||
|
|
||||||
if f.auth == nil || f.authClient == nil {
|
if len(f.password) == 0 || f.auth == nil || f.authClient == nil {
|
||||||
f.log.Errorf("Finish login: Authethication incomplete %p %p", f.auth, f.authClient)
|
f.log.
|
||||||
|
WithField("hasPass", len(f.password) != 0).
|
||||||
|
WithField("hasAuth", f.auth != nil).
|
||||||
|
WithField("hasClient", f.authClient != nil).
|
||||||
|
Error("Finish login: authethication incomplete")
|
||||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
_, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
||||||
if err != nil {
|
if err != nil && err != users.ErrUserAlreadyConnected {
|
||||||
f.log.Errorf("Authethication incomplete %p %p", f.auth, f.authClient)
|
f.log.WithError(err).Errorf("Finish login failed")
|
||||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
f.qml.Login2PasswordErrorAbort(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
index := f.qml.Users().indexByID(user.ID())
|
defer f.qml.LoginFinished()
|
||||||
if index < 0 {
|
|
||||||
qu := newQMLUserFromBacked(f, user)
|
|
||||||
qu.SetSetupGuideSeen(false)
|
|
||||||
f.qml.Users().addUser(qu)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.qml.Users().users[index].update(user)
|
|
||||||
f.qml.LoginFinished()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) loginAbort(username string) {
|
func (f *FrontendQt) loginAbort(username string) {
|
||||||
|
|||||||
@ -28,6 +28,10 @@ import (
|
|||||||
"github.com/therecipe/qt/core"
|
"github.com/therecipe/qt/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
QMLBackend_QRegisterMetaType()
|
||||||
|
}
|
||||||
|
|
||||||
// QMLBackend connects QML frontend with Go backend.
|
// QMLBackend connects QML frontend with Go backend.
|
||||||
type QMLBackend struct {
|
type QMLBackend struct {
|
||||||
core.QObject
|
core.QObject
|
||||||
@ -138,6 +142,8 @@ type QMLBackend struct {
|
|||||||
_ func(address string) `signal:addressChangedLogout`
|
_ func(address string) `signal:addressChangedLogout`
|
||||||
_ func(username string) `signal:userDisconnected`
|
_ func(username string) `signal:userDisconnected`
|
||||||
_ func() `signal:apiCertIssue`
|
_ func() `signal:apiCertIssue`
|
||||||
|
|
||||||
|
_ func(userID string) `signal:userChanged`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QMLBackend) setup(f *FrontendQt) {
|
func (q *QMLBackend) setup(f *FrontendQt) {
|
||||||
@ -150,38 +156,81 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
|||||||
return f.showOnStartup
|
return f.showOnStartup
|
||||||
})
|
})
|
||||||
|
|
||||||
q.ConnectIsDockIconVisible(func() bool {
|
q.ConnectIsDockIconVisible(dockIcon.GetDockIconVisibleState)
|
||||||
return dockIcon.GetDockIconVisibleState()
|
q.ConnectSetDockIconVisible(dockIcon.SetDockIconVisibleState)
|
||||||
})
|
|
||||||
q.ConnectSetDockIconVisible(func(visible bool) {
|
|
||||||
dockIcon.SetDockIconVisibleState(visible)
|
|
||||||
})
|
|
||||||
|
|
||||||
q.SetUsers(NewQMLUserModel(nil))
|
um := NewQMLUserModel(q)
|
||||||
f.loadUsers()
|
um.f = f
|
||||||
|
q.SetUsers(um)
|
||||||
|
um.load()
|
||||||
|
|
||||||
|
q.ConnectUserChanged(um.userChanged)
|
||||||
|
|
||||||
q.SetGoos(runtime.GOOS)
|
q.SetGoos(runtime.GOOS)
|
||||||
|
|
||||||
q.ConnectLogin(func(u, p string) { go f.login(u, p) })
|
q.ConnectLogin(func(u, p string) {
|
||||||
q.ConnectLogin2FA(func(u, p string) { go f.login2FA(u, p) })
|
go func() {
|
||||||
q.ConnectLogin2Password(func(u, p string) { go f.login2Password(u, p) })
|
defer f.panicHandler.HandlePanic()
|
||||||
q.ConnectLoginAbort(func(u string) { go f.loginAbort(u) })
|
f.login(u, p)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
q.ConnectLogin2FA(func(u, p string) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.login2FA(u, p)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
q.ConnectLogin2Password(func(u, p string) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.login2Password(u, p)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
q.ConnectLoginAbort(func(u string) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.loginAbort(u)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
go f.checkUpdatesAndNotify(false)
|
go func() {
|
||||||
q.ConnectCheckUpdates(func() { go f.checkUpdatesAndNotify(true) })
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.checkUpdatesAndNotify(false)
|
||||||
|
}()
|
||||||
|
q.ConnectCheckUpdates(func() {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.checkUpdatesAndNotify(true)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setIsDiskCacheEnabled()
|
f.setIsDiskCacheEnabled()
|
||||||
f.setDiskCachePath()
|
f.setDiskCachePath()
|
||||||
q.ConnectChangeLocalCache(func(e bool, d string) { go f.changeLocalCache(e, d) })
|
q.ConnectChangeLocalCache(func(e bool, d string) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.changeLocalCache(e, d)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setIsAutomaticUpdateOn()
|
f.setIsAutomaticUpdateOn()
|
||||||
q.ConnectToggleAutomaticUpdate(func(m bool) { go f.toggleAutomaticUpdate(m) })
|
q.ConnectToggleAutomaticUpdate(func(m bool) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.toggleAutomaticUpdate(m)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setIsAutostartOn()
|
f.setIsAutostartOn()
|
||||||
q.ConnectToggleAutostart(f.toggleAutostart)
|
q.ConnectToggleAutostart(f.toggleAutostart)
|
||||||
|
|
||||||
f.setIsBetaEnabled()
|
f.setIsBetaEnabled()
|
||||||
q.ConnectToggleBeta(func(m bool) { go f.toggleBeta(m) })
|
q.ConnectToggleBeta(func(m bool) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.toggleBeta(m)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
q.SetIsDoHEnabled(f.settings.GetBool(settings.AllowProxyKey))
|
q.SetIsDoHEnabled(f.settings.GetBool(settings.AllowProxyKey))
|
||||||
q.ConnectToggleDoH(f.toggleDoH)
|
q.ConnectToggleDoH(f.toggleDoH)
|
||||||
@ -195,7 +244,12 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
|||||||
q.ConnectChangePorts(f.changePorts)
|
q.ConnectChangePorts(f.changePorts)
|
||||||
q.ConnectIsPortFree(f.isPortFree)
|
q.ConnectIsPortFree(f.isPortFree)
|
||||||
|
|
||||||
q.ConnectTriggerReset(func() { go f.triggerReset() })
|
q.ConnectTriggerReset(func() {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.triggerReset()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setVersion()
|
f.setVersion()
|
||||||
f.setLogsPath()
|
f.setLogsPath()
|
||||||
@ -203,9 +257,24 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
|||||||
f.setLicensePath()
|
f.setLicensePath()
|
||||||
|
|
||||||
f.setCurrentEmailClient()
|
f.setCurrentEmailClient()
|
||||||
q.ConnectUpdateCurrentMailClient(func() { go f.setCurrentEmailClient() })
|
q.ConnectUpdateCurrentMailClient(func() {
|
||||||
q.ConnectReportBug(func(d, a, e string, i bool) { go f.reportBug(d, a, e, i) })
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.setCurrentEmailClient()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
q.ConnectReportBug(func(d, a, e string, i bool) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.reportBug(d, a, e, i)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setKeychain()
|
f.setKeychain()
|
||||||
q.ConnectSelectKeychain(func(k string) { go f.selectKeychain(k) })
|
q.ConnectSelectKeychain(func(k string) {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.selectKeychain(k)
|
||||||
|
}()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,17 @@
|
|||||||
package qt
|
package qt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/therecipe/qt/core"
|
"github.com/therecipe/qt/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
QMLUser_QRegisterMetaType()
|
||||||
|
QMLUserModel_QRegisterMetaType()
|
||||||
|
}
|
||||||
|
|
||||||
// QMLUserModel stores list of of users
|
// QMLUserModel stores list of of users
|
||||||
type QMLUserModel struct {
|
type QMLUserModel struct {
|
||||||
core.QAbstractListModel
|
core.QAbstractListModel
|
||||||
@ -33,72 +40,168 @@ type QMLUserModel struct {
|
|||||||
_ func() `constructor:"init"`
|
_ func() `constructor:"init"`
|
||||||
_ func(row int) *core.QVariant `slot:"get"`
|
_ func(row int) *core.QVariant `slot:"get"`
|
||||||
|
|
||||||
users []*QMLUser
|
userIDs []string
|
||||||
|
userByID map[string]*QMLUser
|
||||||
|
access sync.RWMutex
|
||||||
|
f *FrontendQt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) init() {
|
func (um *QMLUserModel) init() {
|
||||||
um.SetRoles(map[int]*core.QByteArray{
|
um.access.Lock()
|
||||||
int(core.Qt__UserRole + 1): newQByteArrayFromString("object"),
|
defer um.access.Unlock()
|
||||||
})
|
um.SetCount(0)
|
||||||
um.ConnectRowCount(um.rowCount)
|
um.ConnectRowCount(um.rowCount)
|
||||||
um.ConnectData(um.data)
|
um.ConnectData(um.data)
|
||||||
um.ConnectGet(um.get)
|
um.ConnectGet(um.get)
|
||||||
um.users = []*QMLUser{}
|
um.ConnectCount(func() int {
|
||||||
um.setCount()
|
um.access.RLock()
|
||||||
|
defer um.access.RUnlock()
|
||||||
|
return len(um.userIDs)
|
||||||
|
})
|
||||||
|
um.userIDs = []string{}
|
||||||
|
um.userByID = map[string]*QMLUser{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
|
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
|
||||||
if !index.IsValid() {
|
if !index.IsValid() {
|
||||||
|
um.f.log.WithField("size", len(um.userIDs)).Info("Trying to get user by invalid index")
|
||||||
return core.NewQVariant()
|
return core.NewQVariant()
|
||||||
}
|
}
|
||||||
return um.get(index.Row())
|
return um.get(index.Row())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) get(index int) *core.QVariant {
|
func (um *QMLUserModel) get(index int) *core.QVariant {
|
||||||
if index < 0 || index >= um.rowCount(nil) {
|
um.access.Lock()
|
||||||
|
defer um.access.Unlock()
|
||||||
|
if index < 0 || index >= len(um.userIDs) {
|
||||||
|
um.f.log.WithField("index", index).WithField("size", len(um.userIDs)).Info("Trying to get user by wrong index")
|
||||||
return core.NewQVariant()
|
return core.NewQVariant()
|
||||||
}
|
}
|
||||||
return um.users[index].ToVariant()
|
|
||||||
|
u, err := um.getUserByID(um.userIDs[index])
|
||||||
|
if err != nil {
|
||||||
|
um.f.log.WithError(err).Error("Cannot get user from backend")
|
||||||
|
return core.NewQVariant()
|
||||||
|
}
|
||||||
|
return u.ToVariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (um *QMLUserModel) getUserByID(userID string) (*QMLUser, error) {
|
||||||
|
u, ok := um.userByID[userID]
|
||||||
|
if ok {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := um.f.bridge.GetUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u = newQMLUserFromBacked(um, user)
|
||||||
|
um.userByID[userID] = u
|
||||||
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
|
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
|
||||||
return len(um.users)
|
um.access.RLock()
|
||||||
|
defer um.access.RUnlock()
|
||||||
|
return len(um.userIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) setCount() {
|
func (um *QMLUserModel) setCount() {
|
||||||
um.SetCount(len(um.users))
|
um.SetCount(len(um.userIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) addUser(user *QMLUser) {
|
func (um *QMLUserModel) addUser(userID string) {
|
||||||
um.BeginInsertRows(core.NewQModelIndex(), um.rowCount(nil), um.rowCount(nil))
|
um.BeginInsertRows(core.NewQModelIndex(), len(um.userIDs), len(um.userIDs))
|
||||||
um.users = append(um.users, user)
|
um.access.Lock()
|
||||||
um.setCount()
|
if um.indexByIDNotSafe(userID) < 0 {
|
||||||
|
um.userIDs = append(um.userIDs, userID)
|
||||||
|
}
|
||||||
|
um.access.Unlock()
|
||||||
um.EndInsertRows()
|
um.EndInsertRows()
|
||||||
|
um.setCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) removeUser(row int) {
|
func (um *QMLUserModel) removeUser(row int) {
|
||||||
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
||||||
um.users = append(um.users[:row], um.users[row+1:]...)
|
um.access.Lock()
|
||||||
um.setCount()
|
id := um.userIDs[row]
|
||||||
|
um.userIDs = append(um.userIDs[:row], um.userIDs[row+1:]...)
|
||||||
|
delete(um.userByID, id)
|
||||||
|
um.access.Unlock()
|
||||||
um.EndRemoveRows()
|
um.EndRemoveRows()
|
||||||
|
um.setCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) clear() {
|
func (um *QMLUserModel) clear() {
|
||||||
um.BeginRemoveRows(core.NewQModelIndex(), 0, um.rowCount(nil))
|
um.BeginResetModel()
|
||||||
um.users = []*QMLUser{}
|
um.access.Lock()
|
||||||
um.setCount()
|
um.userIDs = []string{}
|
||||||
um.EndRemoveRows()
|
um.userByID = map[string]*QMLUser{}
|
||||||
|
um.SetCount(0)
|
||||||
|
um.access.Unlock()
|
||||||
|
um.EndResetModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *QMLUserModel) indexByID(id string) int {
|
func (um *QMLUserModel) load() {
|
||||||
for i, qu := range um.users {
|
um.clear()
|
||||||
if id == qu.ID {
|
|
||||||
|
for _, user := range um.f.bridge.GetUsers() {
|
||||||
|
um.addUser(user.ID())
|
||||||
|
|
||||||
|
// We need mark that all existing users already saw setup
|
||||||
|
// guide. This it is OK to construct QML here because it is in main thread.
|
||||||
|
u, err := um.getUserByID(user.ID())
|
||||||
|
if err != nil {
|
||||||
|
um.f.log.WithError(err).Error("Cannot get QMLUser while loading users")
|
||||||
|
}
|
||||||
|
u.SetSetupGuideSeen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no active accounts.
|
||||||
|
if um.Count() == 0 {
|
||||||
|
um.f.log.Info("No active accounts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (um *QMLUserModel) userChanged(userID string) {
|
||||||
|
index := um.indexByIDNotSafe(userID)
|
||||||
|
user, err := um.f.bridge.GetUser(userID)
|
||||||
|
|
||||||
|
if user == nil || err != nil {
|
||||||
|
if index >= 0 { // delete existing user
|
||||||
|
um.removeUser(index)
|
||||||
|
}
|
||||||
|
// if not exiting do nothing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < 0 { // add non-existing user
|
||||||
|
um.addUser(userID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update exiting user
|
||||||
|
um.userByID[userID].update(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (um *QMLUserModel) indexByIDNotSafe(wantID string) int {
|
||||||
|
for i, id := range um.userIDs {
|
||||||
|
if id == wantID {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (um *QMLUserModel) indexByID(id string) int {
|
||||||
|
um.access.RLock()
|
||||||
|
defer um.access.RUnlock()
|
||||||
|
|
||||||
|
return um.indexByIDNotSafe(id)
|
||||||
|
}
|
||||||
|
|
||||||
// QMLUser holds data, slots and signals and for user.
|
// QMLUser holds data, slots and signals and for user.
|
||||||
type QMLUser struct {
|
type QMLUser struct {
|
||||||
core.QObject
|
core.QObject
|
||||||
@ -116,18 +219,66 @@ type QMLUser struct {
|
|||||||
_ func(makeItActive bool) `slot:"toggleSplitMode"`
|
_ func(makeItActive bool) `slot:"toggleSplitMode"`
|
||||||
_ func() `signal:"toggleSplitModeFinished"`
|
_ func() `signal:"toggleSplitModeFinished"`
|
||||||
_ func() `slot:"logout"`
|
_ func() `slot:"logout"`
|
||||||
|
_ func() `slot:"remove"`
|
||||||
_ func(address string) `slot:"configureAppleMail"`
|
_ func(address string) `slot:"configureAppleMail"`
|
||||||
|
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newQMLUserFromBacked(um *QMLUserModel, user types.User) *QMLUser {
|
||||||
|
qu := NewQMLUser(um)
|
||||||
|
qu.ID = user.ID()
|
||||||
|
|
||||||
|
qu.update(user)
|
||||||
|
|
||||||
|
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
|
||||||
|
go func() {
|
||||||
|
defer um.f.panicHandler.HandlePanic()
|
||||||
|
defer qu.ToggleSplitModeFinished()
|
||||||
|
if activateSplitMode == user.IsCombinedAddressMode() {
|
||||||
|
user.SwitchAddressMode()
|
||||||
|
}
|
||||||
|
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
qu.ConnectLogout(func() {
|
||||||
|
qu.SetLoggedIn(false)
|
||||||
|
go func() {
|
||||||
|
defer um.f.panicHandler.HandlePanic()
|
||||||
|
user.Logout()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
qu.ConnectRemove(func() {
|
||||||
|
go func() {
|
||||||
|
defer um.f.panicHandler.HandlePanic()
|
||||||
|
|
||||||
|
// TODO: remove preferences
|
||||||
|
if err := um.f.bridge.DeleteUser(qu.ID, false); err != nil {
|
||||||
|
um.f.log.WithError(err).Error("Failed to remove user")
|
||||||
|
// TODO: notification
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
qu.ConnectConfigureAppleMail(func(address string) {
|
||||||
|
go func() {
|
||||||
|
defer um.f.panicHandler.HandlePanic()
|
||||||
|
um.f.configureAppleMail(qu.ID, address)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
return qu
|
||||||
|
}
|
||||||
|
|
||||||
func (qu *QMLUser) update(user types.User) {
|
func (qu *QMLUser) update(user types.User) {
|
||||||
username := user.Username()
|
username := user.Username()
|
||||||
qu.SetAvatarText(getInitials(username))
|
qu.SetAvatarText(getInitials(username))
|
||||||
qu.SetUsername(username)
|
qu.SetUsername(username)
|
||||||
qu.SetLoggedIn(user.IsConnected())
|
qu.SetLoggedIn(user.IsConnected())
|
||||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||||
qu.SetSetupGuideSeen(true)
|
qu.SetSetupGuideSeen(false)
|
||||||
qu.SetUsedBytes(1.0) // TODO
|
qu.SetUsedBytes(1.0) // TODO
|
||||||
qu.SetTotalBytes(10000.0) // TODO
|
qu.SetTotalBytes(10000.0) // TODO
|
||||||
qu.SetPassword(user.GetBridgePassword())
|
qu.SetPassword(user.GetBridgePassword())
|
||||||
|
|||||||
@ -40,8 +40,13 @@ var (
|
|||||||
log = logrus.WithField("pkg", "users") //nolint[gochecknoglobals]
|
log = logrus.WithField("pkg", "users") //nolint[gochecknoglobals]
|
||||||
isApplicationOutdated = false //nolint[gochecknoglobals]
|
isApplicationOutdated = false //nolint[gochecknoglobals]
|
||||||
|
|
||||||
// ErrWrongMailboxPassword is returned when login password is OK but not the mailbox one.
|
// ErrWrongMailboxPassword is returned when login password is OK but
|
||||||
|
// not the mailbox one.
|
||||||
ErrWrongMailboxPassword = errors.New("wrong mailbox password")
|
ErrWrongMailboxPassword = errors.New("wrong mailbox password")
|
||||||
|
|
||||||
|
// ErrUserAlreadyConnected is returned when authentication was OK but
|
||||||
|
// there is already active account for this user.
|
||||||
|
ErrUserAlreadyConnected = errors.New("user is already connected")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Users is a struct handling users.
|
// Users is a struct handling users.
|
||||||
@ -212,7 +217,7 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []by
|
|||||||
logrus.WithError(err).Warn("Failed to delete new auth session")
|
logrus.WithError(err).Warn("Failed to delete new auth session")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("user is already connected")
|
return nil, ErrUserAlreadyConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the user's credentials with the latest auth used to connect this user.
|
// Update the user's credentials with the latest auth used to connect this user.
|
||||||
|
|||||||
Reference in New Issue
Block a user