Unverified Commit da5679eb authored by Linus Jahn's avatar Linus Jahn
Browse files

RosterPage: Show presence information via. new PresenceCache

This adds a PresenceCache which will cache all incoming presences. The presence
information (currently only status type and status message) can be get from QML
then. They're just displayed as small circle in green (available), orange (away/
XA), grey (unavailable) and red (error). In case of an error a small message is
displayed instead of the last message.
parent 20fe0d47
......@@ -19,6 +19,7 @@ SOURCES += \
src/MessageSessionHandler.cpp \
src/MessageModel.cpp \
src/Notifications.cpp \
src/PresenceCache.cpp \
src/PresenceHandler.cpp \
src/ServiceDiscoveryManager.cpp \
src/VCardManager.cpp \
......@@ -36,6 +37,7 @@ HEADERS += \
src/MessageSessionHandler.h \
src/MessageModel.h \
src/Notifications.h \
src/PresenceCache.h \
src/PresenceHandler.h \
src/MessageHandler.h \
src/Kaidan.h \
......
......@@ -15,6 +15,7 @@ set(KAIDAN_SOURCES
${CURDIR}/MessageSessionHandler.cpp
${CURDIR}/MessageModel.cpp
${CURDIR}/Notifications.cpp
${CURDIR}/PresenceCache.cpp
${CURDIR}/PresenceHandler.cpp
${CURDIR}/ServiceDiscoveryManager.cpp
${CURDIR}/VCardManager.cpp
......
......@@ -33,6 +33,7 @@
#include "ClientWorker.h"
#include "AvatarFileStorage.h"
#include "RosterManager.h"
#include "PresenceCache.h"
#include "PresenceHandler.h"
#include "ServiceDiscoveryManager.h"
#include "Database.h"
......@@ -58,12 +59,12 @@
static const unsigned int KAIDAN_CLIENT_LOOP_INTERVAL = 30;
ClientThread::ClientThread(RosterModel *rosterModel, MessageModel *messageModel,
AvatarFileStorage *avatarStorage, Credentials creds,
QSettings *settings, Kaidan *kaidan,
AvatarFileStorage *avatarStorage, PresenceCache *presenceCache,
Credentials creds, QSettings *settings, Kaidan *kaidan,
QGuiApplication *app, QObject *parent)
: QThread(parent), rosterModel(rosterModel), messageModel(messageModel),
avatarStorage(avatarStorage), creds(creds), settings(settings),
connState(ConnectionState::StateNone), kaidan(kaidan)
avatarStorage(avatarStorage), presenceCache(presenceCache), creds(creds),
settings(settings), connState(ConnectionState::StateNone), kaidan(kaidan)
{
// Set custom thread name
setObjectName("XmppClient");
......@@ -114,7 +115,7 @@ void ClientThread::run()
messageSessionHandler = new MessageSessionHandler(client, messageModel, rosterModel);
vCardManager = new VCardManager(client, avatarStorage, rosterModel);
rosterManager = new RosterManager(kaidan, client, rosterModel, vCardManager);
presenceHandler = new PresenceHandler(client);
presenceHandler = new PresenceHandler(client, presenceCache);
serviceDiscoveryManager = new ServiceDiscoveryManager(client, client->disco());
xmlLogHandler = new XmlLogHandler(client);
......
......@@ -50,6 +50,7 @@ class RosterModel;
class MessageSessionHandler;
class MessageModel;
class AvatarFileStorage;
class PresenceCache;
class PresenceHandler;
class ServiceDiscoveryManager;
class VCardManager;
......@@ -106,9 +107,9 @@ public:
* @param parent Optional QObject-based parent
*/
ClientThread(RosterModel *rosterModel, MessageModel *messageModel,
AvatarFileStorage *avatarStorage, Credentials creds,
QSettings *settings, Kaidan *kaidan, QGuiApplication *app,
QObject *parent = nullptr);
AvatarFileStorage *avatarStorage, PresenceCache *presenceCache,
Credentials creds, QSettings *settings, Kaidan *kaidan,
QGuiApplication *app, QObject *parent = nullptr);
/*
* Will exit the event loop and waits until thread finishes and then
......@@ -248,6 +249,7 @@ private:
ClientWorker *worker;
RosterManager *rosterManager;
MessageSessionHandler *messageSessionHandler;
PresenceCache *presenceCache;
PresenceHandler *presenceHandler;
ServiceDiscoveryManager *serviceDiscoveryManager;
VCardManager *vCardManager;
......
......@@ -39,6 +39,7 @@
#include <QCoreApplication>
// Kaidan
#include "AvatarFileStorage.h"
#include "PresenceCache.h"
#include "RosterModel.h"
#include "MessageModel.h"
#include "Database.h"
......@@ -59,6 +60,7 @@ Kaidan::Kaidan(QGuiApplication *app, QObject *parent) : QObject(parent)
messageModel = new MessageModel(database->getDatabase(), this);
rosterModel = new RosterModel(database->getDatabase(), this);
avatarStorage = new AvatarFileStorage(this);
presenceCache = new PresenceCache(this);
// Connect the avatar changed signal of the avatarStorage with the NOTIFY signal
// of the Q_PROPERTY for the avatar storage (so all avatars are updated in QML)
connect(avatarStorage, &AvatarFileStorage::avatarIdsChanged,
......@@ -81,7 +83,7 @@ Kaidan::Kaidan(QGuiApplication *app, QObject *parent) : QObject(parent)
creds.isFirstTry = false;
// create new client and start thread's main loop (won't connect until requested)
client = new ClientThread(rosterModel, messageModel, avatarStorage, creds,
client = new ClientThread(rosterModel, messageModel, avatarStorage, presenceCache, creds,
settings, this, app);
client->start();
......@@ -103,6 +105,7 @@ Kaidan::~Kaidan()
delete messageModel;
delete database;
delete avatarStorage;
delete presenceCache;
delete settings;
}
......
......@@ -42,6 +42,7 @@
#include "Enums.h"
class AvatarFileStorage;
class PresenceCache;
class RosterManager;
class RosterModel;
class MessageModel;
......@@ -67,6 +68,7 @@ class Kaidan : public QObject
Q_PROPERTY(RosterModel* rosterModel READ getRosterModel NOTIFY rosterModelChanged)
Q_PROPERTY(MessageModel* messageModel READ getMessageModel NOTIFY messageModelChanged)
Q_PROPERTY(AvatarFileStorage* avatarStorage READ getAvatarStorage NOTIFY avatarStorageChanged)
Q_PROPERTY(PresenceCache* presenceCache READ getPresenceCache NOTIFY presenceCacheChanged)
Q_PROPERTY(quint8 connectionState READ getConnectionState NOTIFY connectionStateChanged)
Q_PROPERTY(quint8 disconnReason READ getDisconnReason NOTIFY disconnReasonChanged)
Q_PROPERTY(QString jid READ getJid WRITE setJid NOTIFY jidChanged)
......@@ -252,10 +254,19 @@ public:
return avatarStorage;
}
/**
* Get the presence cache
*/
PresenceCache* getPresenceCache() const
{
return presenceCache;
}
signals:
void rosterModelChanged();
void messageModelChanged();
void avatarStorageChanged();
void presenceCacheChanged();
/**
* Emitted, when the client's connection state has changed (e.g. when
......@@ -335,6 +346,7 @@ private:
RosterManager *rosterManager;
MessageModel *messageModel;
AvatarFileStorage *avatarStorage;
PresenceCache *presenceCache;
QSettings *settings;
ClientThread::Credentials creds;
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PresenceCache.h"
PresenceCache::PresenceCache(QObject *parent) : QObject(parent)
{
connect(this, &PresenceCache::presenceArrived, this, &PresenceCache::updatePresence);
}
PresenceCache::~PresenceCache()
{
for (auto &key : presences.keys()) {
delete presences[key];
presences.remove(key);
}
}
void PresenceCache::updatePresence(QString jid, QString resource,
gloox::Presence::PresenceType type, QString status)
{
if (!presences.contains(jid))
presences[jid] = new ContactPresences(jid);
presences[jid]->getResourcePresence(resource)->setType(type);
presences[jid]->getResourcePresence(resource)->setStatus(status);
emit presenceChanged(jid);
}
QString PresenceCache::getDefaultStatus(QString jid)
{
if (!presences.contains(jid))
presences[jid] = new ContactPresences(jid);
return presences[jid]->getDefaultPresence()->getStatus();
}
quint8 PresenceCache::getDefaultPresType(QString jid)
{
if (!presences.contains(jid))
presences[jid] = new ContactPresences(jid);
return presences[jid]->getDefaultPresence()->getType();
}
ContactPresences::ContactPresences(QString jid, QObject* parent)
: jid(jid), defaultPresence(new EntityPresence(gloox::Presence::Unavailable, "Offline")),
QObject(parent)
{
}
ContactPresences::~ContactPresences()
{
for (auto &key : presences.keys()) {
delete presences[key];
presences.remove(key);
}
}
QQmlListProperty<QString> ContactPresences::getResources()
{
QList<QString*> qList;
for (auto &key : presences.keys()) {
qList << &key;
}
return QQmlListProperty<QString>((QObject*) this, qList);
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PRESENCECACHE_H
#define PRESENCECACHE_H
#include <QObject>
#include <QMap>
#include <QQmlListProperty>
#include <gloox/presence.h>
/**
* @class EntityPresence Holds a presence for a single XMPP entity (JID with resource)
*/
class EntityPresence : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 type READ getType NOTIFY typeChanged)
Q_PROPERTY(QString status READ getStatus NOTIFY statusChanged)
public:
EntityPresence(gloox::Presence::PresenceType type, QString status)
: type(type), status(status)
{
}
quint8 getType() const
{
return (quint8) type;
}
void setType(quint8 type)
{
this->type = (gloox::Presence::PresenceType) type;
emit typeChanged();
}
QString getStatus() const
{
return status;
}
void setStatus(QString status)
{
this->status = status;
emit statusChanged();
}
signals:
void typeChanged();
void statusChanged();
private:
gloox::Presence::PresenceType type;
QString status;
};
/**
* @class ContactPresences Holds presences for each resource of a JID
*/
class ContactPresences : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<QString> resources READ getResources NOTIFY resourcesChanged)
public:
ContactPresences(QString jid, QObject *parent = nullptr);
~ContactPresences();
QQmlListProperty<QString> getResources();
Q_INVOKABLE EntityPresence* getResourcePresence(QString resource)
{
if (!presences.contains(resource))
presences[resource] = new EntityPresence(gloox::Presence::Unavailable, QString());
return presences[resource];
}
Q_INVOKABLE QString getDefaultResource()
{
if (!presences.empty())
return presences.firstKey();
else
return "";
}
Q_INVOKABLE EntityPresence* getDefaultPresence()
{
if (!presences.empty())
return presences.first();
else
return defaultPresence;
}
signals:
void resourcesChanged();
private:
QMap<QString, EntityPresence*> presences;
QString jid;
EntityPresence *defaultPresence;
};
/**
* @class PresenceCache A cache for presence holders for certain JIDs
*/
class PresenceCache : public QObject
{
Q_OBJECT
public:
/**
* Default constructor
*/
PresenceCache(QObject *parent = nullptr);
/**
* Destructor
*/
~PresenceCache();
/**
* Get presences of a certain JID
* @param jid Account address of the presences
*/
Q_INVOKABLE ContactPresences* getPresences(QString jid)
{
if (!presences.contains(jid))
presences[jid] = new ContactPresences(jid);
return presences[jid];
}
Q_INVOKABLE QString getDefaultStatus(QString jid);
Q_INVOKABLE quint8 getDefaultPresType(QString jid);
signals:
void presenceArrived(QString jid, QString resource,
gloox::Presence::PresenceType type, QString status);
void presenceChanged(QString jid);
private slots:
void updatePresence(QString jid, QString resource,
gloox::Presence::PresenceType type, QString status);
private:
QMap<QString, ContactPresences*> presences;
};
#endif // PRESENCECACHE_H
......@@ -29,10 +29,12 @@
*/
#include "PresenceHandler.h"
#include "PresenceCache.h"
#include <QDebug>
PresenceHandler::PresenceHandler(gloox::Client *client)
PresenceHandler::PresenceHandler(gloox::Client *client, PresenceCache *cache)
: client(client), cache(cache)
{
this->client = client;
client->registerPresenceHandler(this);
}
......@@ -43,4 +45,9 @@ PresenceHandler::~PresenceHandler()
void PresenceHandler::handlePresence(const gloox::Presence &presence)
{
// Subscription requests are now managed in the RosterUpdater
emit cache->presenceArrived(
QString::fromStdString(presence.from().bare()),
QString::fromStdString(presence.from().resource()),
presence.subtype(), QString::fromStdString(presence.status())
);
}
......@@ -36,15 +36,18 @@
#include <gloox/presence.h>
#include <gloox/presencehandler.h>
class PresenceCache;
class PresenceHandler : public gloox::PresenceHandler
{
public:
PresenceHandler(gloox::Client *client);
PresenceHandler(gloox::Client *client, PresenceCache *cache);
~PresenceHandler();
virtual void handlePresence(const gloox::Presence &presence);
private:
gloox::Client *client;
PresenceCache *cache;
};
#endif // PRESENCEHANDLER_H
......@@ -43,11 +43,14 @@
#include <QQmlContext>
#include <QTranslator>
#include <QLibraryInfo>
// gloox
#include <gloox/presence.h>
// Kaidan
#include "Kaidan.h"
#include "RosterModel.h"
#include "MessageModel.h"
#include "AvatarFileStorage.h"
#include "PresenceCache.h"
#include "Globals.h"
#include "Enums.h"
#include "StatusBar.h"
......@@ -118,7 +121,11 @@ int main(int argc, char *argv[])
qRegisterMetaType<MessageModel*>("MessageModel*");
qRegisterMetaType<AvatarFileStorage*>("AvatarFileStorage*");
qRegisterMetaType<ContactMap>("ContactMap");
qRegisterMetaType<PresenceCache*>("PresenceCache*");
qRegisterMetaType<ContactPresences*>("ContactPresences*");
qRegisterMetaType<EntityPresence*>("EntityPresence*");
qRegisterMetaType<Qt::ApplicationState>("Qt::ApplicationState");
qRegisterMetaType<gloox::Presence::PresenceType>("gloox::Presence::PresenceType");
qmlRegisterUncreatableMetaObject(Enums::staticMetaObject, APPLICATION_ID,
1, 0, "Kaidan", "Access to enums & flags only");
......
......@@ -63,27 +63,27 @@ Kirigami.ScrollablePage {
verticalLayoutDirection: ListView.TopToBottom
model: kaidan.rosterModel
delegate: RosterListItem {
id: rosterItem
name: model.name ? model.name : model.jid
lastMessage: model.lastMessage
presenceType: kaidan.presenceCache.getDefaultPresType(model.jid)
unreadMessages: model.unreadMessages
avatarImagePath: kaidan.avatarStorage.getHashOfJid(model.jid) !== "" ?
kaidan.avatarStorage.getAvatarUrl(model.jid) :
kaidan.getResourcePath("images/fallback-avatar.svg")
onClicked: {
// first push the chat page
pageStack.push(chatPage, {
"chatName": (model.name ? model.name : model.jid),
"recipientJid": model.jid
});
})
// then set the message filter for this jid
// this will update the unread message count,
// which will update the roster and will reset the
// model variable
kaidan.chatPartner = model.jid;
kaidan.chatPartner = model.jid
}
actions: [
Kirigami.Action {
iconName: "bookmark-remove"
......@@ -93,6 +93,20 @@ Kirigami.ScrollablePage {
}
}
]
function newPresenceArrived(jid) {
if (jid === model.jid) {
rosterItem.presenceType = kaidan.presenceCache.
getDefaultPresType(model.jid)
}
}
Component.onCompleted: {
kaidan.presenceCache.presenceChanged.connect(newPresenceArrived)
}
Component.onDestruction: {
kaidan.presenceCache.presenceChanged.disconnect(newPresenceArrived)
}
}
}
}
......@@ -31,13 +31,16 @@
import QtQuick 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.0 as Kirigami
Kirigami.SwipeListItem {
property string name;
property string lastMessage;
property int unreadMessages;
property string avatarImagePath;
property string name
property string lastMessage
property int unreadMessages
property string avatarImagePath
property int presenceType
property string presenceErrorMsg
id: listItem
topPadding: Kirigami.Units.smallSpacing * 1.5
......@@ -47,14 +50,52 @@ Kirigami.SwipeListItem {
spacing: Kirigami.Units.gridUnit * 0.5
// left side: Avatar
RoundImage {
source: avatarImagePath
width: height
fillMode: Image.PreserveAspectFit
Item {
id: avatarSpace
Layout.preferredHeight: parent.height
Layout.preferredWidth: parent.height
mipmap: true
RoundImage {
id: avatar
anchors.fill: parent
source: avatarImagePath
width: height
fillMode: Image.PreserveAspectFit
mipmap: true
}
Rectangle {
id: presenceIndicator
visible: presenceType !== 8 // invisible when presence is invalid
anchors.right: avatarSpace.right
anchors.bottom: avatarSpace.bottom
width: Kirigami.Units.gridUnit
height: Kirigami.Units.gridUnit
color: {
presenceType === 0 ? "green" : // available
presenceType === 1 ? "darkgreen" : // chat
presenceType === 2 ? "orange" : // away
presenceType === 3 ? "orange" : // do not disturb
presenceType === 4 ? "orange" : // extended away
presenceType === 7 ? "red" : // error
presenceType === 6 ? "red" : // error
"lightgrey" // unavailable (offline) (5), probe (6), invalid (8)
}
radius: Math.min