Compare commits
2 Commits
v25.04.3
...
work/locat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73869e7b49 | ||
|
|
072f7c6626 |
@@ -47,6 +47,7 @@ add_library(neochat STATIC
|
|||||||
filetransferpseudojob.cpp
|
filetransferpseudojob.cpp
|
||||||
models/searchmodel.cpp
|
models/searchmodel.cpp
|
||||||
texthandler.cpp
|
texthandler.cpp
|
||||||
|
models/locationsmodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(neochat-app
|
add_executable(neochat-app
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
#include "models/devicesmodel.h"
|
#include "models/devicesmodel.h"
|
||||||
#include "models/emojimodel.h"
|
#include "models/emojimodel.h"
|
||||||
#include "models/keywordnotificationrulemodel.h"
|
#include "models/keywordnotificationrulemodel.h"
|
||||||
|
#include "models/locationsmodel.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"
|
||||||
@@ -235,6 +236,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||||
|
qmlRegisterType<LocationsModel>("org.kde.neochat", 1, 0, "LocationsModel");
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
102
src/models/locationsmodel.cpp
Normal file
102
src/models/locationsmodel.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "locationsmodel.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
LocationsModel::LocationsModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
connect(this, &LocationsModel::roomChanged, this, [=]() {
|
||||||
|
for (const auto &event : m_room->messageEvents()) {
|
||||||
|
if (!is<RoomMessageEvent>(*event)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event->contentJson()["msgtype"] == "m.location") {
|
||||||
|
const auto &e = *event;
|
||||||
|
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [=](const auto &events) {
|
||||||
|
for (const auto &event : events) {
|
||||||
|
if (!is<RoomMessageEvent>(*event)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event->contentJson()["msgtype"] == "m.location") {
|
||||||
|
const auto &e = *event;
|
||||||
|
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [=](const auto &events) {
|
||||||
|
for (const auto &event : events) {
|
||||||
|
if (!is<RoomMessageEvent>(*event)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event->contentJson()["msgtype"] == "m.location") {
|
||||||
|
const auto &e = *event;
|
||||||
|
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationsModel::addLocation(const RoomMessageEvent *event)
|
||||||
|
{
|
||||||
|
const auto uri = event->contentJson()["org.matrix.msc3488.location"]["uri"].toString();
|
||||||
|
const auto parts = uri.mid(4).split(QLatin1Char(','));
|
||||||
|
const auto latitude = parts[0].toFloat();
|
||||||
|
const auto longitude = parts[1].toFloat();
|
||||||
|
beginInsertRows(QModelIndex(), m_locations.size(), m_locations.size() + 1);
|
||||||
|
m_locations += LocationData{
|
||||||
|
.eventId = event->id(),
|
||||||
|
.latitude = latitude,
|
||||||
|
.longitude = longitude,
|
||||||
|
.text = event->contentJson()["body"].toString(),
|
||||||
|
.author = dynamic_cast<NeoChatUser *>(m_room->user(event->senderId())),
|
||||||
|
};
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatRoom *LocationsModel::room() const
|
||||||
|
{
|
||||||
|
return m_room;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationsModel::setRoom(NeoChatRoom *room)
|
||||||
|
{
|
||||||
|
if (m_room) {
|
||||||
|
disconnect(this, nullptr, m_room, nullptr);
|
||||||
|
}
|
||||||
|
m_room = room;
|
||||||
|
Q_EMIT roomChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> LocationsModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{LongitudeRole, "longitude"},
|
||||||
|
{LatitudeRole, "latitude"},
|
||||||
|
{TextRole, "text"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant LocationsModel::data(const QModelIndex &index, int roleName) const
|
||||||
|
{
|
||||||
|
auto row = index.row();
|
||||||
|
if (roleName == LongitudeRole) {
|
||||||
|
return m_locations[row].longitude;
|
||||||
|
} else if (roleName == LatitudeRole) {
|
||||||
|
return m_locations[row].latitude;
|
||||||
|
} else if (roleName == TextRole) {
|
||||||
|
return m_locations[row].text;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int LocationsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return m_locations.size();
|
||||||
|
}
|
||||||
49
src/models/locationsmodel.h
Normal file
49
src/models/locationsmodel.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
#include <events/roommessageevent.h>
|
||||||
|
|
||||||
|
class LocationsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
TextRole = Qt::DisplayRole,
|
||||||
|
LongitudeRole,
|
||||||
|
LatitudeRole,
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles)
|
||||||
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
|
explicit LocationsModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] NeoChatRoom *room() const;
|
||||||
|
void setRoom(NeoChatRoom *room);
|
||||||
|
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent) const override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void roomChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<NeoChatRoom> m_room;
|
||||||
|
|
||||||
|
struct LocationData {
|
||||||
|
QString eventId;
|
||||||
|
float latitude;
|
||||||
|
float longitude;
|
||||||
|
QString text;
|
||||||
|
NeoChatUser *author;
|
||||||
|
};
|
||||||
|
QList<LocationData> m_locations;
|
||||||
|
void addLocation(const Quotient::RoomMessageEvent *event);
|
||||||
|
};
|
||||||
@@ -510,6 +510,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return DelegateType::Audio;
|
return DelegateType::Audio;
|
||||||
case MessageEventType::Video:
|
case MessageEventType::Video:
|
||||||
return DelegateType::Video;
|
return DelegateType::Video;
|
||||||
|
case MessageEventType::Location:
|
||||||
|
return DelegateType::Location;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -564,6 +566,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
if(e->msgtype() == Quotient::MessageEventType::Location) {
|
||||||
|
return e->contentJson();
|
||||||
|
}
|
||||||
// Cannot use e.contentJson() here because some
|
// Cannot use e.contentJson() here because some
|
||||||
// EventContent classes inject values into the copy of the
|
// EventContent classes inject values into the copy of the
|
||||||
// content JSON stored in EventContent::Base
|
// content JSON stored in EventContent::Base
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
Encrypted,
|
Encrypted,
|
||||||
ReadMarker,
|
ReadMarker,
|
||||||
Poll,
|
Poll,
|
||||||
|
Location,
|
||||||
Other,
|
Other,
|
||||||
};
|
};
|
||||||
Q_ENUM(DelegateType);
|
Q_ENUM(DelegateType);
|
||||||
|
|||||||
78
src/qml/Component/FullScreenMap.qml
Normal file
78
src/qml/Component/FullScreenMap.qml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtLocation 5.15
|
||||||
|
import QtPositioning 5.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var content
|
||||||
|
|
||||||
|
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||||
|
visibility: Qt.WindowFullScreen
|
||||||
|
|
||||||
|
title: i18n("View Location")
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Escape"
|
||||||
|
onActivated: root.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
|
background: AbstractButton {
|
||||||
|
onClicked: root.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
Map {
|
||||||
|
id: map
|
||||||
|
anchors.fill: parent
|
||||||
|
property string latlong: root.content.geo_uri.split(':')[1]
|
||||||
|
property string latitude: latlong.split(',')[0]
|
||||||
|
property string longitude: latlong.split(',')[1]
|
||||||
|
center: QtPositioning.coordinate(latitude, longitude)
|
||||||
|
zoomLevel: 15
|
||||||
|
plugin: Plugin {
|
||||||
|
name: "osm"
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.useragent"
|
||||||
|
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||||
|
}
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.mapping.providersrepository.address"
|
||||||
|
value: "https://autoconfig.kde.org/qtlocation/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MapCircle {
|
||||||
|
radius: 1500 / map.zoomLevel
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
border.color: Kirigami.Theme.linkColor
|
||||||
|
border.width: Kirigami.Units.devicePixelRatio * 2
|
||||||
|
smooth: true
|
||||||
|
opacity: 0.25
|
||||||
|
center: QtPositioning.coordinate(map.latitude, map.longitude)
|
||||||
|
}
|
||||||
|
onCopyrightLinkActivated: {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
text: i18n("Close")
|
||||||
|
icon.name: "dialog-close"
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
|
||||||
|
width: Kirigami.Units.gridUnit * 2
|
||||||
|
height: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
onClicked: root.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/qml/Component/LocationPage.qml
Normal file
59
src/qml/Component/LocationPage.qml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtLocation 5.15
|
||||||
|
import QtPositioning 5.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.Page {
|
||||||
|
id: locationsPage
|
||||||
|
|
||||||
|
required property var room
|
||||||
|
|
||||||
|
title: i18nc("Locations on a map", "Locations")
|
||||||
|
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
Map {
|
||||||
|
id: map
|
||||||
|
anchors.fill: parent
|
||||||
|
plugin: Plugin {
|
||||||
|
name: "osm"
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.useragent"
|
||||||
|
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||||
|
}
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.mapping.providersrepository.address"
|
||||||
|
value: "https://autoconfig.kde.org/qtlocation/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapItemView {
|
||||||
|
model: LocationsModel {
|
||||||
|
room: locationsPage.room
|
||||||
|
}
|
||||||
|
delegate: MapQuickItem {
|
||||||
|
id: point
|
||||||
|
|
||||||
|
required property var longitude
|
||||||
|
required property var latitude
|
||||||
|
required property string text
|
||||||
|
anchorPoint.x: icon.width / 2
|
||||||
|
anchorPoint.y: icon.height / 2
|
||||||
|
coordinate: QtPositioning.coordinate(point.latitude, point.longitude)
|
||||||
|
autoFadeIn: false
|
||||||
|
sourceItem: Kirigami.Icon {
|
||||||
|
id: icon
|
||||||
|
width: height
|
||||||
|
height: Kirigami.Units.iconSizes.medium
|
||||||
|
source: "flag-blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,11 @@ DelegateChooser {
|
|||||||
delegate: PollDelegate {}
|
delegate: PollDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: MessageEventModel.Location
|
||||||
|
delegate: LocationDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.Other
|
roleValue: MessageEventModel.Other
|
||||||
delegate: Item {}
|
delegate: Item {}
|
||||||
|
|||||||
74
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
74
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtLocation 5.15
|
||||||
|
import QtPositioning 5.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
TimelineContainer {
|
||||||
|
id: locationDelegate
|
||||||
|
|
||||||
|
property string latlong: model.content.geo_uri.split(':')[1]
|
||||||
|
property string latitude: latlong.split(',')[0]
|
||||||
|
property string longitude: latlong.split(',')[1]
|
||||||
|
|
||||||
|
property string formattedBody: model.content.formatted_body
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.maximumWidth: locationDelegate.contentMaxWidth
|
||||||
|
Layout.preferredWidth: locationDelegate.contentMaxWidth
|
||||||
|
Map {
|
||||||
|
id: map
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: locationDelegate.contentMaxWidth / 16 * 9
|
||||||
|
|
||||||
|
center: QtPositioning.coordinate(locationDelegate.latitude, locationDelegate.longitude)
|
||||||
|
zoomLevel: 15
|
||||||
|
plugin: Plugin {
|
||||||
|
name: "osm"
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.useragent"
|
||||||
|
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||||
|
}
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.mapping.providersrepository.address"
|
||||||
|
value: "https://autoconfig.kde.org/qtlocation/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MapCircle {
|
||||||
|
radius: 1500 / map.zoomLevel
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
border.color: Kirigami.Theme.linkColor
|
||||||
|
border.width: Kirigami.Units.devicePixelRatio * 2
|
||||||
|
smooth: true
|
||||||
|
opacity: 0.25
|
||||||
|
center: QtPositioning.coordinate(latitude, longitude)
|
||||||
|
}
|
||||||
|
onCopyrightLinkActivated: {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onTapped: {
|
||||||
|
let map = fullScreenMap.createObject(parent, {content: model.content});
|
||||||
|
map.open()
|
||||||
|
}
|
||||||
|
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody ?? model.body, parent.selectedText)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody ?? model.body, parent.selectedText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: fullScreenMap
|
||||||
|
FullScreenMap { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,6 +209,18 @@ Kirigami.OverlayDrawer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: locationsButton
|
||||||
|
|
||||||
|
icon: "map-flat"
|
||||||
|
text: i18n("Show locations for this room")
|
||||||
|
|
||||||
|
onClicked: pageStack.pushDialogLayer("qrc:/LocationsPage.qml", {
|
||||||
|
room: room
|
||||||
|
}, {
|
||||||
|
title: i18nc("Locations on a map", "Locations")
|
||||||
|
})
|
||||||
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
id: favouriteButton
|
id: favouriteButton
|
||||||
|
|
||||||
|
|||||||
@@ -107,5 +107,8 @@
|
|||||||
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
|
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
|
||||||
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
|
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
|
||||||
<file alias="SearchPage.qml">qml/Page/SearchPage.qml</file>
|
<file alias="SearchPage.qml">qml/Page/SearchPage.qml</file>
|
||||||
|
<file alias="LocationDelegate.qml">qml/Component/Timeline/LocationDelegate.qml</file>
|
||||||
|
<file alias="FullScreenMap.qml">qml/Component/FullScreenMap.qml</file>
|
||||||
|
<file alias="LocationsPage.qml">qml/Component/LocationPage.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
Reference in New Issue
Block a user