Skip to main content

location_browser/
location_browser.rs

1//! Demonstrates the `location-browser` feature: acquire device coordinates via
2//! the browser's standard Geolocation API.
3//!
4//! What this example covers:
5//!   - All three [`PageTemplate`] variants (Default, Tickbox, Custom)
6//!   - Blocking call ([`__location__`]) — safe from any context
7//!   - Async call ([`__location_async__`]) — preferred inside `#[tokio::main]`
8//!   - The `#[browser]` attribute macro shorthand (compile check only)
9//!   - [`LocationData`] field access
10//!   - Error handling with [`LocationError`]
11//!
12//! > **Note:** This example opens the system's default browser and spins up a
13//! > temporary local HTTP server.  It requires a browser with Geolocation API
14//! > support and an internet connection may be needed for the browser to provide
15//! > accurate coordinates.  The call blocks until the user grants or denies the
16//! > permission prompt.
17//!
18//! Run with:
19//! ```sh
20//! cargo run --example location_browser --features location
21//! ```
22
23use toolkit_zero::location::browser::{
24    PageTemplate, LocationData, LocationError,
25    __location__, __location_async__,
26    browser,
27};
28
29// ─── PageTemplate construction ────────────────────────────────────────────────
30
31/// Shows how to build each [`PageTemplate`] variant.
32/// This function is intentionally `#[allow(dead_code)]` — it is a compile-check
33/// and documentation aid; the templates are not actually served in this example.
34#[allow(dead_code)]
35fn template_examples() {
36    // 1. Built-in default — a simple single-button consent page.
37    let _default = PageTemplate::default();
38
39    // 2. Default with a custom title.
40    let _custom_title = PageTemplate::Default {
41        title:     Some("My App — Share Location".into()),
42        body_text: Some("This app needs your location to show nearby results.".into()),
43    };
44
45    // 3. Tickbox variant — user must check a box before the button becomes active.
46    let _tickbox = PageTemplate::Tickbox {
47        title:        Some("Location Consent".into()),
48        body_text:    None,
49        consent_text: Some("I agree to share my location with this application.".into()),
50    };
51
52    // 4. Fully custom HTML — place `{}` where the capture button should appear.
53    let _custom_html = PageTemplate::Custom(
54        r#"<!DOCTYPE html>
55<html>
56<head><title>Where are you?</title></head>
57<body>
58  <h1>Share your location</h1>
59  {}
60  <p id="status"></p>
61</body>
62</html>"#
63        .into(),
64    );
65}
66
67// ─── #[browser] macro compile check ──────────────────────────────────────────
68
69/// Compile-check for the `#[browser]` attribute macro.
70/// Each decorated `fn` is replaced in-place with a location-capture statement.
71/// The function **name** becomes the binding that holds the
72/// [`LocationData`] (or propagates the [`LocationError`]).
73#[allow(dead_code)]
74async fn browser_macro_forms() -> Result<LocationData, LocationError> {
75    // Async, plain Default template.
76    #[browser]
77    fn loc() {}
78    Ok(loc)
79}
80
81#[allow(dead_code)]
82async fn browser_macro_tickbox() -> Result<LocationData, LocationError> {
83    // Async, Tickbox with custom consent text.
84    #[browser(tickbox, title = "Verify Location", consent = "I agree")]
85    fn loc() {}
86    Ok(loc)
87}
88
89#[allow(dead_code)]
90fn browser_macro_sync() -> Result<LocationData, LocationError> {
91    // Blocking, custom title only.
92    #[browser(sync, title = "My App")]
93    fn loc() {}
94    Ok(loc)
95}
96
97// ─── Runtime helpers ──────────────────────────────────────────────────────────
98
99/// Pretty-print a [`LocationData`] value.
100fn print_location(label: &str, data: &LocationData) {
101    println!("{label}");
102    println!("  latitude  : {:.6}°", data.latitude);
103    println!("  longitude : {:.6}°", data.longitude);
104    println!("  accuracy  : {:.1} m (95% confidence radius)", data.accuracy);
105    if let Some(alt) = data.altitude {
106        println!("  altitude  : {alt:.1} m");
107    }
108    if let Some(spd) = data.speed {
109        println!("  speed     : {spd:.1} m/s");
110    }
111}
112
113// ─── Entry point ──────────────────────────────────────────────────────────────
114
115#[tokio::main]
116async fn main() {
117    println!("=== location_browser example ===\n");
118
119    // ── 1. Async call (preferred inside #[tokio::main]) ───────────────────────
120    println!("Opening browser to acquire location (async)…");
121    match __location_async__(PageTemplate::default()).await {
122        Ok(data) => print_location("Async result:", &data),
123        Err(LocationError::PermissionDenied) => {
124            eprintln!("Location permission denied — skipping async call.");
125        }
126        Err(e) => eprintln!("Async location error: {e}"),
127    }
128
129    println!();
130
131    // ── 2. Blocking call (also safe inside #[tokio::main]) ────────────────────
132    // __location__ detects the existing Tokio runtime and offloads to a
133    // dedicated OS thread automatically — no runtime nesting issue.
134    println!("Opening browser to acquire location (blocking)…");
135    let template = PageTemplate::Tickbox {
136        title:        Some("Confirm Location".into()),
137        body_text:    None,
138        consent_text: Some("I allow this example to read my location.".into()),
139    };
140    match __location__(template) {
141        Ok(data) => print_location("Blocking result:", &data),
142        Err(LocationError::PermissionDenied) => {
143            eprintln!("Location permission denied — skipping blocking call.");
144        }
145        Err(e) => eprintln!("Blocking location error: {e}"),
146    }
147}