Functional login page

This commit is contained in:
Nicolas Werner 2022-01-24 00:41:55 +01:00
parent aaae72a4f2
commit 4a80fdc951
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
13 changed files with 573 additions and 494 deletions

View file

@ -8,29 +8,131 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import im.nheko 1.0
TextField {
id: input
property alias backgroundColor: backgroundRect.color
ColumnLayout {
id: c
property color backgroundColor: Nheko.colors.base
property alias color: labelC.color
property alias textPadding: input.padding
property alias text: input.text
property alias label: labelC.text
property alias placeholderText: input.placeholderText
property alias font: input.font
property alias echoMode: input.echoMode
property alias selectByMouse: input.selectByMouse
palette: Nheko.colors
color: Nheko.colors.text
signal textEdited
signal accepted
signal editingFinished
function forceActiveFocus() {
input.forceActiveFocus();
}
ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: hover.hovered
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: labelC.contentHeight
Layout.margins: input.padding
Layout.bottomMargin: Nheko.paddingSmall
visible: labelC.text
z: 1
Label {
id: labelC
y: contentHeight + input.padding + Nheko.paddingSmall
enabled: false
palette: Nheko.colors
color: Nheko.colors.text
font.pixelSize: input.font.pixelSize
font.weight: Font.DemiBold
font.letterSpacing: input.font.pixelSize * 0.02
width: parent.width
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
states: State {
name: "focused"
PropertyChanges {
target: labelC
y: 0
}
PropertyChanges {
target: input
opacity: 1
}
}
transitions: Transition {
from: ""
to: "focused"
reversible: true
NumberAnimation {
target: labelC
properties: "y"
duration: 210
easing.type: Easing.InCubic
alwaysRunToEnd: true
}
NumberAnimation {
target: input
properties: "opacity"
duration: 210
easing.type: Easing.InCubic
alwaysRunToEnd: true
}
}
}
}
TextField {
id: input
Layout.fillWidth: true
palette: Nheko.colors
color: labelC.color
opacity: labelC.text ? 0 : 1
onTextEdited: c.textEdited()
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
background: Rectangle {
id: backgroundRect
color: labelC.text ? "transparent" : backgroundColor
}
}
Rectangle {
id: blueBar
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
Layout.fillWidth: true
color: Nheko.colors.highlight
height: 1
width: parent.width
Rectangle {
id: blackBar
anchors.verticalCenter: blueBar.verticalCenter
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
height: parent.height + 1
height: parent.height*2
width: 0
color: Nheko.colors.text
@ -50,11 +152,12 @@ TextField {
to: "focused"
reversible: true
NumberAnimation {
target: blackBar
properties: "width"
duration: 500
easing.type: Easing.InOutQuad
duration: 310
easing.type: Easing.InCubic
alwaysRunToEnd: true
}
@ -64,10 +167,8 @@ TextField {
}
background: Rectangle {
id: backgroundRect
color: Nheko.colors.base
HoverHandler {
id: hover
enabled: c.ToolTip.text
}
}

View file

@ -11,7 +11,6 @@ Popup {
id: quickSwitcher
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
property int textMargin: Math.round(textHeight / 8)
background: null
width: Math.round(parent.width / 2)
@ -34,7 +33,6 @@ Popup {
anchors.fill: parent
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
padding: textMargin
color: Nheko.colors.text
onTextEdited: {
completerPopup.completer.searchString = text;
@ -60,7 +58,7 @@ Popup {
id: completerPopup
x: roomTextInput.x
y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
y: roomTextInput.y + roomTextInput.height
visible: roomTextInput.length > 0
width: parent.width
completerName: "room"

View file

@ -155,6 +155,11 @@ Pane {
}
Shortcut {
sequence: StandardKey.Quit
onActivated: Qt.quit()
}
Shortcut {
sequence: "Ctrl+K"
onActivated: {
@ -366,8 +371,13 @@ Pane {
id: mainWindow
anchors.fill: parent
initialItem: WelcomePage {
//anchors.fill: parent
initialItem: welcomePage
}
Component {
id: welcomePage
WelcomePage {
}
}
@ -378,10 +388,19 @@ Pane {
}
}
Component {
id: loginPage
LoginPage {
}
}
Connections {
function onSwitchToChatPage() {
console.log("AAAA");
mainWindow.replace(chatPage);
mainWindow.replace(null, chatPage);
}
function onSwitchToLoginPage(error) {
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
}
target: MainWindow
}

View file

@ -173,39 +173,37 @@ ApplicationWindow {
}
}
MatrixText {
visible: imagePack.roomid
text: qsTr("State key")
}
MatrixTextField {
id: statekeyField
visible: imagePack.roomid
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("State key")
text: imagePack.statekey
onTextEdited: imagePack.statekey = text
}
MatrixText {
text: qsTr("Packname")
}
MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("Packname")
text: imagePack.packname
onTextEdited: imagePack.packname = text
}
MatrixText {
text: qsTr("Attribution")
}
MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("Attribution")
text: imagePack.attribution
onTextEdited: imagePack.attribution = text
}
MatrixText {
Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Emoji")
}
@ -216,6 +214,9 @@ ApplicationWindow {
}
MatrixText {
Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Sticker")
}
@ -251,27 +252,28 @@ ApplicationWindow {
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: qsTr("Shortcode")
}
MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("Shortcode")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
}
MatrixText {
text: qsTr("Body")
}
MatrixTextField {
id: bodyField
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("Body")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
}
MatrixText {
Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Emoji")
}
@ -282,6 +284,9 @@ ApplicationWindow {
}
MatrixText {
Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Sticker")
}
@ -292,6 +297,9 @@ ApplicationWindow {
}
MatrixText {
Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Remove from pack")
}

View file

@ -188,7 +188,6 @@ ApplicationWindow {
Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
padding: Nheko.paddingMedium
color: Nheko.colors.text
placeholderText: qsTr("Search for public rooms")
onTextChanged: searchTimer.restart()
@ -199,7 +198,6 @@ ApplicationWindow {
Layout.minimumWidth: 0.3 * header.width
Layout.maximumWidth: 0.3 * header.width
padding: Nheko.paddingMedium
color: Nheko.colors.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)

View file

@ -0,0 +1,178 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import QtQuick.Window 2.15
import im.nheko 1.0
import "../components/"
import "../ui/"
import "../"
Item {
id: loginPage
property int maxExpansion: 800
property string error: login.error
Login {
id: login
}
ScrollView {
id: scroll
clip: false
palette: Nheko.colors
ScrollBar.horizontal.visible: false
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: Math.min(loginPage.height, col.implicitHeight)
anchors.margins: Nheko.paddingLarge
contentWidth: availableWidth
ColumnLayout {
id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
height: 128
width: 128
}
RowLayout {
spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField {
id: matrixIdLabel
label: qsTr("Matrix ID")
placeholderText: qsTr("e.g @joe:matrix.org")
onEditingFinished: login.mxid = text
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
Keys.forwardTo: [pwBtn, ssoBtn]
}
Spinner {
height: matrixIdLabel.height/2
Layout.alignment: Qt.AlignBottom
visible: running
running: login.lookingUpHs
foreground: Nheko.colors.mid
}
}
MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error
text: login.mxidError
visible: text
}
MatrixTextField {
id: passwordLabel
Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Your password.")
visible: login.passwordSupported
Keys.forwardTo: [pwBtn, ssoBtn]
}
MatrixTextField {
id: deviceNameLabel
Layout.fillWidth: true
label: qsTr("Device name")
placeholderText: login.initialDeviceName()
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
Keys.forwardTo: [pwBtn, ssoBtn]
}
MatrixTextField {
id: hsLabel
enabled: visible
visible: login.homeserverNeeded
Layout.fillWidth: true
label: qsTr("Homeserver address")
placeholderText: qsTr("server.my:8787")
text: login.homeserver
onEditingFinished: login.homeserver = text
ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
Keys.forwardTo: [pwBtn, ssoBtn]
}
Item {
height: Nheko.avatarSize
Layout.fillWidth: true
Spinner {
height: parent.height
anchors.centerIn: parent
visible: running
running: login.loggingIn
foreground: Nheko.colors.mid
}
}
MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error
text: loginPage.error
visible: text
}
FlatButton {
id: pwBtn
visible: login.passwordSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
function pwLogin() {
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
}
onClicked: pwBtn.pwLogin()
Keys.onEnterPressed: pwBtn.pwLogin()
Keys.onReturnPressed: pwBtn.pwLogin()
Keys.enabled: pwBtn.enabled && login.passwordSupported
}
FlatButton {
id: ssoBtn
visible: login.ssoSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("SSO LOGIN")
function ssoLogin() {
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
}
onClicked: ssoBtn.ssoLogin()
Keys.onEnterPressed: ssoBtn.ssoLogin()
Keys.onReturnPressed: ssoBtn.ssoLogin()
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
}
}
}
ImageButton {
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop()
}
}

View file

@ -6,15 +6,6 @@ import im.nheko 1.0
import "../components/"
ColumnLayout {
FontMetrics {
id: fontMetrics
}
Shortcut {
sequence: StandardKey.Quit
onActivated: Qt.quit()
}
Item {
Layout.fillHeight: true
}
@ -49,27 +40,28 @@ ColumnLayout {
}
RowLayout {
Item {
Layout.fillWidth: true
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
onClicked: {
Item {
Layout.fillWidth: true
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
onClicked: {
}
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
onClicked: {
mainWindow.push(loginPage);
}
}
Item {
Layout.fillWidth: true
}
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
onClicked: {
}
}
Item {
Layout.fillWidth: true
}
}
Item {
Layout.fillHeight: true
}

View file

@ -111,6 +111,7 @@
<file>qml/NotificationWarning.qml</file>
<file>qml/pages/UserSettingsPage.qml</file>
<file>qml/pages/WelcomePage.qml</file>
<file>qml/pages/LoginPage.qml</file>
<file>qml/components/AdaptiveLayout.qml</file>
<file>qml/components/AdaptiveLayoutElement.qml</file>
<file>qml/components/AvatarListTile.qml</file>

View file

@ -5,11 +5,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QDesktopServices>
#include <QFontMetrics>
#include <QLabel>
#include <QPainter>
#include <QStyleOption>
#include <QtMath>
#include <mtx/identifiers.hpp>
#include <mtx/requests.hpp>
@ -18,247 +13,94 @@
#include "Config.h"
#include "Logging.h"
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "SSOHandler.h"
#include "UserSettingsPage.h"
#include "ui/FlatButton.h"
#include "ui/LoadingIndicator.h"
#include "ui/OverlayModal.h"
#include "ui/RaisedButton.h"
#include "ui/TextField.h"
Q_DECLARE_METATYPE(LoginPage::LoginMethod)
using namespace mtx::identifiers;
LoginPage::LoginPage(QWidget *parent)
: QWidget(parent)
LoginPage::LoginPage(QObject *parent)
: QObject(parent)
, inferredServerAddress_()
{
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
top_layout_ = new QVBoxLayout();
top_bar_layout_ = new QHBoxLayout();
top_bar_layout_->setSpacing(0);
top_bar_layout_->setContentsMargins(0, 0, 0, 0);
back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30));
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
top_bar_layout_->addStretch(1);
QIcon icon;
icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
back_button_->setIcon(icon);
back_button_->setIconSize(QSize(32, 32));
QIcon logo;
logo.addFile(QStringLiteral(":/logos/login.png"));
logo_ = new QLabel(this);
logo_->setPixmap(logo.pixmap(128));
logo_layout_ = new QHBoxLayout();
logo_layout_->setContentsMargins(0, 0, 0, 20);
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
form_wrapper_ = new QHBoxLayout();
form_widget_ = new QWidget();
form_widget_->setMinimumSize(QSize(350, 200));
form_layout_ = new QVBoxLayout();
form_layout_->setSpacing(20);
form_layout_->setContentsMargins(0, 0, 0, 30);
form_widget_->setLayout(form_layout_);
form_wrapper_->addStretch(1);
form_wrapper_->addWidget(form_widget_);
form_wrapper_->addStretch(1);
matrixid_input_ = new TextField(this);
matrixid_input_->setLabel(tr("Matrix ID"));
matrixid_input_->setRegexp(QRegularExpression(QStringLiteral("@.+?:.{3,}")));
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
matrixid_input_->setToolTip(
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
"id you need to include your server name after a :.\nYou can also put your homeserver "
"address there, if your server doesn't support .well-known lookup.\nExample: "
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
"field to enter the server manually."));
spinner_ = new LoadingIndicator(this);
spinner_->setFixedHeight(40);
spinner_->setFixedWidth(40);
spinner_->hide();
errorIcon_ = new QLabel(this);
errorIcon_->setPixmap(QPixmap(QStringLiteral(":/icons/icons/error.png")));
errorIcon_->hide();
matrixidLayout_ = new QHBoxLayout();
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
QFont font;
error_matrixid_label_ = new QLabel(this);
error_matrixid_label_->setFont(font);
error_matrixid_label_->setWordWrap(true);
password_input_ = new TextField(this);
password_input_->setLabel(tr("Password"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Your password."));
deviceName_ = new TextField(this);
deviceName_->setLabel(tr("Device name"));
deviceName_->setToolTip(
tr("A name for this device, which will be shown to others, when verifying your devices. "
"If none is provided a default is used."));
serverInput_ = new TextField(this);
serverInput_->setLabel(tr("Homeserver address"));
serverInput_->setPlaceholderText(tr("server.my:8787"));
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
"client API.\nExample: https://server.my:8787"));
serverInput_->hide();
serverLayout_ = new QHBoxLayout();
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
form_layout_->addLayout(matrixidLayout_);
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
form_layout_->addWidget(password_input_);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
form_layout_->addLayout(serverLayout_);
error_matrixid_label_->hide();
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(20);
button_layout_->setContentsMargins(0, 0, 0, 30);
login_button_ = new RaisedButton(tr("LOGIN"), this);
login_button_->setMinimumSize(150, 65);
login_button_->setFontSize(20);
login_button_->setCornerRadius(3);
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
sso_login_button_->setMinimumSize(150, 65);
sso_login_button_->setFontSize(20);
sso_login_button_->setCornerRadius(3);
sso_login_button_->setVisible(false);
button_layout_->addStretch(1);
button_layout_->addWidget(login_button_);
button_layout_->addWidget(sso_login_button_);
button_layout_->addStretch(1);
error_label_ = new QLabel(this);
error_label_->setFont(font);
error_label_->setWordWrap(true);
top_layout_->addLayout(top_bar_layout_);
top_layout_->addStretch(1);
top_layout_->addLayout(logo_layout_);
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
top_layout_->addStretch(1);
setLayout(top_layout_);
[[maybe_unused]] static auto ignored =
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
connect(
this,
&LoginPage::loginOk,
this,
[this](const mtx::responses::Login &res) {
loggingIn_ = false;
emit loggingInChanged();
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
});
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(LoginMethod::SSO);
});
connect(this,
&LoginPage::showErrorMessage,
this,
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
Qt::QueuedConnection);
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
http::client()->set_user(res.user_id);
MainWindow::instance()->showChatPage();
},
Qt::QueuedConnection);
}
void
LoginPage::showError(const QString &msg)
{
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
error_label_->setText(msg);
loggingIn_ = false;
emit loggingInChanged();
error_ = msg;
emit errorOccurred();
}
void
LoginPage::showError(QLabel *label, const QString &msg)
LoginPage::setHomeserver(QString hs)
{
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
label->setFixedHeight((int)qCeil(width / 200.0) * height);
label->setText(msg);
if (hs != homeserver_) {
homeserver_ = hs;
homeserverValid_ = false;
emit homeserverChanged();
http::client()->set_server(hs.toStdString());
checkHomeserverVersion();
}
}
void
LoginPage::onMatrixIdEntered()
{
error_label_->setText(QLatin1String(""));
clearErrors();
homeserverValid_ = false;
emit homeserverChanged();
User user;
try {
user = parse<User>(mxid_.toStdString());
} catch (const std::exception &) {
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
emit mxidErrorChanged();
return;
}
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
if (user.hostname().empty() || user.localpart().empty()) {
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
emit mxidErrorChanged();
return;
} else {
error_matrixid_label_->setText(QLatin1String(""));
error_matrixid_label_->hide();
nhlog::net()->debug("hostname: {}", user.hostname());
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
QString homeServer = QString::fromStdString(user.hostname());
if (homeServer != inferredServerAddress_) {
serverInput_->hide();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
if (serverInput_->isVisible()) {
matrixidLayout_->removeWidget(spinner_);
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
} else {
serverLayout_->removeWidget(spinner_);
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
}
inferredServerAddress_ = homeServer;
serverInput_->setText(homeServer);
if (user.hostname() != inferredServerAddress_.toStdString()) {
homeserverNeeded_ = false;
lookingUpHs_ = true;
emit lookingUpHsChanged();
http::client()->set_server(user.hostname());
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
homeserver_ = QString::fromStdString(user.hostname());
emit homeserverChanged();
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
@ -286,6 +128,7 @@ LoginPage::onMatrixIdEntered()
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
emit homeserverChanged();
checkHomeserverVersion();
});
}
@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered()
void
LoginPage::checkHomeserverVersion()
{
clearErrors();
try {
User user = parse<User>(mxid_.toStdString());
} catch (const std::exception &) {
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
emit mxidErrorChanged();
return;
}
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
@ -332,93 +185,64 @@ LoginPage::checkHomeserverVersion()
});
}
void
LoginPage::onServerAddressEntered()
{
error_label_->setText(QLatin1String(""));
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
http::client()->set_server(serverInput_->text().toStdString());
checkHomeserverVersion();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
}
void
LoginPage::versionError(const QString &error)
{
showError(error_label_, error);
serverInput_->show();
showError(error);
spinner_->stop();
serverLayout_->removeWidget(spinner_);
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
errorIcon_->show();
matrixidLayout_->removeWidget(spinner_);
homeserverNeeded_ = true;
lookingUpHs_ = false;
homeserverValid_ = false;
emit lookingUpHsChanged();
emit versionLookedUp();
}
void
LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
{
passwordSupported = passwordSupported_;
ssoSupported = ssoSupported_;
passwordSupported_ = passwordSupported;
ssoSupported_ = ssoSupported;
serverLayout_->removeWidget(spinner_);
matrixidLayout_->removeWidget(spinner_);
spinner_->stop();
password_input_->setVisible(passwordSupported);
password_input_->setEnabled(passwordSupported);
sso_login_button_->setVisible(ssoSupported);
login_button_->setVisible(passwordSupported);
if (serverInput_->isVisible())
serverInput_->hide();
lookingUpHs_ = false;
homeserverValid_ = true;
emit homeserverChanged();
emit lookingUpHsChanged();
emit versionLookedUp();
}
void
LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
QString userid,
QString password,
QString deviceName)
{
error_label_->setText(QLatin1String(""));
clearErrors();
User user;
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText(QLatin1String(""));
error_matrixid_label_->hide();
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
user = parse<User>(userid.toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
emit mxidErrorChanged();
return;
}
if (loginMethod == LoginMethod::Password) {
if (password_input_->text().isEmpty())
return showError(error_label_, tr("Empty password"));
if (password.isEmpty())
return showError(tr("Empty password"));
http::client()->login(
user.localpart(),
password_input_->text().toStdString(),
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
: deviceName_->text().toStdString(),
password.toStdString(),
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(),
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
auto error = err->matrix_error.error;
if (error.empty())
error = err->parse_error;
showErrorMessage(error_label_, QString::fromStdString(error));
emit errorOccurred();
showError(QString::fromStdString(error));
return;
}
@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
});
} else {
auto sso = new SSOHandler();
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
mtx::requests::Login req{};
req.token = token;
req.type = mtx::user_interactive::auth_types::token;
req.device_id = deviceName_->text().trimmed().isEmpty()
? initialDeviceName()
: deviceName_->text().toStdString();
http::client()->login(
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
showErrorMessage(error_label_,
QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
}
connect(
sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) {
mtx::requests::Login req{};
req.token = token;
req.type = mtx::user_interactive::auth_types::token;
req.device_id =
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString();
http::client()->login(
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
showError(QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
}
if (res.well_known) {
http::client()->set_server(res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
if (res.well_known) {
http::client()->set_server(res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
emit loginOk(res);
});
sso->deleteLater();
});
emit loginOk(res);
});
sso->deleteLater();
});
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
showErrorMessage(error_label_, tr("SSO login failed"));
showError(tr("SSO login failed"));
emit errorOccurred();
sso->deleteLater();
});
@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
}
emit loggingIn();
}
void
LoginPage::reset()
{
matrixid_input_->clear();
password_input_->clear();
password_input_->show();
serverInput_->clear();
spinner_->stop();
errorIcon_->hide();
serverLayout_->removeWidget(spinner_);
serverLayout_->removeWidget(errorIcon_);
matrixidLayout_->removeWidget(spinner_);
inferredServerAddress_.clear();
}
void
LoginPage::onBackButtonClicked()
{
emit backButtonClicked();
}
void
LoginPage::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
loggingIn_ = true;
emit loggingInChanged();
}

