summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2026-02-04 11:34:38 +0100
committerXavier Del Campo Romero <xavi92@disroot.org>2026-02-04 15:24:48 +0100
commit23cc4be5bc02328fe73ccbd67493dd71ef2e2b8f (patch)
treeb9e1a7a416f004d96671c0abf597524946b9dda3
parent3b9973552ec613b37fb511f3a0ae24dacb1e4366 (diff)
Add accounts and login pages
-rw-r--r--Accounts.qml50
-rw-r--r--CMakeLists.txt4
-rw-r--r--ChatView.qml5
-rw-r--r--ContactList.qml4
-rw-r--r--Login.qml101
-rw-r--r--login.cpp124
-rw-r--r--login.h30
-rw-r--r--yc.cpp15
-rw-r--r--yc.h4
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);
+}
diff --git a/login.h b/login.h
new file mode 100644
index 0000000..ca291d5
--- /dev/null
+++ b/login.h
@@ -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
diff --git a/yc.cpp b/yc.cpp
index 94798f6..28e780c 100644
--- a/yc.cpp
+++ b/yc.cpp
@@ -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,
diff --git a/yc.h b/yc.h
index 0bfa786..12bc78d 100644
--- a/yc.h
+++ b/yc.h
@@ -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;