Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ makepad-widgets = { git = "https://github.com/kevinaboos/makepad", branch = "gl_
makepad-code-editor = { git = "https://github.com/kevinaboos/makepad", branch = "gl_sampling_fallback" }


## Including this crate automatically configures all `robius-*` crates to work with Makepad.
robius-use-makepad = "0.1.1"
robius-directories = { git = "https://github.com/project-robius/robius" }
robius-file-picker = { git = "https://github.com/project-robius/robius" }
robius-location = { git = "https://github.com/project-robius/robius" }
robius-open = { git = "https://github.com/project-robius/robius" }
robius-share = { git = "https://github.com/project-robius/robius" }
robius-location = { git = "https://github.com/project-robius/robius" }
robius-open = { git = "https://github.com/project-robius/robius" }
robius-share = { git = "https://github.com/project-robius/robius" }
robius-use-makepad = "0.1.1" ## auto-configures all `robius-*` crates to work with Makepad.


anyhow = "1.0"
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ The following table shows which host systems can currently be used to build Robr
## Known issues
- Matrix-specific links (`https://matrix.to/...`) aren't fully handled in-app yet.
- Ignoring/unignoring a user clears all timelines (see: https://github.com/matrix-org/matrix-rust-sdk/issues/1703); the timeline will be re-filled gradually via back pagination, but the viewport position is not maintained.
- Currently, accessing system geolocation on Android may not succeed due to failing to prompt the user for permission. Please enable the location permission in the App Info settings page for Robrix, and then it should work as expected.


> [!IMPORTANT]
Expand Down
49 changes: 37 additions & 12 deletions src/home/location_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
use std::time::SystemTime;

use makepad_widgets::*;
use matrix_sdk::ruma::MilliSecondsSinceUnixEpoch;
use robius_location::Coordinates;

use crate::location::{get_latest_location, request_location_update, LocationAction, LocationRequest, LocationUpdate};
use crate::{
location::{request_location_update, LocationAction, LocationRequest, LocationUpdate},
utils::time_ago,
};

script_mod! {
use mod.prelude.widgets.*
Expand Down Expand Up @@ -97,6 +101,22 @@ struct LocationPreview {
#[rust] timestamp: Option<SystemTime>,
}

fn location_error_message(error: robius_location::Error) -> &'static str {
use robius_location::Error;
match error {
Error::AuthorizationDenied =>
"Permission denied. Give Robrix location access in your device's settings, then try again.",
Error::TemporarilyUnavailable =>
"Your location isn't available right now. Please try again in a moment.",
Error::Network =>
"A network problem prevented getting your location. Check your connection and try again.",
Error::PermanentlyUnavailable =>
"Location services aren't available on this device.",
Error::AndroidEnvironment | Error::NotMainThread | Error::Unknown =>
"Couldn't get your location. Please try again.",
}
}

impl Widget for LocationPreview {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
let mut needs_redraw = false;
Expand Down Expand Up @@ -139,10 +159,16 @@ impl Widget for LocationPreview {
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
let text = match self.coords {
Some(Ok(c)) => {
format!("➡ Current location: {:.6}, {:.6}", c.latitude, c.longitude)
match self.timestamp
.and_then(MilliSecondsSinceUnixEpoch::from_system_time)
.and_then(time_ago)
{
Some(age) => format!("📍 Your location (from {}): {:.6}, {:.6}", age.to_ascii_lowercase(), c.latitude, c.longitude),
None => format!("📍 Your location: {:.6}, {:.6}", c.latitude, c.longitude),
}
}
Some(Err(e)) => format!("➡ Error getting location: {e:?}"),
None => String::from("➡ Current location is not yet available."),
Some(Err(e)) => format!("⚠️ {}", location_error_message(e)),
None => String::from("📍 Getting your location…"),
};
self.label(cx, ids!(location_label)).set_text(cx, &text);
self.view.draw_walk(cx, scope, walk)
Expand All @@ -151,12 +177,11 @@ impl Widget for LocationPreview {


impl LocationPreview {
fn show(&mut self) {
request_location_update(LocationRequest::UpdateOnce);
if let Some(loc) = get_latest_location() {
self.coords = Some(Ok(loc.coordinates));
self.timestamp = loc.time;
}
fn show(&mut self, cx: &mut Cx) {
request_location_update(cx, LocationRequest::UpdateOnce);
self.coords = None;
self.timestamp = None;
self.button(cx, ids!(send_location_button)).set_enabled(cx, false);
self.visible = true;
}

Expand All @@ -175,9 +200,9 @@ impl LocationPreview {
}

impl LocationPreviewRef {
pub fn show(&self) {
pub fn show(&self, cx: &mut Cx) {
if let Some(mut inner) = self.borrow_mut() {
inner.show();
inner.show(cx);
}
}

Expand Down
100 changes: 30 additions & 70 deletions src/location.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Functions for querying the device's current location.

use std::{sync::{mpsc::{self, Receiver, Sender}, Mutex}, time::SystemTime};
use std::{sync::Mutex, time::SystemTime};

use makepad_widgets::{Cx, error, log};
use robius_location::{Access, Accuracy, Coordinates, Location, Manager};
Expand All @@ -12,7 +12,6 @@ pub enum LocationAction {
Update(LocationUpdate),
/// The location handler encountered an error.
Error(robius_location::Error),
None
}

/// An updated location sample, including coordinates and a system timestamp.
Expand All @@ -24,7 +23,7 @@ pub struct LocationUpdate {

static LATEST_LOCATION: Mutex<Option<LocationUpdate>> = Mutex::new(None);

/// Returns the latest location update's coordinates, if available.
/// Returns the latest location update, if one has been received.
///
/// Note that this function is guaranteed to return `None` if
/// [`init_location_subscriber`] has not been called yet.
Expand Down Expand Up @@ -62,90 +61,51 @@ impl robius_location::Handler for LocationHandler {
}


fn location_request_loop(
request_receiver: Receiver<LocationRequest>,
mut manager: ManagerWrapper,
) -> Result<(), robius_location::Error> {

manager.update_once()?;

while let Ok(request) = request_receiver.recv() {
match request {
LocationRequest::UpdateOnce => {
manager.update_once()?;
}
LocationRequest::StartUpdates => {
manager.start_updates()?;
}
LocationRequest::StopUpdates => {
manager.stop_updates()?;
}
}
}

error!("Location request loop exited unexpectedly (the senders all died).");
Err(robius_location::Error::Unknown)
}


pub enum LocationRequest {
UpdateOnce,
StartUpdates,
StopUpdates,
}

static LOCATION_REQUEST_SENDER: Mutex<Option<Sender<LocationRequest>>> = Mutex::new(None);
/// A wrapper struct for storing the singleton location manager in Cx globals.
#[derive(Default)]
struct LocationManagerGlobal(Option<Manager>);

/// Submits a request to start, stop, or get a single new location update(s).
pub fn request_location_update(request: LocationRequest) {
if let Some(sender) = LOCATION_REQUEST_SENDER.lock().unwrap().as_ref() {
if let Err(err) = sender.send(request) {
error!("Error sending location request: {err:?}");
pub fn request_location_update(cx: &mut Cx, request: LocationRequest) {
let Some(manager) = cx.global::<LocationManagerGlobal>().0.as_mut() else {
error!("Location subscriber not initialized on this thread.");
Cx::post_action(LocationAction::Error(robius_location::Error::Unknown));
return;
};
let (result, show_error) = match request {
LocationRequest::UpdateOnce => (manager.update_once(), true),
LocationRequest::StartUpdates => (manager.start_updates(), true),
LocationRequest::StopUpdates => (manager.stop_updates(), false),
};
if let Err(e) = result {
error!("Error handling location request: {e:?}");
if show_error {
Cx::post_action(LocationAction::Error(e));
}
} else {
error!("No location request sender available.");
}
}

/// Spawns a thread to listen for location requests and updates to the latest location.
/// Initializes the location manager and requests a single location update.
///
/// This will request a single location update immediately upon starting.
/// To request additional updates, use [`request_location_update`].
///
/// It is okay to call this function multiple times, as it will only re-initialize
/// the location subscriber thread if it has not been initialized yet
/// or if it has died and needs to be restarted.
///
/// This function requires passing in a reference to `Cx`,
/// which isn't used, but acts as a guarantee that this function
/// must only be called by the main UI thread.
pub fn init_location_subscriber(_cx: &mut Cx) -> Result<(), robius_location::Error> {
let mut lrs = LOCATION_REQUEST_SENDER.lock().unwrap();
if lrs.is_some() {
log!("Location subscriber already initialized.");
/// It is okay to call this function multiple times; it only initializes the manager once.
pub fn init_location_subscriber(cx: &mut Cx) -> Result<(), robius_location::Error> {
let lm = cx.global::<LocationManagerGlobal>();
if lm.0.is_some() {
// log!("Location subscriber already initialized.");
return Ok(());
}
let manager = ManagerWrapper(Manager::new(LocationHandler)?);
manager.request_authorization(Access::Foreground, Accuracy::Precise)?;
let _ = manager.update_once();

let (request_sender, request_receiver) = mpsc::channel::<LocationRequest>();
*lrs = Some(request_sender);
std::thread::spawn(|| location_request_loop(request_receiver, manager));
let new_manager = Manager::new(LocationHandler)?;
new_manager.request_authorization(Access::Foreground, Accuracy::Precise)?;
let _ = new_manager.update_once();
lm.0 = Some(new_manager);
Ok(())
}

struct ManagerWrapper(Manager);
unsafe impl Send for ManagerWrapper {}
unsafe impl Sync for ManagerWrapper {}
impl std::ops::Deref for ManagerWrapper {
type Target = Manager;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for ManagerWrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
4 changes: 2 additions & 2 deletions src/room/room_input_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,12 @@ impl RoomInputBar {
if let Err(_e) = init_location_subscriber(cx) {
error!("Failed to initialize location subscriber");
enqueue_popup_notification(
"Failed to initialize location services.",
"Couldn't start location services. Please try again.",
PopupKind::Error,
None,
);
}
self.view.location_preview(cx, ids!(location_preview)).show();
self.view.location_preview(cx, ids!(location_preview)).show(cx);
self.redraw(cx);
}

Expand Down
Loading
Loading