Browse Source

Room history: Send/display static location events

Fixes #952
merge-requests/1327/merge
Bilal Elmoussaoui 4 years ago committed by Julian Sparber
parent
commit
d5749f75a4
  1. 35
      Cargo.lock
  2. 4
      Cargo.toml
  3. 16
      build-aux/org.gnome.Fractal.Devel.json
  4. 28
      data/resources/icons/scalable/actions/map-marker-symbolic.svg
  5. 2
      data/resources/resources.gresource.xml
  6. 9
      data/resources/style.css
  7. 23
      data/resources/ui/content-message-location.ui
  8. 29
      data/resources/ui/content-room-history.ui
  9. 1
      po/POTFILES.in
  10. 118
      src/session/content/room_history/message_row/location.rs
  11. 16
      src/session/content/room_history/message_row/mod.rs
  12. 77
      src/session/content/room_history/mod.rs

35
Cargo.lock generated

@ -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"

4
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 = [

16
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",

28
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>

After

Width:  |  Height:  |  Size: 2.6 KiB

2
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>

9
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;

23
data/resources/ui/content-message-location.ui

@ -0,0 +1,23 @@
<?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="ShumateSimpleMap" id="map">
<property name="overflow">GTK_OVERFLOW_HIDDEN</property>
<style>
<class name="map"/>
</style>
</object>
</child>
<style>
<class name="location" />
</style>
</template>
</interface>

29
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>

1
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

118
src/session/content/room_history/message_row/location.rs

@ -0,0 +1,118 @@
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 map: TemplateChild<shumate::SimpleMap>,
#[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);
}
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();
self.map.set_map_source(Some(&source));
let viewport = self.map.viewport().unwrap();
viewport.set_zoom_level(12.0);
let marker_layer = shumate::MarkerLayer::new(&viewport);
marker_layer.add_marker(&self.marker);
self.map.add_overlay_layer(&marker_layer);
// Hide the scale
self.map.scale().unwrap().hide();
self.parent_constructed(obj);
}
fn dispose(&self, _obj: &Self::Type) {
self.map.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.map
.size_allocate(&gtk::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
.viewport()
.unwrap()
.set_location(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()),
],
))]);
}
}

16
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>())

77
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},
@ -50,7 +57,7 @@ mod imp {
use glib::{signal::SignalHandlerId, subclass::InitializingObject};
use super::*;
use crate::Application;
use crate::{components::Toast, window::Window, Application};
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/content-room-history.ui")]
@ -141,6 +148,33 @@ 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 {
let toast_error = match widget.send_location().await {
// Do nothing if the request was canceled by the user
Err(ashpd::Error::Response(ashpd::desktop::ResponseError::Cancelled)) => {
log::error!("Location request was cancelled by the user");
Some(gettext("The location request has been cancelled."))
},
Err(err) => {
log::error!("Failed to send location {}", err);
Some(gettext("Failed to retrieve current location."))
}
_ => None,
};
if let Some(window) = widget
.root()
.as_ref()
.and_then(|root| root.downcast_ref::<Window>())
{
if let Some(message) = toast_error {
window.add_toast(&Toast::new(&message));
}
}
}));
});
}
fn instance_init(obj: &InitializingObject<Self>) {
@ -771,6 +805,47 @@ 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(Some(0), Some(0), Some(Accuracy::Exact))
.await?;
// We want to be listening for new locations whenever the session is up
// otherwise we might lose the first response and will have to wait for a future
// update by geoclue
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);

Loading…
Cancel
Save