diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2026-02-04 11:34:38 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2026-02-04 15:24:48 +0100 |
| commit | 23cc4be5bc02328fe73ccbd67493dd71ef2e2b8f (patch) | |
| tree | b9e1a7a416f004d96671c0abf597524946b9dda3 | |
| parent | 3b9973552ec613b37fb511f3a0ae24dacb1e4366 (diff) | |
Add accounts and login pages
| -rw-r--r-- | Accounts.qml | 50 | ||||
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | ChatView.qml | 5 | ||||
| -rw-r--r-- | ContactList.qml | 4 | ||||
| -rw-r--r-- | Login.qml | 101 | ||||
| -rw-r--r-- | login.cpp | 124 | ||||
| -rw-r--r-- | login.h | 30 | ||||
| -rw-r--r-- | yc.cpp | 15 | ||||
| -rw-r--r-- | yc.h | 4 |
9 files changed, 334 insertions, 3 deletions
diff --git a/Accounts.qml b/Accounts.qml new file mode 100644 index 0000000..dc91e66 --- /dev/null +++ b/Accounts.qml @@ -0,0 +1,50 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 +import org.yachat.app 1.0 + +Page +{ + header: CustToolbar + { + id: header + title: qsTr("Accounts") + + ToolButton + { + text: qsTr("+") + anchors.right: parent.right + onClicked: stack.push("qrc:/org/yachat/app/Login.qml", {}) + } + } + + ListView + { + ListModel + { + id: acmodel + } + + Component.onCompleted: + { + var accounts = Yc.accounts() + + for (var i = 0; i < accounts.length; i++) + acmodel.append({"jid": accounts[i]}) + } + + width: parent.width + anchors.fill: parent + anchors.horizontalCenter: parent.horizontalCenter + model: acmodel + delegate: ItemDelegate + { + required property var model + + width: parent.width + text: model.jid + anchors.horizontalCenter: parent.horizontalCenter + } + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f3bcb9..734046d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ qt_add_executable(${PROJECT_NAME} direction.h jiddb.cpp jiddb.h + login.cpp + login.h main.cpp omemo_db.cpp omemo_db.h @@ -39,10 +41,12 @@ qt_add_qml_module(${PROJECT_NAME} VERSION 1.0 DEPENDENCIES QtQuick QML_FILES + Accounts.qml ChatView.qml ContactList.qml CustToolbar.qml EncryptionPopup.qml + Login.qml Main.qml ) diff --git a/ChatView.qml b/ChatView.qml index 3c5706a..f06d4f7 100644 --- a/ChatView.qml +++ b/ChatView.qml @@ -28,6 +28,11 @@ Page wrapMode: TextArea.Wrap Layout.fillWidth: true placeholderText: qsTr("Compose message") + + Component.onCompleted: + { + textarea.forceActiveFocus() + } } Button diff --git a/ContactList.qml b/ContactList.qml index 0eee13b..574bb7e 100644 --- a/ContactList.qml +++ b/ContactList.qml @@ -14,6 +14,7 @@ Page { text: qsTr("+") anchors.left: parent.left + onClicked: stack.push("qrc:/org/yachat/app/Accounts.qml", {}) } } @@ -44,11 +45,10 @@ Page model: cmodel delegate: ItemDelegate { - required property int index required property var model width: parent.width - text: "<b>" + model.jid + "</b>" + text: model.to + " <i>(" + model.jid + "</i>)" anchors.horizontalCenter: parent.horizontalCenter onClicked: stack.push("qrc:/org/yachat/app/ChatView.qml", { diff --git a/Login.qml b/Login.qml new file mode 100644 index 0000000..24354f6 --- /dev/null +++ b/Login.qml @@ -0,0 +1,101 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 +import org.yachat.app 1.0 + +Page +{ + header: CustToolbar + { + title: qsTr("Login") + } + + Login + { + id: login + } + + ColumnLayout + { + Row + { + Layout.fillWidth: true + + Text + { + text: qsTr("Username: ") + } + + TextField + { + id: username + placeholderText: qsTr("Enter username") + + Component.onCompleted: + { + username.forceActiveFocus() + } + } + } + + Row + { + Layout.fillWidth: true + + Text + { + text: qsTr("Password: ") + } + + TextField + { + id: password + placeholderText: qsTr("Enter password") + echoMode: TextInput.Password + } + } + + Button + { + text: qsTr("Login") + enabled: username.text && password.text + onClicked: + { + login.start(username.text, password.text) + } + + Connections + { + target: login + function onAuthSuccess(client) + { + Yc.addAccount(client) + Yc.storeAccount(client) + } + + function onClose() + { + stack.pop() + } + } + } + + Row + { + Text + { + id: error + + Connections + { + target: login + function onError(msg) + { + error.text = msg + } + } + } + } + } +} diff --git a/login.cpp b/login.cpp new file mode 100644 index 0000000..ac16d59 --- /dev/null +++ b/login.cpp @@ -0,0 +1,124 @@ +#include "login.h" +#include <QString> +#include "QXmppStanza.h" + +QString Login::error_to_str(const QXmppStanza::Error::Condition c) +{ + switch (c) + { + case QXmppStanza::Error::BadRequest: + return tr("BadRequest"); + case QXmppStanza::Error::Conflict: + return tr("Conflict"); + case QXmppStanza::Error::FeatureNotImplemented: + return tr("FeatureNotImplemented"); + case QXmppStanza::Error::Forbidden: + return tr("Forbidden"); + case QXmppStanza::Error::Gone: + return tr("Gone"); + case QXmppStanza::Error::InternalServerError: + return tr("internal server error"); + case QXmppStanza::Error::ItemNotFound: + return tr("item not found"); + case QXmppStanza::Error::JidMalformed: + return tr("JID Malformed"); + case QXmppStanza::Error::NotAcceptable: + return tr("not acceptable"); + case QXmppStanza::Error::NotAllowed: + return tr("not allowed"); + case QXmppStanza::Error::NotAuthorized: + return tr("not authorized"); + case QXmppStanza::Error::RecipientUnavailable: + return tr("recipient unavailable"); + case QXmppStanza::Error::Redirect: + return tr("Redirect"); + case QXmppStanza::Error::RegistrationRequired: + return tr("RegistrationRequired"); + case QXmppStanza::Error::RemoteServerNotFound: + return tr("RemoteServerNotFound"); + case QXmppStanza::Error::RemoteServerTimeout: + return tr("RemoteServerTimeout"); + case QXmppStanza::Error::ResourceConstraint: + return tr("ResourceConstraint"); + case QXmppStanza::Error::ServiceUnavailable: + return tr("ServiceUnavailable"); + case QXmppStanza::Error::SubscriptionRequired: + return tr("SubscriptionRequired"); + case QXmppStanza::Error::UndefinedCondition: + return tr("UndefinedCondition"); + case QXmppStanza::Error::UnexpectedRequest: + return tr("UnexpectedRequest"); + case QXmppStanza::Error::PolicyViolation: + return tr("PolicyViolation"); + + default: + break; + } + + return tr("UnknownError"); +} + +Login::Login(QObject *parent) : + QObject(parent) +{ +} + +void Login::start(QString jid, QString pwd) +{ + QString domain; + + if (!jid_is_valid(jid, domain)) + emit error(tr("Invalid username")); + else if (pwd.isEmpty()) + emit error(tr("Invalid password")); + else + setup(jid, pwd, domain); +} + +bool Login::jid_is_valid(const QString &jid, QString &domain) +{ + if (jid.isEmpty() || jid.count('@') != 1) + return false; + else if ((domain = jid.mid(jid.indexOf('@') + 1)).isEmpty()) + return false; + + return true; +} + +void Login::setup(const QString &jid, const QString pwd, const QString &domain) +{ + QXmppConfiguration cfg; + + cfg.setStreamSecurityMode(QXmppConfiguration::TLSRequired); + cfg.setJid(jid); + cfg.setPassword(pwd); + cfg.setAutoReconnectionEnabled(false); + + const auto client = new Client(jid); + + connect(client, &Client::stateChanged, + [this, d = domain] (const Client::State state) + { + if (state == Client::ConnectingState) + { + emit error(tr("Connecting to ") + d + "..."); + } + }); + + connect(client, &Client::connected, this, + [this, client] + { + emit authSuccess(client); + emit close(); + }); + + connect(client, &Client::disconnected, this, + [this, client] + { + emit error(tr("Disconnected from server: ") + + error_to_str(client->xmppStreamError())); + delete client; + }); + + client->connectToServer(cfg); +} @@ -0,0 +1,30 @@ +#ifndef LOGIN_H +#define LOGIN_H + +#include "client.h" +#include <QQmlApplicationEngine> +#include <QObject> +#include <QString> + +class Login : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Login(QObject *parent = nullptr); + Q_INVOKABLE void start(QString jid, QString pwd); + +Q_SIGNALS: + void authSuccess(Client *c); + void close(); + void error(QString error); + +private: + void setup(const QString &jid, const QString pwd, const QString &domain); + static QString error_to_str(const QXmppStanza::Error::Condition c); + static bool jid_is_valid(const QString &jid, QString &domain); +}; + + +#endif @@ -77,6 +77,11 @@ void Yc::addOutMessage(const QString &msg, const QDateTime &dt) #endif } +void Yc::storeAccount(Client *c) +{ + creds.store(c); +} + void Yc::addAccount(Client *const c) { c->configuration().setAutoReconnectionEnabled(true); @@ -165,6 +170,16 @@ void Yc::retrieveConversations() } } +QStringList Yc::accounts() const +{ + QStringList ret; + + for (const auto c : clients) + ret << c->jidBare(); + + return ret; +} + void Yc::init() { creds.load().then(this, @@ -16,13 +16,15 @@ public: Yc(QObject *parent = nullptr); ~Yc(); Q_INVOKABLE void init(); + Q_INVOKABLE QStringList accounts() const; + Q_INVOKABLE void addAccount(Client *c); + Q_INVOKABLE void storeAccount(Client *c); private: void connectAccounts(const QList<Credentials::Pair> &pairs); void startChat(QString from, QString to); void addInMessage(const QXmppMessage &msg); void addOutMessage(const QString &msg, const QDateTime &dt); - void addAccount(Client *c); void storeMessage(const QString &from, const QString &to, const QString &msg, const QDateTime &dt, const Direction dir) const; void storeMessage(const QXmppMessage &msg, const Direction dir) const; |
