mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 20:56:51 +00:00
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.
|
||||
go (func() {
|
||||
go func() {
|
||||
<-time.After(10 * time.Minute)
|
||||
_ = os.RemoveAll(dir)
|
||||
})()
|
||||
}()
|
||||
|
||||
// Make sure the file is only readable for the current user.
|
||||
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
|
||||
|
||||
@ -48,9 +48,9 @@ Item {
|
||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user.totalBytes ? root.user.usedBytes / root.user.totalBytes : 0
|
||||
property string totalSpace: root.spaceWithUnits(root.user.totalBytes)
|
||||
property string usedSpace: root.spaceWithUnits(root.user.usedBytes)
|
||||
property real usedFraction: root.user && root.user.totalBytes ? Math.abs(root.user.usedBytes / root.user.totalBytes) : 0
|
||||
property string totalSpace: root.spaceWithUnits(root.user ? root.user.totalBytes : 0)
|
||||
property string usedSpace: root.spaceWithUnits(root.user ? root.user.usedBytes : 0)
|
||||
|
||||
function spaceWithUnits(bytes){
|
||||
if (bytes*1 !== bytes ) return "0 kB"
|
||||
@ -96,7 +96,7 @@ Item {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
text: root.user.avatarText.toUpperCase()
|
||||
text: root.user ? root.user.avatarText.toUpperCase(): ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
@ -128,7 +128,7 @@ Item {
|
||||
)
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: user.username
|
||||
text: root.user ? user.username : ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
@ -143,7 +143,7 @@ Item {
|
||||
RowLayout {
|
||||
Label {
|
||||
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
|
||||
type: {
|
||||
switch (root.type) {
|
||||
@ -155,7 +155,7 @@ Item {
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: user.loggedIn ? " / " + root.totalSpace : ""
|
||||
text: root.user && root.user.loggedIn ? " / " + root.totalSpace : ""
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
@ -168,7 +168,7 @@ Item {
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: root.type == AccountDelegate.LargeView && user.loggedIn
|
||||
visible: root.user ? root.type == AccountDelegate.LargeView : false
|
||||
width: 140
|
||||
height: 4
|
||||
radius: 3
|
||||
@ -177,6 +177,7 @@ Item {
|
||||
Rectangle {
|
||||
radius: 3
|
||||
color: root.usedSpaceColor
|
||||
visible: root.user ? parent.visible && root.user.loggedIn : false
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
|
||||
@ -21,246 +21,216 @@ import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ScrollView {
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var notifications
|
||||
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 showSetupGuide(var user, string address)
|
||||
|
||||
ColumnLayout {
|
||||
id: pane
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _topMargin: 32
|
||||
property int _detailsTopMargin: 25
|
||||
property int _bottomMargin: 12
|
||||
property int _spacing: 20
|
||||
property int _lineWidth: 1
|
||||
|
||||
width: root.width
|
||||
ScrollView {
|
||||
clip: true
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
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
|
||||
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user.loggedIn
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user.loggedIn
|
||||
onClicked: root.user.logout()
|
||||
}
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._topMargin
|
||||
anchors.bottomMargin: root._bottomMargin
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: !root.user.loggedIn
|
||||
enabled: !root.user.loggedIn
|
||||
onClicked: root.parent.rightContent.showSignIn()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
visible: true
|
||||
enabled: true
|
||||
onClicked: root.user.remove()
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.user.logout()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: root.user ? !root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.parent.rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.user.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: root._lineWidth
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
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
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
|
||||
showSeparator: splitMode.visible
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, user.addresses[0])
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Split addresses allows you to configure multiple email addresses individually. Changing its mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user ? root.user.splitMode : false
|
||||
visible: root.user ? root.user.addresses.length > 1 : false
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
showSeparator: addressSelector.visible
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? root.user.splitMode : false
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
model: root.user ? root.user.addresses : null
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
description: "MISSING WIREFRAME" // TODO
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user.loggedIn
|
||||
visible: !root.user.splitMode
|
||||
onClicked: root.showSetupGuide(root.user,user.addresses[0])
|
||||
}
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Split addresses allows you to configure multiple email addresses individually. Changing its mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user.splitMode
|
||||
visible: root.user.addresses.length > 1
|
||||
enabled: root.user.loggedIn
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._detailsTopMargin
|
||||
anchors.bottomMargin: root._spacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user.loggedIn
|
||||
spacing: root._spacing
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
|
||||
visible: root.user.splitMode
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
Layout.fillWidth: true
|
||||
model: root.user.addresses
|
||||
|
||||
property var _topBottomMargins : 8
|
||||
property var _leftRightMargins : 16
|
||||
|
||||
background: RoundedRectangle {
|
||||
radiusTopLeft : 6
|
||||
radiusTopRight : 6
|
||||
radiusBottomLeft : addressSelector.down ? 0 : 6
|
||||
radiusBottomRight : addressSelector.down ? 0 : 6
|
||||
|
||||
height: addressSelector.contentItem.height
|
||||
//width: addressSelector.contentItem.width
|
||||
|
||||
fillColor : root.colorScheme.background_norm
|
||||
strokeColor : root.colorScheme.border_norm
|
||||
strokeWidth : 1
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: listItem
|
||||
width: root.width
|
||||
height: children[0].height + 4 + 2*addressSelector._topBottomMargins
|
||||
|
||||
Label {
|
||||
anchors {
|
||||
top : parent.top
|
||||
left : parent.left
|
||||
topMargin : addressSelector._topBottomMargins + 4
|
||||
leftMargin : addressSelector._leftRightMargins
|
||||
}
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
property bool isOver: false
|
||||
color: {
|
||||
if (listItem.isOver) return root.colorScheme.interaction_weak_hover
|
||||
if (addressSelector.highlightedIndex === index) return root.colorScheme.interaction_weak
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: listItem.isOver = true
|
||||
onExited: listItem.isOver = false
|
||||
onClicked : {
|
||||
addressSelector.currentIndex = index
|
||||
addressSelector.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
topPadding : addressSelector._topBottomMargins+4
|
||||
bottomPadding : addressSelector._topBottomMargins
|
||||
leftPadding : addressSelector._leftRightMargins
|
||||
rightPadding : addressSelector._leftRightMargins
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: addressSelector.displayText
|
||||
elide: Text.ElideMiddle
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user ? root.user.password : ""
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight: 1}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
spacing: root._spacing
|
||||
visible: root.user.loggedIn
|
||||
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Item {height: 1}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user.password
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user.password
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,19 +42,6 @@ QtObject {
|
||||
backend: root.backend
|
||||
notifications: root._notifications
|
||||
|
||||
onLogin: {
|
||||
backend.login(username, password)
|
||||
}
|
||||
onLogin2FA: {
|
||||
backend.login2FA(username, code)
|
||||
}
|
||||
onLogin2Password: {
|
||||
backend.login2Password(username, password)
|
||||
}
|
||||
onLoginAbort: {
|
||||
backend.loginAbort(username)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
backend.dockIconVisible = visible
|
||||
}
|
||||
@ -167,12 +154,10 @@ QtObject {
|
||||
break;
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:
|
||||
calcStatusWindowPosition()
|
||||
toggleWindow(statusWindow)
|
||||
break
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
toggleWindow(mainWindow)
|
||||
calcStatusWindowPosition()
|
||||
toggleWindow(statusWindow)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -181,12 +166,30 @@ QtObject {
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
if (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) {
|
||||
mainWindow.showAndRise()
|
||||
if (u) {
|
||||
if (c === 1 && u.loggedIn === false) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
if (root.backend.showOnStartup) {
|
||||
|
||||
@ -116,10 +116,10 @@ ColumnLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "name/pass error"
|
||||
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
||||
|
||||
onClicked: {
|
||||
user.loginUsernamePasswordError()
|
||||
root.backend.loginUsernamePasswordError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
@ -127,9 +127,9 @@ ColumnLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "free user error"
|
||||
enabled: user !== undefined && user.isLoginRequested
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
user.loginFreeUserError()
|
||||
root.backend.loginFreeUserError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
@ -137,9 +137,9 @@ ColumnLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "connection error"
|
||||
enabled: user !== undefined && user.isLoginRequested
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
user.loginConnectionError()
|
||||
root.backend.loginConnectionError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
@ -160,9 +160,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
text: "request"
|
||||
|
||||
enabled: user !== undefined && user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
||||
onClicked: {
|
||||
user.login2FARequested()
|
||||
root.backend.login2FARequested()
|
||||
user.isLogin2FARequested = true
|
||||
}
|
||||
}
|
||||
@ -171,9 +171,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
user.login2FAError()
|
||||
root.backend.login2FAError("")
|
||||
user.isLogin2FAProvided = false
|
||||
}
|
||||
}
|
||||
@ -182,9 +182,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined && user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
user.login2FAErrorAbort()
|
||||
root.backend.login2FAErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
@ -205,9 +205,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
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: {
|
||||
user.login2PasswordRequested()
|
||||
root.backend.login2PasswordRequested("")
|
||||
user.isLogin2PasswordRequested = true
|
||||
}
|
||||
}
|
||||
@ -216,9 +216,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
user.login2PasswordError()
|
||||
root.backend.login2PasswordError("")
|
||||
|
||||
user.isLogin2PasswordProvided = false
|
||||
}
|
||||
@ -228,9 +228,9 @@ ColumnLayout {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined && user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
user.login2PasswordErrorAbort()
|
||||
root.backend.login2PasswordErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
@ -30,54 +30,60 @@ ColumnLayout {
|
||||
property string label
|
||||
property string value
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
width: root.width
|
||||
|
||||
Item {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,24 +28,8 @@ Item {
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property var noUser: QtObject {
|
||||
property var avatarText: ""
|
||||
property var username: ""
|
||||
property var password: ""
|
||||
property var usedBytes: 1
|
||||
property var totalBytes: 1
|
||||
property var loggedIn: false
|
||||
property var splitMode: false
|
||||
property var addresses: []
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@ -183,6 +167,7 @@ Item {
|
||||
onClicked: {
|
||||
var user = root.backend.users.get(index)
|
||||
accounts.currentIndex = index
|
||||
if (!user) return
|
||||
if (user.loggedIn) {
|
||||
rightContent.showAccount()
|
||||
} else {
|
||||
@ -248,8 +233,8 @@ Item {
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
user: {
|
||||
if (accounts.currentIndex < 0) return root.noUser
|
||||
if (root.backend.users.count == 0) return root.noUser
|
||||
if (accounts.currentIndex < 0) return undefined
|
||||
if (root.backend.users.count == 0) return undefined
|
||||
return root.backend.users.get(accounts.currentIndex)
|
||||
}
|
||||
onShowSignIn: {
|
||||
@ -261,7 +246,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout { // 1
|
||||
GridLayout { // 1 Sign In
|
||||
columns: 2
|
||||
|
||||
Button {
|
||||
@ -271,7 +256,10 @@ Item {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: rightContent.showAccount()
|
||||
onClicked: {
|
||||
signIn.abort()
|
||||
rightContent.showAccount()
|
||||
}
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
@ -289,11 +277,6 @@ Item {
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
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: {
|
||||
if (accounts.currentIndex < 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 showHelpView () { rightContent.currentIndex = 6 }
|
||||
function showBugReport () { rightContent.currentIndex = 7 }
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onLoginFinished: rightContent.showAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,5 +344,4 @@ Item {
|
||||
signIn.username = username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -43,6 +43,8 @@ SettingsView {
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -62,6 +64,8 @@ SettingsView {
|
||||
autostart.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -78,6 +82,8 @@ SettingsView {
|
||||
root.notifications.askDisableBeta()
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@ -117,6 +123,8 @@ SettingsView {
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isDoHEnabled
|
||||
onClicked: root.backend.toggleDoH(!doh.checked)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -128,6 +136,8 @@ SettingsView {
|
||||
description: qsTr("Choose which ports are used by default.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showPortSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -139,6 +149,8 @@ SettingsView {
|
||||
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showSMTPSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -150,6 +162,8 @@ SettingsView {
|
||||
description: qsTr("Configure Bridge's local cache settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showLocalCacheSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -163,6 +177,8 @@ SettingsView {
|
||||
onClicked: {
|
||||
root.notifications.askResetBridge()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
onBack: root.parent.showAccount()
|
||||
|
||||
@ -39,7 +39,9 @@ SettingsView {
|
||||
actionIcon: "./icons/ic-external-link.svg"
|
||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||
type: SettingsItem.PrimaryButton
|
||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/bridge/install")}
|
||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/support/categories/bridge/")}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -55,6 +57,8 @@ SettingsView {
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -64,7 +68,9 @@ SettingsView {
|
||||
actionText: qsTr("View logs")
|
||||
description: qsTr("Open and review logs to troubleshoot.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {Qt.openUrlExternally(root.backend.logsPath)}
|
||||
onClicked: {Qt.openUrlExternally("file://"+root.backend.logsPath)}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -78,6 +84,8 @@ SettingsView {
|
||||
root.backend.updateCurrentMailClient()
|
||||
root.parent.showBugReport()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
@ -91,7 +99,7 @@ SettingsView {
|
||||
text: {
|
||||
var version = root.backend.version
|
||||
var license = qsTr("License")
|
||||
var licensePath = root.backend.licensePath
|
||||
var licensePath = "file://"+root.backend.licensePath
|
||||
var release= qsTr("Release notes")
|
||||
var releaseNotesLink = root.backend.releaseNotesLink
|
||||
return `<p style="text-align:center;">Proton Mail Bridge v${version}<br>
|
||||
|
||||
@ -54,6 +54,8 @@ SettingsView {
|
||||
type: SettingsItem.Toggle
|
||||
checked: root._diskCacheEnabled
|
||||
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
@ -67,6 +69,8 @@ SettingsView {
|
||||
pathDialog.open()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
FileDialog {
|
||||
id: pathDialog
|
||||
title: qsTr("Select cache location")
|
||||
|
||||
@ -41,11 +41,6 @@ ApplicationWindow {
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
|
||||
// show Setup Guide on every new user
|
||||
Connections {
|
||||
target: root.backend.users
|
||||
@ -98,7 +93,15 @@ ApplicationWindow {
|
||||
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
|
||||
}
|
||||
|
||||
@ -121,19 +124,6 @@ ApplicationWindow {
|
||||
onShowSetupGuide: {
|
||||
root.showSetup(user,address)
|
||||
}
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
}
|
||||
onLogin2FA: {
|
||||
root.login2FA(username, code)
|
||||
}
|
||||
onLogin2Password: {
|
||||
root.login2Password(username, password)
|
||||
}
|
||||
onLoginAbort: {
|
||||
root.loginAbort(username)
|
||||
}
|
||||
}
|
||||
|
||||
WelcomeGuide {
|
||||
@ -142,19 +132,6 @@ ApplicationWindow {
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
}
|
||||
onLogin2FA: {
|
||||
root.login2FA(username, code)
|
||||
}
|
||||
onLogin2Password: {
|
||||
root.login2Password(username, password)
|
||||
}
|
||||
onLoginAbort: {
|
||||
root.loginAbort(username)
|
||||
}
|
||||
}
|
||||
|
||||
SetupGuide {
|
||||
|
||||
@ -56,6 +56,8 @@ QtObject {
|
||||
root.updateSilentRestartNeeded,
|
||||
root.updateSilentError,
|
||||
root.updateIsLatestVersion,
|
||||
root.loginConnectionError,
|
||||
root.onlyPaidUsers,
|
||||
root.disableBeta,
|
||||
root.enableBeta,
|
||||
root.bugReportSendSuccess,
|
||||
@ -119,7 +121,7 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
},
|
||||
@ -174,7 +176,7 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||
root.updateManualError.active = false
|
||||
}
|
||||
},
|
||||
@ -217,7 +219,7 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||
root.updateForce.active = false
|
||||
}
|
||||
},
|
||||
@ -252,7 +254,7 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||
root.updateForceError.active = false
|
||||
}
|
||||
},
|
||||
@ -307,7 +309,7 @@ QtObject {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||
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
|
||||
property Notification bugReportSendSuccess: Notification {
|
||||
@ -420,9 +468,6 @@ QtObject {
|
||||
onTriggered: {
|
||||
root.bugReportSendSuccess.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -22,8 +22,6 @@ import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
import "."
|
||||
|
||||
T.ApplicationWindow {
|
||||
id: root
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
import "." as Proton
|
||||
|
||||
T.Button {
|
||||
property ColorScheme colorScheme
|
||||
@ -32,7 +33,7 @@ T.Button {
|
||||
|
||||
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?
|
||||
// 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)
|
||||
}
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
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.impl 2.12
|
||||
|
||||
import "."
|
||||
|
||||
T.Dialog {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
@ -19,7 +19,8 @@ import QtQuick 2.13
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
import "." as Proton
|
||||
|
||||
T.Label {
|
||||
id: root
|
||||
@ -46,7 +47,7 @@ T.Label {
|
||||
// weight 700, size 12, height 16, spacing 0.4
|
||||
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
|
||||
palette.link: root.colorScheme.interaction_norm
|
||||
@ -56,78 +57,78 @@ T.Label {
|
||||
|
||||
font.weight: {
|
||||
switch (root.type) {
|
||||
case Label.LabelType.Heading:
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.fontWeight_700
|
||||
case Label.LabelType.Title:
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.fontWeight_700
|
||||
case Label.LabelType.Lead:
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.fontWeight_400
|
||||
case Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body:
|
||||
return Style.fontWeight_400
|
||||
case Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
return Style.fontWeight_600
|
||||
case Label.LabelType.Body_bold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.fontWeight_700
|
||||
case Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption:
|
||||
return Style.fontWeight_400
|
||||
case Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
return Style.fontWeight_600
|
||||
case Label.LabelType.Caption_bold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.fontWeight_700
|
||||
}
|
||||
}
|
||||
|
||||
font.pixelSize: {
|
||||
switch (root.type) {
|
||||
case Label.LabelType.Heading:
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.heading_font_size
|
||||
case Label.LabelType.Title:
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.title_font_size
|
||||
case Label.LabelType.Lead:
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.lead_font_size
|
||||
case Label.LabelType.Body:
|
||||
case Label.LabelType.Body_semibold:
|
||||
case Label.LabelType.Body_bold:
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_font_size
|
||||
case Label.LabelType.Caption:
|
||||
case Label.LabelType.Caption_semibold:
|
||||
case Label.LabelType.Caption_bold:
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_font_size
|
||||
}
|
||||
}
|
||||
|
||||
lineHeight: {
|
||||
switch (root.type) {
|
||||
case Label.LabelType.Heading:
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.heading_line_height
|
||||
case Label.LabelType.Title:
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.title_line_height
|
||||
case Label.LabelType.Lead:
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.lead_line_height
|
||||
case Label.LabelType.Body:
|
||||
case Label.LabelType.Body_semibold:
|
||||
case Label.LabelType.Body_bold:
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_line_height
|
||||
case Label.LabelType.Caption:
|
||||
case Label.LabelType.Caption_semibold:
|
||||
case Label.LabelType.Caption_bold:
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_line_height
|
||||
}
|
||||
}
|
||||
|
||||
font.letterSpacing: {
|
||||
switch (root.type) {
|
||||
case Label.LabelType.Heading:
|
||||
case Label.LabelType.Title:
|
||||
case Label.LabelType.Lead:
|
||||
case Proton.Label.LabelType.Heading:
|
||||
case Proton.Label.LabelType.Title:
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return 0
|
||||
case Label.LabelType.Body:
|
||||
case Label.LabelType.Body_semibold:
|
||||
case Label.LabelType.Body_bold:
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_letter_spacing
|
||||
case Label.LabelType.Caption:
|
||||
case Label.LabelType.Caption_semibold:
|
||||
case Label.LabelType.Caption_bold:
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_letter_spacing
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@ import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
import "." as Proton
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@ -131,7 +132,7 @@ Item {
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
|
||||
@ -141,10 +142,10 @@ Item {
|
||||
|
||||
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
|
||||
id: hint
|
||||
|
||||
@ -154,7 +155,7 @@ Item {
|
||||
|
||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||
|
||||
type: Label.LabelType.Caption
|
||||
type: Proton.Label.LabelType.Caption
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
@ -168,7 +169,7 @@ Item {
|
||||
color: root.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: assistiveText
|
||||
|
||||
@ -189,7 +190,7 @@ Item {
|
||||
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 {
|
||||
|
||||
@ -21,7 +21,8 @@ import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Layouts 1.12
|
||||
import "."
|
||||
|
||||
import "." as Proton
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@ -128,22 +129,22 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
type: Label.LabelType.Body_semibold
|
||||
type: Proton.Label.LabelType.Body_semibold
|
||||
}
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: hint
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||
horizontalAlignment: Text.AlignRight
|
||||
type: Label.LabelType.Caption
|
||||
type: Proton.Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +271,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Proton.Button {
|
||||
colorScheme: root.colorScheme
|
||||
id: eyeButton
|
||||
|
||||
@ -299,7 +300,7 @@ Item {
|
||||
sourceSize.height: assistiveText.height
|
||||
}
|
||||
|
||||
Label {
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: assistiveText
|
||||
|
||||
@ -319,7 +320,7 @@ Item {
|
||||
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.impl 2.13
|
||||
|
||||
RowLayout{
|
||||
Item {
|
||||
id: root
|
||||
property var colorScheme
|
||||
property bool checked
|
||||
property bool disabled
|
||||
property bool hovered
|
||||
property bool loading
|
||||
|
||||
signal clicked
|
||||
|
||||
property bool _disabled: !enabled
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
implicitWidth: 40
|
||||
@ -38,12 +42,12 @@ RowLayout{
|
||||
radius: 20
|
||||
color: {
|
||||
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
|
||||
}
|
||||
border {
|
||||
width: 1
|
||||
color: (root.disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||
color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -55,7 +59,7 @@ RowLayout{
|
||||
radius: 12
|
||||
color: {
|
||||
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.hovered) return root.colorScheme.interaction_norm_hover
|
||||
@ -101,7 +105,7 @@ RowLayout{
|
||||
hoverEnabled: true
|
||||
onEntered: {root.hovered = true }
|
||||
onExited: {root.hovered = false }
|
||||
onClicked: { root.clicked();}
|
||||
onClicked: { if (root.enabled) root.clicked();}
|
||||
onPressed: {root.hovered = true }
|
||||
onReleased: { root.hovered = containsMouse }
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ ColorScheme 4.0 ColorScheme.qml
|
||||
ApplicationWindow 4.0 ApplicationWindow.qml
|
||||
Button 4.0 Button.qml
|
||||
CheckBox 4.0 CheckBox.qml
|
||||
ComboBox 4.0 ComboBox.qml
|
||||
Dialog 4.0 Dialog.qml
|
||||
Label 4.0 Label.qml
|
||||
Menu 4.0 Menu.qml
|
||||
|
||||
@ -21,7 +21,7 @@ import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
Item {
|
||||
id: root
|
||||
property var colorScheme
|
||||
|
||||
@ -32,36 +32,45 @@ ColumnLayout {
|
||||
property var type: SettingsItem.ActionType.Toggle
|
||||
|
||||
property bool checked: true
|
||||
property bool disabled: false
|
||||
property bool loading: false
|
||||
property bool showSeparator: true
|
||||
|
||||
property var _bottomMargin: 20
|
||||
property var _lineWidth: 1
|
||||
property var _toggleTopMargin: 6
|
||||
|
||||
signal clicked
|
||||
|
||||
spacing: 20
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.parent.Layout.maximumWidth
|
||||
|
||||
enum ActionType {
|
||||
Toggle = 1, Button = 2, PrimaryButton = 3
|
||||
}
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
anchors.fill: parent
|
||||
spacing: 16
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: root._bottomMargin
|
||||
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
id:mainLabel
|
||||
id: mainLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: root.text
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.minimumWidth: mainLabel.width
|
||||
Layout.maximumWidth: root.Layout.maximumWidth - root.spacing - (
|
||||
toggle.visible ? toggle.width : button.width
|
||||
)
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
colorScheme: root.colorScheme
|
||||
@ -70,15 +79,12 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Toggle {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: root._toggleTopMargin
|
||||
id: toggle
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type == SettingsItem.ActionType.Toggle
|
||||
visible: root.type === SettingsItem.ActionType.Toggle
|
||||
|
||||
checked: root.checked
|
||||
loading: root.loading
|
||||
@ -86,20 +92,25 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
id: button
|
||||
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 != "" ? " " : "")
|
||||
loading: root.loading
|
||||
icon.source: root.actionIcon
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
secondary: root.type != SettingsItem.PrimaryButton
|
||||
secondary: root.type !== SettingsItem.PrimaryButton
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
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
|
||||
|
||||
ScrollView {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var colorScheme
|
||||
@ -31,36 +31,45 @@ ScrollView {
|
||||
|
||||
signal back()
|
||||
|
||||
property int _leftRightMargins: 64
|
||||
property int _topBottomMargins: 68
|
||||
property int _spacing: 22
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _topMargin: 32
|
||||
property int _bottomMargin: 32
|
||||
property int _spacing: 20
|
||||
|
||||
clip: true
|
||||
contentWidth: pane.width
|
||||
contentHeight: pane.height
|
||||
|
||||
RowLayout{
|
||||
id: pane
|
||||
width: root.width
|
||||
ScrollView {
|
||||
clip: true
|
||||
|
||||
width:root.width
|
||||
height:root.height
|
||||
|
||||
contentWidth: content.width
|
||||
contentHeight: content.height
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: root._spacing
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
width: root.width - (root._leftMargin + root._rightMargin)
|
||||
|
||||
anchors{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: root._topMargin
|
||||
bottomMargin: root._bottomMargin
|
||||
leftMargin: root._leftMargin
|
||||
rightMargin: root._rightMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: 10
|
||||
leftMargin: 18
|
||||
topMargin: root._topMargin
|
||||
leftMargin: (root._leftMargin-backButton.width) / 2
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.back()
|
||||
|
||||
@ -28,7 +28,6 @@ Item {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
|
||||
property var user
|
||||
property string address
|
||||
|
||||
@ -124,7 +123,9 @@ Item {
|
||||
console.log(" TODO configure ", model.name)
|
||||
return
|
||||
}
|
||||
root.user.configureAppleMail(root.address)
|
||||
if (user) {
|
||||
root.user.configureAppleMail(root.address)
|
||||
}
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
@ -139,7 +140,9 @@ Item {
|
||||
flat: true
|
||||
|
||||
onClicked: {
|
||||
user.setupGuideSeen = true
|
||||
if (user) {
|
||||
user.setupGuideSeen = true
|
||||
}
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,31 +27,27 @@ Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
function abort() {
|
||||
root.loginAbort(usernameTextField.text)
|
||||
function reset() {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
|
||||
}
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
function abort() {
|
||||
root.reset()
|
||||
root.backend.loginAbort(usernameTextField.text)
|
||||
}
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var backend
|
||||
property var window
|
||||
|
||||
property alias username: usernameTextField.text
|
||||
state: "Page 1"
|
||||
|
||||
onLoginAbort: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
}
|
||||
|
||||
property alias currentIndex: stackLayout.currentIndex
|
||||
|
||||
StackLayout {
|
||||
@ -83,18 +79,16 @@ Item {
|
||||
onLoginFreeUserError: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError")
|
||||
stackLayout.loginFailed()
|
||||
window.notifyOnlyPaidUsers()
|
||||
}
|
||||
|
||||
onLoginConnectionError: {
|
||||
if (stackLayout.currentIndex == 0 ) {
|
||||
stackLayout.loginFailed()
|
||||
}
|
||||
window.notifyConnectionLostWhileLogin()
|
||||
}
|
||||
|
||||
onLogin2FARequested: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested")
|
||||
|
||||
stackLayout.currentIndex = 1
|
||||
}
|
||||
onLogin2FAError: {
|
||||
@ -108,19 +102,12 @@ Item {
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort")
|
||||
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
|
||||
root.reset()
|
||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
onLogin2PasswordRequested: {
|
||||
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested")
|
||||
|
||||
stackLayout.currentIndex = 2
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
@ -134,22 +121,13 @@ Item {
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort")
|
||||
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
|
||||
root.reset()
|
||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
onLoginFinished: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
passwordTextField.text = ""
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
root.reset()
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,6 +146,7 @@ Item {
|
||||
passwordTextField.enabled = true
|
||||
passwordTextField.error = false
|
||||
passwordTextField.assistiveText = ""
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
@ -303,7 +282,7 @@ Item {
|
||||
enabled = false
|
||||
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.error = false
|
||||
twoFactorPasswordTextField.assistiveText = ""
|
||||
twoFactorPasswordTextField.text=""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
@ -388,7 +368,7 @@ Item {
|
||||
enabled = false
|
||||
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.error = false
|
||||
secondPasswordTextField.assistiveText = ""
|
||||
secondPasswordTextField.text = ""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
@ -460,7 +441,7 @@ Item {
|
||||
enabled = false
|
||||
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 {
|
||||
id: notificationFilter
|
||||
|
||||
source: root.notifications.all
|
||||
source: root.notifications ? root.notifications.all : undefined
|
||||
whitelist: root.notificationWhitelist
|
||||
blacklist: root.notificationBlacklist
|
||||
|
||||
@ -59,19 +59,19 @@ Item {
|
||||
label.text = topmost.text
|
||||
|
||||
switch (topmost.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
image.color = root.colorScheme.signal_danger
|
||||
label.color = root.colorScheme.signal_danger
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
image.color = root.colorScheme.signal_warning
|
||||
label.color = root.colorScheme.signal_warning
|
||||
break;
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
image.color = root.colorScheme.signal_success
|
||||
label.color = root.colorScheme.signal_success
|
||||
break;
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
image.color = root.colorScheme.signal_info
|
||||
label.color = root.colorScheme.signal_info
|
||||
break;
|
||||
|
||||
@ -198,7 +198,7 @@ Window {
|
||||
Button {
|
||||
Layout.margins: 12
|
||||
colorScheme: root.colorScheme
|
||||
visible: !viewItem.user.loggedIn
|
||||
visible: viewItem.user ? !viewItem.user.loggedIn : false
|
||||
text: qsTr("Sign in")
|
||||
onClicked: {
|
||||
root.showSignIn(viewItem.username)
|
||||
|
||||
@ -28,12 +28,6 @@ Item {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
property var window
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
@ -230,22 +224,14 @@ Item {
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillWidth: true
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
username: {
|
||||
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
|
||||
window: root.window
|
||||
}
|
||||
|
||||
// Right margin
|
||||
|
||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
// Primary buttons
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import Proton 4.0
|
||||
|
||||
import "../Proton"
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
@ -20,7 +20,7 @@ import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
@ -33,29 +33,35 @@ RowLayout {
|
||||
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
error: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
CheckBox {
|
||||
text: ""
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: ""
|
||||
error: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: ""
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,34 +73,40 @@ RowLayout {
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
checked: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
checked: true
|
||||
error: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Checkbox"
|
||||
checked: true
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
CheckBox {
|
||||
text: ""
|
||||
checked: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: ""
|
||||
checked: true
|
||||
error: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: ""
|
||||
checked: true
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
ColumnLayout {
|
||||
@ -32,30 +33,36 @@ RowLayout {
|
||||
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
loading: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
loading: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,35 +74,41 @@ RowLayout {
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
checked: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
checked: true
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: "Toggle"
|
||||
checked: true
|
||||
loading: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
checked: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
checked: true
|
||||
enabled: false
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: ""
|
||||
checked: true
|
||||
loading: true
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,52 +19,68 @@ import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
color: colorScheme.background_norm
|
||||
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
|
||||
|
||||
spacing: 5
|
||||
ColumnLayout {
|
||||
anchors.margins: 20
|
||||
|
||||
Buttons {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
width: root.width
|
||||
|
||||
TextFields {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
spacing: 5
|
||||
|
||||
TextAreas {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
Buttons {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
CheckBoxes {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
CheckBoxes {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
RadioButtons {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
ComboBoxes {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
Switches {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
RadioButtons {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
Switches {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
TextAreas {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
|
||||
TextFields {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
@ -20,9 +20,10 @@ import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import "../Proton"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
// Norm
|
||||
@ -148,6 +149,23 @@ RowLayout {
|
||||
placeholderText: "Placeholder"
|
||||
label: "Label"
|
||||
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 {
|
||||
@ -157,12 +175,5 @@ RowLayout {
|
||||
placeholderText: "Placeholder"
|
||||
assistiveText: "Assistive text"
|
||||
}
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
placeholderText: "Placeholder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,6 @@ type FrontendQt struct {
|
||||
newVersionInfo updater.VersionInfo
|
||||
|
||||
log *logrus.Entry
|
||||
usersMtx sync.Mutex
|
||||
initializing sync.WaitGroup
|
||||
initializationDone sync.Once
|
||||
|
||||
|
||||
@ -43,6 +43,11 @@ func (f *FrontendQt) watchEvents() {
|
||||
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
|
||||
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 {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
@ -77,7 +82,7 @@ func (f *FrontendQt) watchEvents() {
|
||||
case <-updateApplicationCh:
|
||||
f.updateForce()
|
||||
case userID := <-userChangedCh:
|
||||
f.userChanged(userID)
|
||||
f.qml.UserChanged(userID)
|
||||
case <-certIssue:
|
||||
f.qml.ApiCertIssue()
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ func (f *FrontendQt) initiateQtApplication() error {
|
||||
// QML Engine and path
|
||||
f.engine = qml.NewQQmlApplicationEngine(f.app)
|
||||
|
||||
f.qml = NewQMLBackend(nil)
|
||||
f.qml = NewQMLBackend(f.engine)
|
||||
f.qml.setup(f)
|
||||
f.engine.RootContext().SetContextProperty("go", f.qml)
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ func (f *FrontendQt) checkUpdates() error {
|
||||
|
||||
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
defer checkingUpdates.Unlock()
|
||||
defer f.qml.CheckUpdatesFinished()
|
||||
|
||||
if err := f.checkUpdates(); err != nil {
|
||||
@ -68,7 +68,7 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||
|
||||
func (f *FrontendQt) updateForce() {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
defer checkingUpdates.Unlock()
|
||||
|
||||
version := ""
|
||||
if err := f.checkUpdates(); err == nil {
|
||||
|
||||
@ -22,79 +22,10 @@ package qt
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"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) {
|
||||
var err error
|
||||
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)
|
||||
if err != nil {
|
||||
// TODO login free user error
|
||||
f.qml.LoginUsernamePasswordError(err.Error())
|
||||
f.loginClean()
|
||||
return
|
||||
@ -185,29 +117,24 @@ func (f *FrontendQt) login2Password(username, mboxPassword string) {
|
||||
func (f *FrontendQt) finishLogin() {
|
||||
defer f.loginClean()
|
||||
|
||||
if f.auth == nil || f.authClient == nil {
|
||||
f.log.Errorf("Finish login: Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
if len(f.password) == 0 || f.auth == nil || f.authClient == nil {
|
||||
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.")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
||||
if err != nil {
|
||||
f.log.Errorf("Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||
_, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
||||
if err != nil && err != users.ErrUserAlreadyConnected {
|
||||
f.log.WithError(err).Errorf("Finish login failed")
|
||||
f.qml.Login2PasswordErrorAbort(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
index := f.qml.Users().indexByID(user.ID())
|
||||
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()
|
||||
defer f.qml.LoginFinished()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loginAbort(username string) {
|
||||
|
||||
@ -28,6 +28,10 @@ import (
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
QMLBackend_QRegisterMetaType()
|
||||
}
|
||||
|
||||
// QMLBackend connects QML frontend with Go backend.
|
||||
type QMLBackend struct {
|
||||
core.QObject
|
||||
@ -138,6 +142,8 @@ type QMLBackend struct {
|
||||
_ func(address string) `signal:addressChangedLogout`
|
||||
_ func(username string) `signal:userDisconnected`
|
||||
_ func() `signal:apiCertIssue`
|
||||
|
||||
_ func(userID string) `signal:userChanged`
|
||||
}
|
||||
|
||||
func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
@ -150,38 +156,81 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
return f.showOnStartup
|
||||
})
|
||||
|
||||
q.ConnectIsDockIconVisible(func() bool {
|
||||
return dockIcon.GetDockIconVisibleState()
|
||||
})
|
||||
q.ConnectSetDockIconVisible(func(visible bool) {
|
||||
dockIcon.SetDockIconVisibleState(visible)
|
||||
})
|
||||
q.ConnectIsDockIconVisible(dockIcon.GetDockIconVisibleState)
|
||||
q.ConnectSetDockIconVisible(dockIcon.SetDockIconVisibleState)
|
||||
|
||||
q.SetUsers(NewQMLUserModel(nil))
|
||||
f.loadUsers()
|
||||
um := NewQMLUserModel(q)
|
||||
um.f = f
|
||||
q.SetUsers(um)
|
||||
um.load()
|
||||
|
||||
q.ConnectUserChanged(um.userChanged)
|
||||
|
||||
q.SetGoos(runtime.GOOS)
|
||||
|
||||
q.ConnectLogin(func(u, p string) { go f.login(u, p) })
|
||||
q.ConnectLogin2FA(func(u, p string) { go f.login2FA(u, p) })
|
||||
q.ConnectLogin2Password(func(u, p string) { go f.login2Password(u, p) })
|
||||
q.ConnectLoginAbort(func(u string) { go f.loginAbort(u) })
|
||||
q.ConnectLogin(func(u, p string) {
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
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)
|
||||
q.ConnectCheckUpdates(func() { go f.checkUpdatesAndNotify(true) })
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.checkUpdatesAndNotify(false)
|
||||
}()
|
||||
q.ConnectCheckUpdates(func() {
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.checkUpdatesAndNotify(true)
|
||||
}()
|
||||
})
|
||||
|
||||
f.setIsDiskCacheEnabled()
|
||||
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()
|
||||
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()
|
||||
q.ConnectToggleAutostart(f.toggleAutostart)
|
||||
|
||||
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.ConnectToggleDoH(f.toggleDoH)
|
||||
@ -195,7 +244,12 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
q.ConnectChangePorts(f.changePorts)
|
||||
q.ConnectIsPortFree(f.isPortFree)
|
||||
|
||||
q.ConnectTriggerReset(func() { go f.triggerReset() })
|
||||
q.ConnectTriggerReset(func() {
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.triggerReset()
|
||||
}()
|
||||
})
|
||||
|
||||
f.setVersion()
|
||||
f.setLogsPath()
|
||||
@ -203,9 +257,24 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
f.setLicensePath()
|
||||
|
||||
f.setCurrentEmailClient()
|
||||
q.ConnectUpdateCurrentMailClient(func() { go f.setCurrentEmailClient() })
|
||||
q.ConnectReportBug(func(d, a, e string, i bool) { go f.reportBug(d, a, e, i) })
|
||||
q.ConnectUpdateCurrentMailClient(func() {
|
||||
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()
|
||||
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
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
QMLUser_QRegisterMetaType()
|
||||
QMLUserModel_QRegisterMetaType()
|
||||
}
|
||||
|
||||
// QMLUserModel stores list of of users
|
||||
type QMLUserModel struct {
|
||||
core.QAbstractListModel
|
||||
@ -33,72 +40,168 @@ type QMLUserModel struct {
|
||||
_ func() `constructor:"init"`
|
||||
_ func(row int) *core.QVariant `slot:"get"`
|
||||
|
||||
users []*QMLUser
|
||||
userIDs []string
|
||||
userByID map[string]*QMLUser
|
||||
access sync.RWMutex
|
||||
f *FrontendQt
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) init() {
|
||||
um.SetRoles(map[int]*core.QByteArray{
|
||||
int(core.Qt__UserRole + 1): newQByteArrayFromString("object"),
|
||||
})
|
||||
um.access.Lock()
|
||||
defer um.access.Unlock()
|
||||
um.SetCount(0)
|
||||
um.ConnectRowCount(um.rowCount)
|
||||
um.ConnectData(um.data)
|
||||
um.ConnectGet(um.get)
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
um.ConnectCount(func() int {
|
||||
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 {
|
||||
if !index.IsValid() {
|
||||
um.f.log.WithField("size", len(um.userIDs)).Info("Trying to get user by invalid index")
|
||||
return core.NewQVariant()
|
||||
}
|
||||
return um.get(index.Row())
|
||||
}
|
||||
|
||||
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 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 {
|
||||
return len(um.users)
|
||||
um.access.RLock()
|
||||
defer um.access.RUnlock()
|
||||
return len(um.userIDs)
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) setCount() {
|
||||
um.SetCount(len(um.users))
|
||||
um.SetCount(len(um.userIDs))
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) addUser(user *QMLUser) {
|
||||
um.BeginInsertRows(core.NewQModelIndex(), um.rowCount(nil), um.rowCount(nil))
|
||||
um.users = append(um.users, user)
|
||||
um.setCount()
|
||||
func (um *QMLUserModel) addUser(userID string) {
|
||||
um.BeginInsertRows(core.NewQModelIndex(), len(um.userIDs), len(um.userIDs))
|
||||
um.access.Lock()
|
||||
if um.indexByIDNotSafe(userID) < 0 {
|
||||
um.userIDs = append(um.userIDs, userID)
|
||||
}
|
||||
um.access.Unlock()
|
||||
um.EndInsertRows()
|
||||
um.setCount()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) removeUser(row int) {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
||||
um.users = append(um.users[:row], um.users[row+1:]...)
|
||||
um.setCount()
|
||||
um.access.Lock()
|
||||
id := um.userIDs[row]
|
||||
um.userIDs = append(um.userIDs[:row], um.userIDs[row+1:]...)
|
||||
delete(um.userByID, id)
|
||||
um.access.Unlock()
|
||||
um.EndRemoveRows()
|
||||
um.setCount()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) clear() {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), 0, um.rowCount(nil))
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
um.EndRemoveRows()
|
||||
um.BeginResetModel()
|
||||
um.access.Lock()
|
||||
um.userIDs = []string{}
|
||||
um.userByID = map[string]*QMLUser{}
|
||||
um.SetCount(0)
|
||||
um.access.Unlock()
|
||||
um.EndResetModel()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) indexByID(id string) int {
|
||||
for i, qu := range um.users {
|
||||
if id == qu.ID {
|
||||
func (um *QMLUserModel) load() {
|
||||
um.clear()
|
||||
|
||||
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 -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.
|
||||
type QMLUser struct {
|
||||
core.QObject
|
||||
@ -116,18 +219,66 @@ type QMLUser struct {
|
||||
_ func(makeItActive bool) `slot:"toggleSplitMode"`
|
||||
_ func() `signal:"toggleSplitModeFinished"`
|
||||
_ func() `slot:"logout"`
|
||||
_ func() `slot:"remove"`
|
||||
_ func(address string) `slot:"configureAppleMail"`
|
||||
|
||||
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) {
|
||||
username := user.Username()
|
||||
qu.SetAvatarText(getInitials(username))
|
||||
qu.SetUsername(username)
|
||||
qu.SetLoggedIn(user.IsConnected())
|
||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||
qu.SetSetupGuideSeen(true)
|
||||
qu.SetSetupGuideSeen(false)
|
||||
qu.SetUsedBytes(1.0) // TODO
|
||||
qu.SetTotalBytes(10000.0) // TODO
|
||||
qu.SetPassword(user.GetBridgePassword())
|
||||
|
||||
@ -40,8 +40,13 @@ var (
|
||||
log = logrus.WithField("pkg", "users") //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")
|
||||
|
||||
// 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.
|
||||
@ -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")
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user