From dbc10685f0373f988f7b72c1473001b2526464ec Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 27 Aug 2023 09:54:12 +0000 Subject: [PATCH] Port login to FormCard --- src/login.cpp | 18 ++- src/login.h | 10 ++ src/qml/Component/Login/Loading.qml | 13 ++- src/qml/Component/Login/Login.qml | 43 ++++---- src/qml/Component/Login/LoginMethod.qml | 22 ++-- src/qml/Component/Login/LoginRegister.qml | 21 ++-- src/qml/Component/Login/LoginStep.qml | 18 +-- src/qml/Component/Login/Password.qml | 53 +++++---- src/qml/Component/Login/Sso.qml | 57 ++++------ src/qml/Page/WelcomePage.qml | 128 ++++++++++++---------- 10 files changed, 205 insertions(+), 178 deletions(-) diff --git a/src/login.cpp b/src/login.cpp index 962cc925d..84d0ee7a4 100644 --- a/src/login.cpp +++ b/src/login.cpp @@ -87,7 +87,11 @@ void Login::init() Q_EMIT isLoggingInChanged(); }); connect(m_connection, &Connection::loginError, this, [this](QString error, const QString &) { - Q_EMIT errorOccured(i18n("Login Failed: %1", error)); + if (error == QStringLiteral("Invalid username or password")) { + setInvalidPassword(true); + } else { + Q_EMIT errorOccured(i18n("Login Failed: %1", error)); + } m_isLoggingIn = false; Q_EMIT isLoggingInChanged(); }); @@ -133,6 +137,7 @@ QString Login::password() const void Login::setPassword(const QString &password) { + setInvalidPassword(false); m_password = password; Q_EMIT passwordChanged(); } @@ -199,4 +204,15 @@ bool Login::isLoggedIn() const return m_isLoggedIn; } +void Login::setInvalidPassword(bool invalid) +{ + m_invalidPassword = invalid; + Q_EMIT isInvalidPasswordChanged(); +} + +bool Login::isInvalidPassword() const +{ + return m_invalidPassword; +} + #include "moc_login.cpp" diff --git a/src/login.h b/src/login.h index bb85ee1ce..d34664284 100644 --- a/src/login.h +++ b/src/login.h @@ -73,6 +73,11 @@ class Login : public QObject */ Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged) + /** + * @brief Whether the password (or the username) is invalid. + */ + Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged) + public: explicit Login(QObject *parent = nullptr); @@ -100,6 +105,9 @@ public: bool isLoggedIn() const; + bool isInvalidPassword() const; + void setInvalidPassword(bool invalid); + Q_INVOKABLE void login(); Q_INVOKABLE void loginWithSso(); @@ -116,6 +124,7 @@ Q_SIGNALS: void testingChanged(); void isLoggingInChanged(); void isLoggedInChanged(); + void isInvalidPasswordChanged(); private: void setHomeserverReachable(bool reachable); @@ -131,4 +140,5 @@ private: bool m_testing = false; bool m_isLoggingIn = false; bool m_isLoggedIn = false; + bool m_invalidPassword = false; }; diff --git a/src/qml/Component/Login/Loading.qml b/src/qml/Component/Login/Loading.qml index 3ee2afcde..2a6e26bdb 100644 --- a/src/qml/Component/Login/Loading.qml +++ b/src/qml/Component/Login/Loading.qml @@ -6,16 +6,19 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.20 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 -Kirigami.LoadingPlaceholder { - property var showContinueButton: false - property var showBackButton: false - - QQC2.Label { +LoginStep { + id: root + FormCard.FormTextDelegate { text: i18n("Please wait. This might take a little while.") } + FormCard.AbstractFormDelegate { + contentItem: QQC2.BusyIndicator {} + background: null + } Connections { target: Controller diff --git a/src/qml/Component/Login/Login.qml b/src/qml/Component/Login/Login.qml index 93f155d57..088c56434 100644 --- a/src/qml/Component/Login/Login.qml +++ b/src/qml/Component/Login/Login.qml @@ -7,43 +7,34 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 LoginStep { - id: login + id: root - showContinueButton: true - showBackButton: false - - title: i18nc("@title", "Login") - message: i18n("Enter your Matrix ID") + onActiveFocusChanged: if (activeFocus) matrixIdField.forceActiveFocus() Component.onCompleted: { LoginHelper.matrixId = "" } - Kirigami.FormLayout { - QQC2.TextField { - id: matrixIdField - Kirigami.FormData.label: i18n("Matrix ID:") - placeholderText: "@user:matrix.org" - Accessible.name: i18n("Matrix ID") - onTextChanged: { - LoginHelper.matrixId = text - } + FormCard.FormTextFieldDelegate { + id: matrixIdField + label: i18n("Matrix ID:") + placeholderText: "@user:example.org" + Accessible.name: i18n("Matrix ID") + onTextChanged: { + LoginHelper.matrixId = text + } - Component.onCompleted: { - matrixIdField.forceActiveFocus() - } - - Keys.onReturnPressed: { - login.action.trigger() - } + Keys.onReturnPressed: { + root.action.trigger() } } - action: Kirigami.Action { + nextAction: Kirigami.Action { text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue") onTriggered: { if (LoginHelper.supportsSso && LoginHelper.supportsPassword) { @@ -56,4 +47,10 @@ LoginStep { } enabled: LoginHelper.homeserverReachable } + // TODO: enable once we have registration + // previousAction: Kirigami.Action { + // onTriggered: { + // root.processed("qrc:/Login.qml") + // } + // } } diff --git a/src/qml/Component/Login/LoginMethod.qml b/src/qml/Component/Login/LoginMethod.qml index 71b5ddb7c..d05bf59e3 100644 --- a/src/qml/Component/Login/LoginMethod.qml +++ b/src/qml/Component/Login/LoginMethod.qml @@ -4,28 +4,26 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 + import org.kde.kirigami 2.15 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 LoginStep { - id: loginMethod + id: root - title: i18n("Login Methods") + onActiveFocusChanged: if (activeFocus) loginPasswordButton.forceActiveFocus() - Layout.alignment: Qt.AlignHCenter - - QQC2.Button { - Layout.alignment: Qt.AlignHCenter - text: i18n("Login with password") - Layout.preferredWidth: Kirigami.Units.gridUnit * 12 + FormCard.FormButtonDelegate { + id: loginPasswordButton + text: i18nc("@action:button", "Login with password") onClicked: processed("qrc:/Password.qml") } - QQC2.Button { - Layout.alignment: Qt.AlignHCenter - text: i18n("Login with single sign-on") - Layout.preferredWidth: Kirigami.Units.gridUnit * 12 + FormCard.FormButtonDelegate { + id: loginSsoButton + text: i18nc("@action:button", "Login with single sign-on") onClicked: processed("qrc:/Sso.qml") } } diff --git a/src/qml/Component/Login/LoginRegister.qml b/src/qml/Component/Login/LoginRegister.qml index 5c82d8935..f2af06fdc 100644 --- a/src/qml/Component/Login/LoginRegister.qml +++ b/src/qml/Component/Login/LoginRegister.qml @@ -6,25 +6,22 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 LoginStep { - id: loginRegister + id: root - Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true - QQC2.Button { - Layout.alignment: Qt.AlignHCenter - text: i18n("Login") - Layout.preferredWidth: Kirigami.Units.gridUnit * 12 - onClicked: processed("qrc:/Login.qml") + FormCard.FormButtonDelegate { + text: i18nc("@action:button", "Login") + onClicked: root.processed("qrc:/Login.qml") } - QQC2.Button { - Layout.alignment: Qt.AlignHCenter - text: i18n("Register") - Layout.preferredWidth: Kirigami.Units.gridUnit * 12 - onClicked: processed("qrc:/Homeserver.qml") + FormCard.FormButtonDelegate { + text: i18nc("@action:button", "Register") + onClicked: root.processed("qrc:/Homeserver.qml") } } diff --git a/src/qml/Component/Login/LoginStep.qml b/src/qml/Component/Login/LoginStep.qml index b5242badd..ad047ab2a 100644 --- a/src/qml/Component/Login/LoginStep.qml +++ b/src/qml/Component/Login/LoginStep.qml @@ -7,21 +7,25 @@ import QtQuick.Layouts 1.14 /// Step for the login/registration flow ColumnLayout { + id: root - property string title: i18n("Welcome") - property string message: i18n("Welcome") - property bool showContinueButton: false - property bool showBackButton: false - property bool acceptable: false - property string previousUrl: "" + /// Set to true if the login step does not have any controls. This will ensure that the focus remains on the "continue" button + property bool noControls: false /// Process this module, this is called by the continue button. /// Should call \sa processed when it finish successfully. - property QQC2.Action action: null + property QQC2.Action nextAction: null + + /// Go to the previous module. This is called by the "go back" button. + /// If no "go back" button should be shown, this should be null. + property QQC2.Action previousAction: null /// Called when switching to the next step. signal processed(url nextUrl) + /// Show a message in a banner at the top of the page. signal showMessage(string message) + signal clearError() + } diff --git a/src/qml/Component/Login/Password.qml b/src/qml/Component/Login/Password.qml index c42ba1acc..46d808221 100644 --- a/src/qml/Component/Login/Password.qml +++ b/src/qml/Component/Login/Password.qml @@ -6,25 +6,12 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 LoginStep { - id: password - - title: i18nc("@title", "Password") - message: i18n("Enter your password") - showContinueButton: true - showBackButton: true - previousUrl: LoginHelper.isLoggingIn ? "" : LoginHelper.supportsSso ? "qrc:/LoginMethod.qml" : "qrc:/Login.qml" - - action: Kirigami.Action { - text: i18nc("@action:button", "Login") - enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn - onTriggered: { - LoginHelper.login(); - } - } + id: root Connections { target: LoginHelper @@ -33,20 +20,32 @@ LoginStep { } } - Kirigami.FormLayout { - Kirigami.PasswordField { - id: passwordField - onTextChanged: LoginHelper.password = text - enabled: !LoginHelper.isLoggingIn - Accessible.name: i18n("Password") + onActiveFocusChanged: if(activeFocus) passwordField.forceActiveFocus() - Component.onCompleted: { - passwordField.forceActiveFocus() - } + FormCard.FormTextFieldDelegate { + id: passwordField - Keys.onReturnPressed: { - password.action.trigger() - } + label: i18n("Password:") + onTextChanged: LoginHelper.password = text + enabled: !LoginHelper.isLoggingIn + echoMode: TextInput.Password + Accessible.name: i18n("Password") + statusMessage: LoginHelper.isInvalidPassword ? i18n("Invalid username or password") : "" + + Keys.onReturnPressed: { + root.action.trigger() } } + + nextAction: Kirigami.Action { + text: i18nc("@action:button", "Login") + enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn + onTriggered: { + root.clearError() + LoginHelper.login(); + } + } + previousAction: Kirigami.Action { + onTriggered: processed("qrc:/Login.qml") + } } diff --git a/src/qml/Component/Login/Sso.qml b/src/qml/Component/Login/Sso.qml index 152c1ec4d..61f4b36fa 100644 --- a/src/qml/Component/Login/Sso.qml +++ b/src/qml/Component/Login/Sso.qml @@ -6,47 +6,38 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.12 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 LoginStep { id: root - title: i18nc("@title", "Login") - message: i18n("Login with single sign-on") + noControls: true - Kirigami.FormLayout { - Connections { - target: LoginHelper - function onSsoUrlChanged() { - UrlHelper.openUrl(LoginHelper.ssoUrl) - root.showMessage(i18n("Complete the authentication steps in your browser")) - loginButton.enabled = true - loginButton.text = i18n("Login") - } - function onConnected() { - processed("qrc:/Loading.qml") - } + Component.onCompleted: LoginHelper.loginWithSso() + + Connections { + target: LoginHelper + function onSsoUrlChanged() { + UrlHelper.openUrl(LoginHelper.ssoUrl) } - RowLayout { - QQC2.Button { - text: i18nc("@action:button", "Back") - - onClicked: { - module.source = "qrc:/Login.qml" - } - } - QQC2.Button { - id: loginButton - text: i18n("Login") - onClicked: { - LoginHelper.loginWithSso() - loginButton.enabled = false - loginButton.text = i18n("Loading…") - } - Component.onCompleted: forceActiveFocus() - Keys.onReturnPressed: clicked() - } + function onConnected() { + processed("qrc:/Loading.qml") } } + + FormCard.FormTextDelegate { + text: i18n("Continue the login process in your browser.") + } + + previousAction: Kirigami.Action { + onTriggered: processed("qrc:/Login.qml") + } + + nextAction: Kirigami.Action { + text: i18nc("@action:button", "Re-open SSO URL") + onTriggered: UrlHelper.openUrl(LoginHelper.ssoUrl) + } } + diff --git a/src/qml/Page/WelcomePage.qml b/src/qml/Page/WelcomePage.qml index 4ba68dc3d..79cfb3317 100644 --- a/src/qml/Page/WelcomePage.qml +++ b/src/qml/Page/WelcomePage.qml @@ -6,15 +6,16 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami +import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.neochat 1.0 -Kirigami.ScrollablePage { - id: welcomePage +FormCard.FormCardPage { + id: root property alias currentStep: module.item - title: module.item.title ?? i18n("Welcome") + title: i18n("Welcome") header: QQC2.Control { contentItem: Kirigami.InlineMessage { @@ -25,79 +26,90 @@ Kirigami.ScrollablePage { } } - Component.onCompleted: LoginHelper.init() + FormCard.FormCard { + id: contentCard - Connections { - target: LoginHelper - function onErrorOccured(message) { - headerMessage.text = message; - headerMessage.visible = true; - headerMessage.type = Kirigami.MessageType.Error; + FormCard.AbstractFormDelegate { + contentItem: Kirigami.Icon { + source: "org.kde.neochat" + Layout.fillWidth: true + Layout.preferredHeight: Kirigami.Units.gridUnit * 16 + } + background: Item {} + onActiveFocusChanged: if (activeFocus) module.item.forceActiveFocus() } - } - Connections { - target: Controller - function onInitiated() { - pageStack.layers.pop(); + FormCard.FormTextDelegate { + id: welcomeMessage + text: AccountRegistry.accountCount > 0 ? i18n("Log in to a different account.") : i18n("Welcome to NeoChat! Continue by logging in.") } - } - ColumnLayout { - Kirigami.Icon { - source: "org.kde.neochat" - Layout.fillWidth: true - Layout.preferredHeight: Kirigami.Units.gridUnit * 16 - } - QQC2.Label { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - font.pixelSize: 25 - text: module.item.message ?? module.item.title ?? i18n("Welcome to Matrix") + FormCard.FormDelegateSeparator { + above: welcomeMessage } Loader { id: module - Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true source: "qrc:/Login.qml" - onSourceChanged: { - headerMessage.visible = false - headerMessage.text = "" - } - } - RowLayout { - Layout.alignment: Qt.AlignHCenter - QQC2.Button { - text: i18nc("@action:button", "Back") + Connections { + target: currentStep - enabled: welcomePage.currentStep.previousUrl !== "" - visible: welcomePage.currentStep.showBackButton - Layout.alignment: Qt.AlignHCenter - onClicked: { - module.source = welcomePage.currentStep.previousUrl + function onProcessed(nextUrl) { + module.source = nextUrl; + headerMessage.text = ""; + headerMessage.visible = false; + if (!module.item.noControls) { + module.item.forceActiveFocus() + } else { + continueButton.forceActiveFocus() + } + } + function onShowMessage(message) { + headerMessage.text = message; + headerMessage.visible = true; + headerMessage.type = Kirigami.MessageType.Information; + } + function onClearError() { + headerMessage.text = ""; + headerMessage.visible = false; } } - - QQC2.Button { - id: continueButton - enabled: welcomePage.currentStep.acceptable - visible: welcomePage.currentStep.showContinueButton - action: welcomePage.currentStep.action + Connections { + target: LoginHelper + function onErrorOccured(message) { + headerMessage.text = message; + headerMessage.visible = message.length > 0; + headerMessage.type = Kirigami.MessageType.Error; + } } } - Connections { - target: currentStep + FormCard.FormDelegateSeparator { + below: continueButton + } - function onProcessed(nextUrl) { - module.source = nextUrl; - } - function onShowMessage(message) { - headerMessage.text = message; - headerMessage.visible = true; - headerMessage.type = Kirigami.MessageType.Information; - } + FormCard.FormButtonDelegate { + id: continueButton + text: root.currentStep.nextAction ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue") + visible: root.currentStep.nextAction + onClicked: root.currentStep.nextAction.trigger() + icon.name: "arrow-right" + enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false + } + + FormCard.FormButtonDelegate { + text: i18nc("@action:button", "Go back") + visible: root.currentStep.previousAction + onClicked: root.currentStep.previousAction.trigger() + icon.name: "arrow-left" + enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false } } + + Component.onCompleted: { + LoginHelper.init() + module.item.forceActiveFocus() + } }