use std::sync::Arc;
use windows::{
Devices::Geolocation::{
BasicGeoposition, GeolocationAccessStatus, Geolocator as WindowsGeolocator,
PositionAccuracy, PositionChangedEventArgs, PositionStatus, StatusChangedEventArgs,
},
Foundation::TypedEventHandler,
};
use crate::geolocation::core::{Error, Event, Geocoordinates, PowerMode, Status};
pub struct Geolocator {
device_geolocator: WindowsGeolocator,
}
impl Geolocator {
pub fn new() -> Result<Self, Error> {
let access_status = match WindowsGeolocator::RequestAccessAsync() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
let access_status = match access_status.get() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
if access_status != GeolocationAccessStatus::Allowed {
return Err(Error::AccessDenied);
}
let device_geolocator =
WindowsGeolocator::new().map_err(|e| Error::DeviceError(e.to_string()))?;
Ok(Self { device_geolocator })
}
}
pub async fn get_coordinates(geolocator: &Geolocator) -> Result<Geocoordinates, Error> {
let location = geolocator.device_geolocator.GetGeopositionAsync();
let location = match location {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
let location = match location.get() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
let location_coordinate = match location.Coordinate() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
let location_point = match location_coordinate.Point() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
let position = match location_point.Position() {
Ok(v) => v,
Err(e) => return Err(Error::DeviceError(e.to_string())),
};
Ok(position.into())
}
pub fn listen(
geolocator: &Geolocator,
callback: Arc<dyn Fn(Event) + Send + Sync>,
) -> Result<(), Error> {
let callback1 = callback.clone();
let callback2 = callback.clone();
geolocator
.device_geolocator
.StatusChanged(&TypedEventHandler::new(
move |_geolocator: &Option<WindowsGeolocator>,
event_args: &Option<StatusChangedEventArgs>| {
if let Some(status) = event_args {
let status = status.Status()?;
(callback1)(Event::StatusChanged(status.into()))
}
Ok(())
},
))
.map_err(|e| Error::DeviceError(e.to_string()))?;
geolocator
.device_geolocator
.PositionChanged(&TypedEventHandler::new(
move |_geolocator: &Option<WindowsGeolocator>,
event_args: &Option<PositionChangedEventArgs>| {
if let Some(position) = event_args {
let position = position.Position()?.Coordinate()?.Point()?.Position()?;
(callback2)(Event::NewGeocoordinates(position.into()))
}
Ok(())
},
))
.map_err(|e| Error::DeviceError(e.to_string()))?;
Ok(())
}
pub fn set_power_mode(geolocator: &mut Geolocator, power_mode: PowerMode) -> Result<(), Error> {
match power_mode {
PowerMode::High => geolocator
.device_geolocator
.SetDesiredAccuracy(PositionAccuracy::High)
.map_err(|e| Error::DeviceError(e.to_string()))?,
PowerMode::Low => geolocator
.device_geolocator
.SetDesiredAccuracy(PositionAccuracy::Default)
.map_err(|e| Error::DeviceError(e.to_string()))?,
};
Ok(())
}
impl From<PositionStatus> for Status {
fn from(value: PositionStatus) -> Self {
match value.0 {
0 => Status::Ready,
1 => Status::Initializing,
3 => Status::Disabled,
5 => Status::NotAvailable,
_ => Status::Unknown,
}
}
}
impl From<BasicGeoposition> for Geocoordinates {
fn from(position: BasicGeoposition) -> Self {
Geocoordinates {
latitude: position.Latitude,
longitude: position.Longitude,
}
}
}