View file

@ -6,16 +6,7 @@
#pragma once
#include <QWidget>
class FlatButton;
class LoadingIndicator;
class OverlayModal;
class RaisedButton;
class TextField;
class QLabel;
class QVBoxLayout;
class QHBoxLayout;
#include <QObject>
namespace mtx {
namespace responses {
@ -23,24 +14,61 @@ struct Login;
}
}
class LoginPage : public QWidget
class LoginPage : public QObject
{
Q_OBJECT
Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged)
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(QString mxidError READ mxidError NOTIFY mxidErrorChanged)
Q_PROPERTY(QString error READ error NOTIFY errorOccurred)
Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
Q_PROPERTY(bool homeserverValid READ homeserverValid NOTIFY lookingUpHsChanged)
Q_PROPERTY(bool loggingIn READ loggingIn NOTIFY loggingInChanged)
Q_PROPERTY(bool passwordSupported READ passwordSupported NOTIFY versionLookedUp)
Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp)
Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp)
public:
enum class LoginMethod
{
Password,
SSO,
};
Q_ENUM(LoginMethod)
LoginPage(QWidget *parent = nullptr);
LoginPage(QObject *parent = nullptr);
void reset();
Q_INVOKABLE QString initialDeviceName() const
{
return QString::fromStdString(initialDeviceName_());
}
bool lookingUpHs() const { return lookingUpHs_; }
bool loggingIn() const { return loggingIn_; }
bool passwordSupported() const { return passwordSupported_; }
bool ssoSupported() const { return ssoSupported_; }
bool homeserverNeeded() const { return homeserverNeeded_; }
bool homeserverValid() const { return homeserverValid_; }
QString homeserver() { return homeserver_; }
QString mxid() { return mxid_; }
QString error() { return error_; }
QString mxidError() { return mxidError_; }
void setHomeserver(QString hs);
void setMxid(QString id)
{
if (id != mxid_) {
mxid_ = id;
emit matrixIdChanged();
onMatrixIdEntered();
}
}
signals:
void backButtonClicked();
void loggingIn();
void loggingInChanged();
void errorOccurred();
//! Used to trigger the corresponding slot outside of the main thread.
@ -48,28 +76,26 @@ signals:
void versionOkCb(bool passwordSupported, bool ssoSupported);
void loginOk(const mtx::responses::Login &res);
void showErrorMessage(QLabel *label, const QString &msg);
protected:
void paintEvent(QPaintEvent *event) override;
void onServerAddressEntered();
void matrixIdChanged();
void homeserverChanged();
void mxidErrorChanged();
void lookingUpHsChanged();
void versionLookedUp();
void versionLookupFinished();
public slots:
// Displays errors produced during the login.
void showError(const QString &msg);
void showError(QLabel *label, const QString &msg);
private slots:
// Callback for the back button.
void onBackButtonClicked();
// Callback for the login button.
void onLoginButtonClicked(LoginMethod loginMethod);
// Callback for probing the server found in the mxid
void onMatrixIdEntered();
// Callback for probing the manually entered server
void onServerAddressEntered();
void onLoginButtonClicked(LoginMethod loginMethod,
QString userid,
QString password,
QString deviceName);
// Callback for errors produced during server probing
void versionError(const QString &error_message);
@ -78,7 +104,8 @@ private slots:
private:
void checkHomeserverVersion();
std::string initialDeviceName()
void onMatrixIdEntered();
std::string initialDeviceName_() const
{
#if defined(Q_OS_MAC)
return "Nheko on macOS";
@ -92,34 +119,27 @@ private:
return "Nheko";
#endif
}
void clearErrors()
{
error_.clear();
mxidError_.clear();
emit errorOccurred();
emit mxidErrorChanged();
}
QVBoxLayout *top_layout_;
QHBoxLayout *top_bar_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
QLabel *error_matrixid_label_;
QHBoxLayout *serverLayout_;
QHBoxLayout *matrixidLayout_;
LoadingIndicator *spinner_;
QLabel *errorIcon_;
QString inferredServerAddress_;
FlatButton *back_button_;
RaisedButton *login_button_, *sso_login_button_;
QString mxid_;
QString homeserver_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
QString mxidError_;
QString error_;
TextField *matrixid_input_;
TextField *password_input_;
TextField *deviceName_;
TextField *serverInput_;
bool passwordSupported = true;
bool ssoSupported = false;
bool passwordSupported_ = true;
bool ssoSupported_ = false;
bool lookingUpHs_ = false;
bool loggingIn_ = false;
bool homeserverNeeded_ = false;
bool homeserverValid_ = false;
};

