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}