1use crate::{EventKitError, Result};
7use objc2::rc::Retained;
8use objc2_core_location::{CLLocationCoordinate2D, CLLocationManager};
9use std::time::Duration;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum LocationAuthorizationStatus {
14 NotDetermined,
15 Restricted,
16 Denied,
17 Authorized,
18}
19
20#[derive(Debug, Clone, Copy)]
22pub struct Coordinate {
23 pub latitude: f64,
24 pub longitude: f64,
25}
26
27pub struct LocationManager {
32 manager: Retained<CLLocationManager>,
33}
34
35impl LocationManager {
36 pub fn new() -> Self {
38 let manager = unsafe { CLLocationManager::new() };
39 Self { manager }
40 }
41
42 pub fn authorization_status(&self) -> LocationAuthorizationStatus {
44 let status = unsafe { self.manager.authorizationStatus() };
45 match status.0 {
49 0 => LocationAuthorizationStatus::NotDetermined,
50 1 => LocationAuthorizationStatus::Restricted,
51 2 => LocationAuthorizationStatus::Denied,
52 3 | 4 => LocationAuthorizationStatus::Authorized,
53 _ => LocationAuthorizationStatus::Denied,
54 }
55 }
56
57 pub fn request_when_in_use_authorization(&self) {
62 unsafe {
63 self.manager.requestWhenInUseAuthorization();
64 }
65 }
66
67 pub fn cached_location(&self) -> Option<Coordinate> {
71 let location = unsafe { self.manager.location() }?;
72 let coord: CLLocationCoordinate2D = unsafe { location.coordinate() };
73 Some(Coordinate {
74 latitude: coord.latitude,
75 longitude: coord.longitude,
76 })
77 }
78
79 pub fn get_current_location(&self, timeout: Duration) -> Result<Coordinate> {
84 match self.authorization_status() {
85 LocationAuthorizationStatus::NotDetermined => {
86 self.request_when_in_use_authorization();
87 std::thread::sleep(Duration::from_millis(500));
89 if self.authorization_status() != LocationAuthorizationStatus::Authorized {
90 return Err(EventKitError::AuthorizationDenied);
91 }
92 }
93 LocationAuthorizationStatus::Denied => {
94 return Err(EventKitError::AuthorizationDenied);
95 }
96 LocationAuthorizationStatus::Restricted => {
97 return Err(EventKitError::AuthorizationRestricted);
98 }
99 LocationAuthorizationStatus::Authorized => {}
100 }
101
102 unsafe {
104 self.manager.startUpdatingLocation();
105 }
106
107 let start = std::time::Instant::now();
110 let result = loop {
111 if let Some(location) = self.cached_location() {
112 break Some(location);
113 }
114
115 if start.elapsed() >= timeout {
116 break None;
117 }
118
119 std::thread::sleep(Duration::from_millis(100));
121 };
122
123 unsafe {
124 self.manager.stopUpdatingLocation();
125 }
126
127 result.ok_or_else(|| EventKitError::EventKitError("Location request timed out".into()))
128 }
129}
130
131impl Default for LocationManager {
132 fn default() -> Self {
133 Self::new()
134 }
135}