View file

@ -87,7 +87,6 @@ MainWindow::MainWindow(QWindow *parent)
setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
// modal_ = new OverlayModal(this);
// QFont font;
// font.setStyleStrategy(QFont::PreferAntialias);
// setFont(font);
@ -95,31 +94,19 @@ MainWindow::MainWindow(QWindow *parent)
trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
// welcome_page_ = new WelcomePage(this);
// login_page_ = new LoginPage(this);
// register_page_ = new RegisterPage(this);
//// Initialize sliding widget manager.
// connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
// connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
// connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
// connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
// connect(register_page_, &RegisterPage::registering, this,
// &MainWindow::showOverlayProgressBar); connect(login_page_, &LoginPage::errorOccurred, this,
// [this]() { removeOverlayProgressBar(); }); connect(
// register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar();
// });
// connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
// connect(
// chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
login_page_->showError(msg);
showLoginPage();
switchToLoginPage(msg);
});
connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
@ -210,6 +197,7 @@ MainWindow::registerQmlTypes()
qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
qmlRegisterUncreatableType<DeviceVerificationFlow>(
"im.nheko",
1,
@ -375,9 +363,7 @@ MainWindow::removeOverlayProgressBar()
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this, timer]() {
timer->deleteLater();
});
connect(timer, &QTimer::timeout, this, [this, timer]() { timer->deleteLater(); });
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
// QTimer::singleShot(0, this, [this]() {
@ -404,7 +390,6 @@ MainWindow::showChatPage()
showOverlayProgressBar();
// login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token);
connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
@ -463,8 +448,7 @@ MainWindow::hasActiveUser()
void
MainWindow::showOverlayProgressBar()
{
}
{}
void
MainWindow::openCreateRoomDialog(
@ -485,8 +469,7 @@ MainWindow::showTransparentOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
void
MainWindow::showSolidOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
{
}
{}
bool
MainWindow::hasActiveDialogs() const
@ -503,8 +486,7 @@ MainWindow::pageSupportsTray() const
void
MainWindow::hideOverlay()
{
}
{}
inline void
MainWindow::showDialog(QWidget *dialog)
@ -520,11 +502,6 @@ MainWindow::showWelcomePage()
removeOverlayProgressBar();
}
void
MainWindow::showLoginPage()
{
}
void
MainWindow::showRegisterPage()
{}

