Port login to FormCard

This commit is contained in:
Tobias Fella
2023-08-27 09:54:12 +00:00
parent 96582a12bc
commit dbc10685f0
10 changed files with 205 additions and 178 deletions

View File

@@ -87,7 +87,11 @@ void Login::init()
Q_EMIT isLoggingInChanged(); Q_EMIT isLoggingInChanged();
}); });
connect(m_connection, &Connection::loginError, this, [this](QString error, const QString &) { 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; m_isLoggingIn = false;
Q_EMIT isLoggingInChanged(); Q_EMIT isLoggingInChanged();
}); });
@@ -133,6 +137,7 @@ QString Login::password() const
void Login::setPassword(const QString &password) void Login::setPassword(const QString &password)
{ {
setInvalidPassword(false);
m_password = password; m_password = password;
Q_EMIT passwordChanged(); Q_EMIT passwordChanged();
} }
@@ -199,4 +204,15 @@ bool Login::isLoggedIn() const
return m_isLoggedIn; 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" #include "moc_login.cpp"

View File

@@ -73,6 +73,11 @@ class Login : public QObject
*/ */
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged) 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: public:
explicit Login(QObject *parent = nullptr); explicit Login(QObject *parent = nullptr);
@@ -100,6 +105,9 @@ public:
bool isLoggedIn() const; bool isLoggedIn() const;
bool isInvalidPassword() const;
void setInvalidPassword(bool invalid);
Q_INVOKABLE void login(); Q_INVOKABLE void login();
Q_INVOKABLE void loginWithSso(); Q_INVOKABLE void loginWithSso();
@@ -116,6 +124,7 @@ Q_SIGNALS:
void testingChanged(); void testingChanged();
void isLoggingInChanged(); void isLoggingInChanged();
void isLoggedInChanged(); void isLoggedInChanged();
void isInvalidPasswordChanged();
private: private:
void setHomeserverReachable(bool reachable); void setHomeserverReachable(bool reachable);
@@ -131,4 +140,5 @@ private:
bool m_testing = false; bool m_testing = false;
bool m_isLoggingIn = false; bool m_isLoggingIn = false;
bool m_isLoggedIn = false; bool m_isLoggedIn = false;
bool m_invalidPassword = false;
}; };

View File

