diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index af077124..655d53f1 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -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 } - } diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index 174951a0..6d217c72 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -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" diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 004169e1..f3976cc0 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -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 } diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 9c46a490..eb420fce 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -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") } diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index 99da63bb..36c29a0b 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -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) diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml new file mode 100644 index 00000000..0beb2bdc --- /dev/null +++ b/resources/qml/pages/LoginPage.qml @@ -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() + } +} diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml index d95b6104..43050d8e 100644 --- a/resources/qml/pages/WelcomePage.qml +++ b/resources/qml/pages/WelcomePage.qml @@ -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 } diff --git a/resources/res.qrc b/resources/res.qrc index 0222619b..efc0e74a 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -111,6 +111,7 @@ qml/NotificationWarning.qml qml/pages/UserSettingsPage.qml qml/pages/WelcomePage.qml + qml/pages/LoginPage.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 4c28f364..cfc600ae 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -5,11 +5,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include -#include -#include -#include -#include -#include #include #include @@ -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"); - - 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"); 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(&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(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(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(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(matrixid_input_->text().toStdString()); + user = parse(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(); } diff --git a/src/LoginPage.h b/src/LoginPage.h index fbfd8710..a613bb48 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -6,16 +6,7 @@ #pragma once -#include - -class FlatButton; -class LoadingIndicator; -class OverlayModal; -class RaisedButton; -class TextField; -class QLabel; -class QVBoxLayout; -class QHBoxLayout; +#include 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; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 03a99b0f..3efdc23f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -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("im.nheko", 1, 0, "MxcAnimatedImage"); qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType("im.nheko", 1, 0, "Login"); qmlRegisterUncreatableType( "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) void MainWindow::showSolidOverlayModal(QWidget *, QFlags) -{ -} +{} 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() {} diff --git a/src/MainWindow.h b/src/MainWindow.h index ea919f4d..5a351893 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -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. diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 9419f224..13ab5dbb 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -110,7 +110,7 @@ public slots: RoomlistModel *rooms() { return rooms_; } private: - bool isInitialSync_ = true; + bool isInitialSync_ = true; RoomlistModel *rooms_ = nullptr; CommunitiesModel *communities_ = nullptr;