Add live location tracking model
This can either watch a single live location beacon or all of those in a given room.
This commit is contained in:
@@ -56,6 +56,7 @@ add_library(neochat STATIC
|
|||||||
events/stickerevent.cpp
|
events/stickerevent.cpp
|
||||||
models/reactionmodel.cpp
|
models/reactionmodel.cpp
|
||||||
delegatesizehelper.cpp
|
delegatesizehelper.cpp
|
||||||
|
models/livelocationsmodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(neochat
|
ecm_qt_declare_logging_category(neochat
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
#include "models/emoticonfiltermodel.h"
|
#include "models/emoticonfiltermodel.h"
|
||||||
#include "models/imagepacksmodel.h"
|
#include "models/imagepacksmodel.h"
|
||||||
#include "models/keywordnotificationrulemodel.h"
|
#include "models/keywordnotificationrulemodel.h"
|
||||||
|
#include "models/livelocationsmodel.h"
|
||||||
#include "models/messageeventmodel.h"
|
#include "models/messageeventmodel.h"
|
||||||
#include "models/messagefiltermodel.h"
|
#include "models/messagefiltermodel.h"
|
||||||
#include "models/publicroomlistmodel.h"
|
#include "models/publicroomlistmodel.h"
|
||||||
@@ -247,6 +248,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||||
qmlRegisterType<StateFilterModel>("org.kde.neochat", 1, 0, "StateFilterModel");
|
qmlRegisterType<StateFilterModel>("org.kde.neochat", 1, 0, "StateFilterModel");
|
||||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||||
|
qmlRegisterType<LiveLocationsModel>("org.kde.neochat", 1, 0, "LiveLocationsModel");
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
146
src/models/livelocationsmodel.cpp
Normal file
146
src/models/livelocationsmodel.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "livelocationsmodel.h"
|
||||||
|
|
||||||
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs)
|
||||||
|
{
|
||||||
|
return lhs.eventId < rhs.eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveLocationsModel::LiveLocationsModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
connect(
|
||||||
|
this,
|
||||||
|
&LiveLocationsModel::roomChanged,
|
||||||
|
this,
|
||||||
|
[this]() {
|
||||||
|
for (const auto &event : m_room->messageEvents()) {
|
||||||
|
addEvent(event.get());
|
||||||
|
}
|
||||||
|
connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [this](const auto &events) {
|
||||||
|
for (const auto &event : events) {
|
||||||
|
addEvent(event.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [this](const auto &events) {
|
||||||
|
for (const auto &event : events) {
|
||||||
|
addEvent(event.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection); // deferred so we are sure the eventId filter is set
|
||||||
|
}
|
||||||
|
|
||||||
|
int LiveLocationsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_locations.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const
|
||||||
|
{
|
||||||
|
if (!checkIndex(index)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &data = m_locations.at(index.row());
|
||||||
|
switch (roleName) {
|
||||||
|
case LatitudeRole: {
|
||||||
|
const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0];
|
||||||
|
return latitude.toFloat();
|
||||||
|
}
|
||||||
|
case LongitudeRole: {
|
||||||
|
const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto longitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1];
|
||||||
|
return longitude.toFloat();
|
||||||
|
}
|
||||||
|
case AssetRole:
|
||||||
|
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"].toString();
|
||||||
|
case AuthorRole:
|
||||||
|
return m_room->getUser(data.senderId);
|
||||||
|
case IsLiveRole:
|
||||||
|
return data.beaconInfo["live"_ls].toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> LiveLocationsModel::roleNames() const
|
||||||
|
{
|
||||||
|
auto r = QAbstractListModel::roleNames();
|
||||||
|
r.insert(LatitudeRole, "latitude");
|
||||||
|
r.insert(LongitudeRole, "longitude");
|
||||||
|
r.insert(AssetRole, "asset");
|
||||||
|
r.insert(AuthorRole, "author");
|
||||||
|
r.insert(IsLiveRole, "isLive");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiveLocationsModel::addEvent(const Quotient::RoomEvent *event)
|
||||||
|
{
|
||||||
|
if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info") {
|
||||||
|
LiveLocationData data;
|
||||||
|
data.senderId = event->senderId();
|
||||||
|
data.beaconInfo = event->contentJson();
|
||||||
|
if (event->contentJson()["live"_ls].toBool()) {
|
||||||
|
data.eventId = event->id();
|
||||||
|
} else {
|
||||||
|
data.eventId = event->fullJson()["replaces_state"_ls].toString();
|
||||||
|
}
|
||||||
|
updateLocationData(std::move(data));
|
||||||
|
}
|
||||||
|
if (event->matrixType() == "org.matrix.msc3672.beacon"_ls) {
|
||||||
|
LiveLocationData data;
|
||||||
|
data.eventId = event->contentJson()["m.relates_to"_ls].toObject()["event_id"_ls].toString();
|
||||||
|
data.senderId = event->senderId();
|
||||||
|
data.beacon = event->contentJson();
|
||||||
|
updateLocationData(std::move(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiveLocationsModel::updateLocationData(LiveLocationData &&data)
|
||||||
|
{
|
||||||
|
if (!m_eventId.isEmpty() && data.eventId != m_eventId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::lower_bound(m_locations.begin(), m_locations.end(), data);
|
||||||
|
if (it == m_locations.end() || it->eventId != data.eventId) {
|
||||||
|
const auto row = std::distance(m_locations.begin(), it);
|
||||||
|
beginInsertRows({}, row, row);
|
||||||
|
m_locations.insert(it, std::move(data));
|
||||||
|
endInsertRows();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto idx = index(std::distance(m_locations.begin(), it), 0);
|
||||||
|
|
||||||
|
// TODO Qt6: port to toInteger(), timestamps are in ms since epoch, ie. 64 bit values
|
||||||
|
if (it->beacon.isEmpty() || it->beacon.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beacon.value("org.matrix.msc3488.ts"_ls).toDouble()) {
|
||||||
|
it->beacon = std::move(data.beacon);
|
||||||
|
}
|
||||||
|
if (it->beaconInfo.isEmpty()
|
||||||
|
|| it->beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble()) {
|
||||||
|
it->beaconInfo = std::move(data.beaconInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT dataChanged(idx, idx);
|
||||||
|
}
|
||||||
67
src/models/livelocationsmodel.h
Normal file
67
src/models/livelocationsmodel.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
namespace Quotient
|
||||||
|
{
|
||||||
|
class RoomMessageEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LiveLocationData {
|
||||||
|
QString eventId;
|
||||||
|
QString senderId;
|
||||||
|
QJsonObject beaconInfo;
|
||||||
|
QJsonObject beacon;
|
||||||
|
};
|
||||||
|
bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs);
|
||||||
|
|
||||||
|
/** Accumulates live location beacon events in a given room
|
||||||
|
* and provides the last known state for one or more live location beacons.
|
||||||
|
*/
|
||||||
|
class LiveLocationsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(NeoChatRoom *room MEMBER m_room NOTIFY roomChanged)
|
||||||
|
/** The event id of the beacon start event, ie. the one all suspequent
|
||||||
|
* events use to relate to the same beacon.
|
||||||
|
* If this is set only this specific beacon will be coverd by this model,
|
||||||
|
* if it is empty, all beacons in the room will be covered.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString eventId MEMBER m_eventId NOTIFY eventIdChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LiveLocationsModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
enum Roles {
|
||||||
|
LatitudeRole, /**< Latest latitude of a live locaction beacon. */
|
||||||
|
LongitudeRole, /**< Latest longitude of a live locaction beacon. */
|
||||||
|
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||||
|
AuthorRole, /**< The author of the event. */
|
||||||
|
IsLiveRole, /**< Boolean that indicates whether a live location beacon is still live. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = {}) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int roleName) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void roomChanged();
|
||||||
|
void eventIdChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addEvent(const Quotient::RoomEvent *event);
|
||||||
|
void updateLocationData(LiveLocationData &&data);
|
||||||
|
|
||||||
|
QPointer<NeoChatRoom> m_room;
|
||||||
|
QString m_eventId;
|
||||||
|
|
||||||
|
QList<LiveLocationData> m_locations;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user