@@ -6,16 +6,19 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.20 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.LoadingPlaceholder { LoginStep {
property var showContinueButton: false id: root
property var showBackButton: false FormCard.FormTextDelegate {
QQC2.Label {
text: i18n("Please wait. This might take a little while.") text: i18n("Please wait. This might take a little while.")
} }
FormCard.AbstractFormDelegate {
contentItem: QQC2.BusyIndicator {}
background: null
}
Connections { Connections {
target: Controller target: Controller

View File

@@ -7,43 +7,34 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
LoginStep { LoginStep {
id: login id: root
showContinueButton: true onActiveFocusChanged: if (activeFocus) matrixIdField.forceActiveFocus()
showBackButton: false
title: i18nc("@title", "Login")
message: i18n("Enter your Matrix ID")
Component.onCompleted: { Component.onCompleted: {
LoginHelper.matrixId = "" LoginHelper.matrixId = ""
} }
Kirigami.FormLayout { FormCard.FormTextFieldDelegate {
QQC2.TextField { id: matrixIdField
id: matrixIdField label: i18n("Matrix ID:")
Kirigami.FormData.label: i18n("Matrix ID:") placeholderText: "@user:example.org"
placeholderText: "@user:matrix.org" Accessible.name: i18n("Matrix ID")
Accessible.name: i18n("Matrix ID") onTextChanged: {
onTextChanged: { LoginHelper.matrixId = text
LoginHelper.matrixId = text }
}
Component.onCompleted: { Keys.onReturnPressed: {
matrixIdField.forceActiveFocus() root.action.trigger()
}
Keys.onReturnPressed: {
login.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") text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue")
onTriggered: { onTriggered: {
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) { if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
@@ -56,4 +47,10 @@ LoginStep {
} }
enabled: LoginHelper.homeserverReachable enabled: LoginHelper.homeserverReachable
} }
// TODO: enable once we have registration
// previousAction: Kirigami.Action {
// onTriggered: {
// root.processed("qrc:/Login.qml")
// }
// }
} }

View File

@@ -4,28 +4,26 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
LoginStep { LoginStep {
id: loginMethod id: root
title: i18n("Login Methods") onActiveFocusChanged: if (activeFocus) loginPasswordButton.forceActiveFocus()
Layout.alignment: Qt.AlignHCenter FormCard.FormButtonDelegate {
id: loginPasswordButton
QQC2.Button { text: i18nc("@action:button", "Login with password")
Layout.alignment: Qt.AlignHCenter
text: i18n("Login with password")
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
onClicked: processed("qrc:/Password.qml") onClicked: processed("qrc:/Password.qml")
} }
QQC2.Button { FormCard.FormButtonDelegate {
Layout.alignment: Qt.AlignHCenter id: loginSsoButton
text: i18n("Login with single sign-on") text: i18nc("@action:button", "Login with single sign-on")
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
onClicked: processed("qrc:/Sso.qml") onClicked: processed("qrc:/Sso.qml")
} }
} }

View File

@@ -6,25 +6,22 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
LoginStep { LoginStep {
id: loginRegister id: root
Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true
QQC2.Button { FormCard.FormButtonDelegate {
Layout.alignment: Qt.AlignHCenter text: i18nc("@action:button", "Login")
text: i18n("Login") onClicked: root.processed("qrc:/Login.qml")
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
onClicked: processed("qrc:/Login.qml")
} }
QQC2.Button { FormCard.FormButtonDelegate {
Layout.alignment: Qt.AlignHCenter text: i18nc("@action:button", "Register")
text: i18n("Register") onClicked: root.processed("qrc:/Homeserver.qml")
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
onClicked: processed("qrc:/Homeserver.qml")
} }
} }

View File

@@ -7,21 +7,25 @@ import QtQuick.Layouts 1.14
/// Step for the login/registration flow /// Step for the login/registration flow
ColumnLayout { ColumnLayout {
id: root
property string title: i18n("Welcome") /// Set to true if the login step does not have any controls. This will ensure that the focus remains on the "continue" button
property string message: i18n("Welcome") property bool noControls: false
property bool showContinueButton: false
property bool showBackButton: false
property bool acceptable: false
property string previousUrl: ""
/// Process this module, this is called by the continue button. /// Process this module, this is called by the continue button.
/// Should call \sa processed when it finish successfully. /// 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. /// Called when switching to the next step.
signal processed(url nextUrl) signal processed(url nextUrl)
/// Show a message in a banner at the top of the page.
signal showMessage(string message) signal showMessage(string message)
signal clearError()
} }

View File

@@ -6,25 +6,12 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
LoginStep { LoginStep {
id: password id: root
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();
}
}
Connections { Connections {
target: LoginHelper target: LoginHelper
@@ -33,20 +20,32 @@ LoginStep {
} }
} }
Kirigami.FormLayout { onActiveFocusChanged: if(activeFocus) passwordField.forceActiveFocus()
Kirigami.PasswordField {
id: passwordField
onTextChanged: LoginHelper.password = text
enabled: !LoginHelper.isLoggingIn
Accessible.name: i18n("Password")
Component.onCompleted: { FormCard.FormTextFieldDelegate {
passwordField.forceActiveFocus() id: passwordField
}
Keys.onReturnPressed: { label: i18n("Password:")
password.action.trigger() 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")
}
} }

View File

@@ -6,47 +6,38 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.12 as Kirigami import org.kde.kirigami 2.12 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
LoginStep { LoginStep {
id: root id: root
title: i18nc("@title", "Login") noControls: true
message: i18n("Login with single sign-on")
Kirigami.FormLayout { Component.onCompleted: LoginHelper.loginWithSso()
Connections {
target: LoginHelper Connections {
function onSsoUrlChanged() { target: LoginHelper
UrlHelper.openUrl(LoginHelper.ssoUrl) function onSsoUrlChanged() {
root.showMessage(i18n("Complete the authentication steps in your browser")) UrlHelper.openUrl(LoginHelper.ssoUrl)
loginButton.enabled = true
loginButton.text = i18n("Login")
}
function onConnected() {
processed("qrc:/Loading.qml")
}
} }
RowLayout { function onConnected() {
QQC2.Button { processed("qrc:/Loading.qml")
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()
}
} }
} }
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)
}
} }

View File

@@ -6,15 +6,16 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.ScrollablePage { FormCard.FormCardPage {
id: welcomePage id: root
property alias currentStep: module.item property alias currentStep: module.item
title: module.item.title ?? i18n("Welcome") title: i18n("Welcome")
header: QQC2.Control { header: QQC2.Control {
contentItem: Kirigami.InlineMessage { contentItem: Kirigami.InlineMessage {
@@ -25,79 +26,90 @@ Kirigami.ScrollablePage {
} }
} }
Component.onCompleted: LoginHelper.init() FormCard.FormCard {
id: contentCard
Connections { FormCard.AbstractFormDelegate {
target: LoginHelper contentItem: Kirigami.Icon {
function onErrorOccured(message) { source: "org.kde.neochat"
headerMessage.text = message; Layout.fillWidth: true
headerMessage.visible = true; Layout.preferredHeight: Kirigami.Units.gridUnit * 16
headerMessage.type = Kirigami.MessageType.Error; }
background: Item {}
onActiveFocusChanged: if (activeFocus) module.item.forceActiveFocus()
} }
}
Connections { FormCard.FormTextDelegate {
target: Controller id: welcomeMessage
function onInitiated() { text: AccountRegistry.accountCount > 0 ? i18n("Log in to a different account.") : i18n("Welcome to NeoChat! Continue by logging in.")
pageStack.layers.pop();
} }
}
ColumnLayout { FormCard.FormDelegateSeparator {
Kirigami.Icon { above: welcomeMessage
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")
} }
Loader { Loader {
id: module id: module
Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true
source: "qrc:/Login.qml" source: "qrc:/Login.qml"
onSourceChanged: {
headerMessage.visible = false
headerMessage.text = ""
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
QQC2.Button { Connections {
text: i18nc("@action:button", "Back") target: currentStep
enabled: welcomePage.currentStep.previousUrl !== "" function onProcessed(nextUrl) {
visible: welcomePage.currentStep.showBackButton module.source = nextUrl;
Layout.alignment: Qt.AlignHCenter headerMessage.text = "";
onClicked: { headerMessage.visible = false;
module.source = welcomePage.currentStep.previousUrl 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;
} }
} }
Connections {
QQC2.Button { target: LoginHelper
id: continueButton function onErrorOccured(message) {
enabled: welcomePage.currentStep.acceptable headerMessage.text = message;
visible: welcomePage.currentStep.showContinueButton headerMessage.visible = message.length > 0;
action: welcomePage.currentStep.action headerMessage.type = Kirigami.MessageType.Error;
}
} }
} }
Connections { FormCard.FormDelegateSeparator {
target: currentStep below: continueButton
}
function onProcessed(nextUrl) { FormCard.FormButtonDelegate {
module.source = nextUrl; id: continueButton
} text: root.currentStep.nextAction ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
function onShowMessage(message) { visible: root.currentStep.nextAction
headerMessage.text = message; onClicked: root.currentStep.nextAction.trigger()
headerMessage.visible = true; icon.name: "arrow-right"
headerMessage.type = Kirigami.MessageType.Information; 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()
}
} }