View file

@ -20,7 +20,6 @@
class ChatPage;
class RegisterPage;
class LoginPage;
class WelcomePage;
class LoadingIndicator;
@ -65,6 +64,9 @@ public:
MxcImageProvider *imageProvider() { return imgProvider; }
//! Show the chat page and start communicating with the given access token.
void showChatPage();
protected:
void closeEvent(QCloseEvent *event);
bool event(QEvent *event) override;
@ -76,15 +78,9 @@ private slots:
//! Show the welcome page in the main window.
void showWelcomePage();
//! Show the login page in the main window.
void showLoginPage();
//! Show the register page in the main window.
void showRegisterPage();
//! Show the chat page and start communicating with the given access token.
void showChatPage();
void showOverlayProgressBar();
void removeOverlayProgressBar();
@ -96,6 +92,7 @@ signals:
void switchToChatPage();
void switchToWelcomePage();
void switchToLoginPage(QString error);
private:
void showDialog(QWidget *dialog);
@ -112,8 +109,6 @@ private:
//! The initial welcome screen.
WelcomePage *welcome_page_;
//! The login screen.
LoginPage *login_page_;
//! The register page.
RegisterPage *register_page_;
//! The main chat area.

View file

@ -110,7 +110,7 @@ public slots:
RoomlistModel *rooms() { return rooms_; }
private:
bool isInitialSync_ = true;
bool isInitialSync_ = true;
RoomlistModel *rooms_ = nullptr;
CommunitiesModel *communities_ = nullptr;