[fractal/bilelmoussaoui/location-event] Room history: Send/display static location events
- From: Bilal Elmoussaoui <bilelmoussaoui src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/bilelmoussaoui/location-event] Room history: Send/display static location events
- Date: Wed, 13 Apr 2022 00:41:14 +0000 (UTC)
commit 57fe0e0d8ca371b79c86f905a9beedd01c34e67f
Author: Bilal Elmoussaoui <belmouss redhat com>
Date: Tue Apr 12 01:18:38 2022 +0200
Room history: Send/display static location events
Fixes #952
Cargo.lock | 35 +++++-
Cargo.toml | 4 +
build-aux/org.gnome.Fractal.Devel.json | 16 +++
.../icons/scalable/actions/map-marker-symbolic.svg | 28 +++++
data/resources/resources.gresource.xml | 2 +
data/resources/style.css | 9 ++
data/resources/ui/content-message-location.ui | 32 ++++++
data/resources/ui/content-room-history.ui | 29 +++--
po/POTFILES.in | 1 +
.../content/room_history/message_row/location.rs | 125 +++++++++++++++++++++
.../content/room_history/message_row/mod.rs | 16 ++-
src/session/content/room_history/mod.rs | 53 +++++++++
12 files changed, 339 insertions(+), 11 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index e6e806286..b821a10f9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -140,9 +140,9 @@ dependencies = [
[[package]]
name = "ashpd"
-version = "0.2.4"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "098dee97729c0164b39a8a7de9c20e4b0eb9cd57f87c8bb465224587b44b1683"
+checksum = "5eea0a7a98b3bd2832eb087e1078f6f58db5a54447574d3007cdac6309c1e9f1"
dependencies = [
"enumflags2",
"futures",
@@ -1021,6 +1021,7 @@ dependencies = [
"indexmap",
"libadwaita",
"libsecret",
+ "libshumate",
"log",
"matrix-sdk",
"mime",
@@ -2204,6 +2205,36 @@ dependencies = [
"system-deps 6.0.2",
]
+[[package]]
+name = "libshumate"
+version = "0.1.0-alpha.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5c0b7cb25a837204c7eda0879877e0716924c2f970c08e60228bd8410ddc372"
+dependencies = [
+ "gdk4",
+ "gio",
+ "glib",
+ "gtk4",
+ "libc",
+ "libshumate-sys",
+ "once_cell",
+]
+
+[[package]]
+name = "libshumate-sys"
+version = "0.1.0-alpha.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9380bffe8a69af1cd5c6ae0b6dfd2942017d89d9565f64d0e3579629e3921f07"
+dependencies = [
+ "gdk4-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk4-sys",
+ "libc",
+ "system-deps 6.0.2",
+]
+
[[package]]
name = "libspa"
version = "0.4.1"
diff --git a/Cargo.toml b/Cargo.toml
index 4e9f3b3f4..ec4aa6c66 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,6 +63,10 @@ features = ["v4_6"]
package = "libadwaita"
version = "0.1.0"
+[dependencies.shumate]
+package = "libshumate"
+version = "0.1.0-alpha.4"
+
[dependencies.matrix-sdk]
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
features = [
diff --git a/build-aux/org.gnome.Fractal.Devel.json b/build-aux/org.gnome.Fractal.Devel.json
index f216f2100..79a8168c8 100644
--- a/build-aux/org.gnome.Fractal.Devel.json
+++ b/build-aux/org.gnome.Fractal.Devel.json
@@ -31,6 +31,22 @@
]
},
"modules" : [
+ {
+ "name": "libshumate",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Dgir=false",
+ "-Dvapi=false",
+ "-Dgtk_doc=false"
+ ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.gnome.org/GNOME/libshumate/",
+ "tag": "1.0.0.alpha.1"
+ }
+ ]
+ },
{
"name" : "fractal",
"buildsystem" : "meson",
diff --git a/data/resources/icons/scalable/actions/map-marker-symbolic.svg
b/data/resources/icons/scalable/actions/map-marker-symbolic.svg
new file mode 100644
index 000000000..6511c8744
--- /dev/null
+++ b/data/resources/icons/scalable/actions/map-marker-symbolic.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 16 16"
version="1.1" id="svg7384" height="16">
+ <metadata id="metadata90">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <defs id="defs7386">
+ <linearGradient osb:paint="solid" id="linearGradient7212">
+ <stop style="stop-color:#000000;stop-opacity:1;" offset="0" id="stop7214"/>
+ </linearGradient>
+ </defs>
+ <g transform="translate(-380.00852,79.9875)" style="display:inline" id="layer1"/>
+ <g transform="translate(-621.00872,446.9875)" style="display:inline" id="layer9"/>
+ <g transform="translate(-621.00872,446.9875)" style="display:inline" id="g7628"/>
+ <g transform="translate(-380.00852,-120.0125)" id="layer13">
+ <path d="m 388.00867,121.00914 c -2.76142,0 -5,2.23858 -5,5 0,0.17259 0.0142,0.33191 0.0312,0.5
0.0137,0.16725 0.0358,0.33617 0.0625,0.5 0.57248,3.51444 2.9063,6.00336 4.9063,8.00336 2,-2 4.33372,-4.48892
4.9062,-8.00336 0.0267,-0.16383 0.0488,-0.33275 0.0625,-0.5 0.0171,-0.16809 0.0312,-0.32741 0.0312,-0.5
0,-2.76142 -2.23858,-5 -5,-5 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0
-2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z" id="path5874-9"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#2e3436;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:
auto;enable-background:new"/>
+ </g>
+ <g transform="translate(-380.00852,79.9875)" style="display:inline" id="g6387"/>
+ <g transform="translate(-380.00852,79.9875)" style="display:inline" id="layer10"/>
+ <g transform="translate(-380.00852,79.9875)" id="layer12"/>
+ <g transform="translate(-380.00852,79.9875)" style="display:inline" id="layer11"/>
+</svg>
\ No newline at end of file
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 79008ec34..8f23ec3f8 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -14,6 +14,7 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/idp-google-dark.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/idp-google.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/idp-twitter.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/map-marker-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/empty-page.svg</file>
@@ -54,6 +55,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="content-member-row.ui">ui/content-member-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-audio.ui">ui/content-message-audio.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-file.ui">ui/content-message-file.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="content-message-location.ui">ui/content-message-location.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-media.ui">ui/content-message-media.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-reaction-list.ui">ui/content-message-reaction-list.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-message-reaction.ui">ui/content-message-reaction.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index e79105199..64e3e1dda 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -375,6 +375,15 @@ login {
font-size: 3em;
}
+.room-history .event-content .location .map {
+ border-radius: 6px;
+ background-color: @borders;
+}
+
+.room-history .event-content .location .map-marker {
+ color: @accent_color;
+}
+
.room-history .event-content .thumbnail {
border-radius: 6px;
background-color: @borders;
diff --git a/data/resources/ui/content-message-location.ui b/data/resources/ui/content-message-location.ui
new file mode 100644
index 000000000..4a98c8dd2
--- /dev/null
+++ b/data/resources/ui/content-message-location.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkImage" id="marker_img">
+ <property name="icon-name">map-marker-symbolic</property>
+ <property name="pixel-size">32</property>
+ <style>
+ <class name="map-marker" />
+ </style>
+ </object>
+ <template class="ContentMessageLocation" parent="GtkWidget">
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <property name="overflow">GTK_OVERFLOW_HIDDEN</property>
+ <child>
+ <object class="ShumateMap" id="map" />
+ </child>
+ <child type="overlay">
+ <object class="ShumateLicense" id="license">
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ </object>
+ </child>
+ <style>
+ <class name="map"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="location" />
+ </style>
+ </template>
+</interface>
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index 3d3baa56f..aeb7d0b88 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -21,6 +21,20 @@
</item>
</section>
</menu>
+ <menu id="message-menu-model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Location</attribute>
+ <attribute name="action">room-history.send-location</attribute>
+ <attribute name="icon">map-marker-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Attachment</attribute>
+ <attribute name="action">room-history.select-file</attribute>
+ <attribute name="icon">mail-attachment-symbolic</attribute>
+ </item>
+ </section>
+ </menu>
<template class="ContentRoomHistory" parent="AdwBin">
<property name="vexpand">True</property>
<property name="hexpand">True</property>
@@ -209,13 +223,6 @@
<style>
<class name="toolbar"/>
</style>
- <child>
- <object class="GtkButton">
- <property name="valign">end</property>
- <property name="icon-name">mail-attachment-symbolic</property>
- <property name="action-name">room-history.select-file</property>
- </object>
- </child>
<child>
<object class="GtkMenuButton" id="markdown_button">
<property name="valign">end</property>
@@ -250,6 +257,14 @@
</child>
</object>
</child>
+ <child>
+ <object class="GtkMenuButton">
+ <property name="valign">end</property>
+ <property name="direction">up</property>
+ <property name="icon-name">view-more-horizontal-symbolic</property>
+ <property name="menu-model">message-menu-model</property>
+ </object>
+ </child>
<child>
<object class="GtkButton">
<property name="valign">end</property>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0110d7b5a..1f3367553 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -57,6 +57,7 @@ src/session/content/room_details/member_page/mod.rs
src/session/content/room_details/mod.rs
src/session/content/room_history/item_row.rs
src/session/content/room_history/message_row/audio.rs
+src/session/content/room_history/message_row/location.rs
src/session/content/room_history/message_row/media.rs
src/session/content/room_history/message_row/mod.rs
src/session/content/room_history/mod.rs
diff --git a/src/session/content/room_history/message_row/location.rs
b/src/session/content/room_history/message_row/location.rs
new file mode 100644
index 000000000..ac7faf78c
--- /dev/null
+++ b/src/session/content/room_history/message_row/location.rs
@@ -0,0 +1,125 @@
+use adw::{prelude::*, subclass::prelude::*};
+use gtk::{glib, subclass::prelude::*, CompositeTemplate};
+use shumate::prelude::*;
+
+use crate::i18n::gettext_f;
+
+mod imp {
+ use glib::subclass::InitializingObject;
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/Fractal/content-message-location.ui")]
+ pub struct MessageLocation {
+ #[template_child]
+ pub overlay: TemplateChild<gtk::Overlay>,
+ #[template_child]
+ pub map: TemplateChild<shumate::Map>,
+ #[template_child]
+ pub license: TemplateChild<shumate::License>,
+ #[template_child]
+ pub marker_img: TemplateChild<gtk::Image>,
+ pub marker: shumate::Marker,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for MessageLocation {
+ const NAME: &'static str = "ContentMessageLocation";
+ type Type = super::MessageLocation;
+ type ParentType = gtk::Widget;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ klass.set_accessible_role(gtk::AccessibleRole::Navigation);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for MessageLocation {
+ fn constructed(&self, obj: &Self::Type) {
+ self.marker.set_child(Some(&*self.marker_img));
+
+ let registry = shumate::MapSourceRegistry::with_defaults();
+ let source = registry.by_id(&shumate::MAP_SOURCE_OSM_MAPNIK).unwrap();
+
+ let viewport = self.map.viewport().unwrap();
+
+ let layer = shumate::MapLayer::new(&source, &viewport);
+ self.map.add_layer(&layer);
+
+ let marker_layer = shumate::MarkerLayer::new(&viewport);
+ marker_layer.add_marker(&self.marker);
+ self.map.add_layer(&marker_layer);
+
+ self.map.set_map_source(&source);
+ viewport.set_reference_map_source(Some(&source));
+ viewport.set_zoom_level(12.0);
+
+ self.license.append_map_source(&source);
+ self.parent_constructed(obj);
+ }
+
+ fn dispose(&self, obj: &Self::Type) {
+ self.overlay.unparent();
+ }
+ }
+
+ impl WidgetImpl for MessageLocation {
+ fn measure(
+ &self,
+ _widget: &Self::Type,
+ _orientation: gtk::Orientation,
+ _for_size: i32,
+ ) -> (i32, i32, i32, i32) {
+ (300, 300, -1, -1)
+ }
+
+ fn size_allocate(&self, _widget: &Self::Type, width: i32, height: i32, baseline: i32) {
+ self.overlay
+ .size_allocate(>k::Allocation::new(0, 0, width, height), baseline)
+ }
+ }
+}
+
+glib::wrapper! {
+ /// A widget displaying a location message in the timeline.
+ pub struct MessageLocation(ObjectSubclass<imp::MessageLocation>)
+ @extends gtk::Widget, @implements gtk::Accessible;
+}
+
+impl MessageLocation {
+ /// Create a new location message.
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create MessageLocation")
+ }
+
+ pub fn set_geo_uri(&self, uri: &str) {
+ let imp = self.imp();
+
+ let mut uri = uri.trim_start_matches("geo:").split(',');
+ let latitude = uri
+ .next()
+ .and_then(|lat_s| lat_s.parse::<f64>().ok())
+ .unwrap_or_default();
+ let longitude = uri
+ .next()
+ .and_then(|lon_s| lon_s.parse::<f64>().ok())
+ .unwrap_or_default();
+
+ imp.map.center_on(latitude, longitude);
+ imp.marker.set_location(latitude, longitude);
+
+ self.update_property(&[gtk::accessible::Property::Description(&gettext_f(
+ "Location at latitude {latitude} and longitude {longitude}",
+ &[
+ ("latitude", &latitude.to_string()),
+ ("longitude", &longitude.to_string()),
+ ],
+ ))]);
+ }
+}
diff --git a/src/session/content/room_history/message_row/mod.rs
b/src/session/content/room_history/message_row/mod.rs
index e5ae47c7b..5cca6a6ce 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -1,5 +1,6 @@
mod audio;
mod file;
+mod location;
mod media;
mod reaction;
mod reaction_list;
@@ -21,7 +22,7 @@ use matrix_sdk::ruma::events::{
};
use self::{
- audio::MessageAudio, file::MessageFile, media::MessageMedia,
+ audio::MessageAudio, file::MessageFile, location::MessageLocation, media::MessageMedia,
reaction_list::MessageReactionList, reply::MessageReply, text::MessageText,
};
use crate::{
@@ -305,7 +306,18 @@ fn build_content(parent: &adw::Bin, event: &Event, compact: bool) {
};
child.image(message, &event.room().session(), compact);
}
- MessageType::Location(_message) => {}
+ MessageType::Location(message) => {
+ let child = if let Some(Ok(child)) =
+ parent.child().map(|w| w.downcast::<MessageLocation>())
+ {
+ child
+ } else {
+ let child = MessageLocation::new();
+ parent.set_child(Some(&child));
+ child
+ };
+ child.set_geo_uri(&message.geo_uri);
+ }
MessageType::Notice(message) => {
let child = if let Some(Ok(child)) =
parent.child().map(|w| w.downcast::<MessageText>())
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index 24f45f854..6cdd57edd 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -8,6 +8,11 @@ mod verification_info_bar;
use std::str::FromStr;
use adw::subclass::prelude::*;
+use ashpd::{
+ desktop::location::{Accuracy, LocationProxy},
+ WindowIdentifier,
+};
+use futures::TryFutureExt;
use gettextrs::gettext;
use gtk::{
gdk, gio, glib,
@@ -20,6 +25,7 @@ use matrix_sdk::ruma::events::room::message::{
EmoteMessageEventContent, FormattedBody, MessageType, RoomMessageEventContent,
TextMessageEventContent,
};
+use ruma::events::{room::message::LocationMessageEventContent, AnyMessageLikeEventContent};
use sourceview::prelude::*;
use self::{
@@ -28,6 +34,7 @@ use self::{
};
use crate::{
components::{CustomEntry, DragOverlay, Pill, RoomTitle},
+ i18n::gettext_f,
session::{
content::{MarkdownPopover, RoomDetails},
room::{Event, Room, RoomType, Timeline, TimelineState},
@@ -141,6 +148,14 @@ mod imp {
klass.install_action("room-history.open-emoji", None, move |widget, _, _| {
widget.open_emoji();
});
+
+ klass.install_action("room-history.send-location", None, move |widget, _, _| {
+ spawn!(clone!(@weak widget => async move {
+ if let Err(err) = widget.send_location().await {
+ log::error!("Failed to send location {}", err);
+ }
+ }));
+ });
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -771,6 +786,44 @@ impl RoomHistory {
self.imp().message_entry.emit_insert_emoji();
}
+ async fn send_location(&self) -> ashpd::Result<()> {
+ if let Some(room) = self.room() {
+ let connection = ashpd::zbus::Connection::session().await?;
+ let proxy = LocationProxy::new(&connection).await?;
+ let identifier = WindowIdentifier::default();
+
+ let session = proxy
+ .create_session(None, Some(0), Some(Accuracy::Exact))
+ .await?;
+
+ let (_, location) = futures::try_join!(
+ proxy.start(&session, &identifier).into_future(),
+ proxy.receive_location_updated().into_future()
+ )?;
+
+ let iso8601_datetime =
+ glib::DateTime::from_unix_local(location.timestamp().as_secs() as i64)
+ .expect("Valid location timestamp");
+ let geo_uri = format!("geo:{},{}", location.latitude(), location.longitude());
+ let location_body = gettext_f(
+ "User Location {geo_uri} at {iso8601_datetime}",
+ &[
+ ("geo_uri", &geo_uri),
+ (
+ "iso8601_datetime",
+ iso8601_datetime.format_iso8601().unwrap().as_str(),
+ ),
+ ],
+ );
+ room.send_room_message_event(AnyMessageLikeEventContent::RoomMessage(
+ RoomMessageEventContent::new(MessageType::Location(
+ LocationMessageEventContent::new(location_body, geo_uri),
+ )),
+ ));
+ }
+ Ok(())
+ }
+
fn open_attach_dialog(&self, bytes: Vec<u8>, mime: mime::Mime, title: &str) {
let window = self.root().unwrap().downcast::<gtk::Window>().unwrap();
let dialog = AttachmentDialog::new(&window);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]