use bevy::prelude::*;
#[cfg(target_os = "ios")]
mod backend {
unsafe extern "C" {
pub fn att_request();
pub fn att_status() -> i32;
}
}
#[cfg(not(target_os = "ios"))]
mod backend {
use std::sync::atomic::{AtomicI32, Ordering};
static STATUS: AtomicI32 = AtomicI32::new(0);
fn env_status() -> i32 {
match std::env::var("BEVY_IOS_FAKE_ATT")
.unwrap_or_default()
.to_ascii_lowercase()
.as_str()
{
"restricted" => 1,
"denied" => 2,
"authorized" => 3,
"notdetermined" => 0,
_ => 3, }
}
pub unsafe fn att_request() {
STATUS.store(env_status(), Ordering::SeqCst);
}
pub unsafe fn att_status() -> i32 {
STATUS.load(Ordering::SeqCst)
}
}
#[derive(Resource, Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TrackingStatus {
#[default]
NotDetermined,
Restricted,
Denied,
Authorized,
}
impl TrackingStatus {
fn from_i32(v: i32) -> TrackingStatus {
match v {
1 => TrackingStatus::Restricted,
2 => TrackingStatus::Denied,
3 => TrackingStatus::Authorized,
_ => TrackingStatus::NotDetermined,
}
}
pub fn is_authorized(self) -> bool {
matches!(self, TrackingStatus::Authorized)
}
pub fn is_determined(self) -> bool {
!matches!(self, TrackingStatus::NotDetermined)
}
}
#[derive(Message, Clone, Debug)]
pub struct RequestTracking;
#[derive(Message, Clone, Debug)]
pub struct TrackingStatusChanged(pub TrackingStatus);
pub struct AttPlugin;
impl Plugin for AttPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<TrackingStatus>()
.add_message::<RequestTracking>()
.add_message::<TrackingStatusChanged>()
.add_systems(Update, (pump_requests, poll_status).chain());
}
}
fn pump_requests(mut requests: MessageReader<RequestTracking>) {
let mut wanted = false;
for _ in requests.read() {
wanted = true;
}
if wanted {
unsafe { backend::att_request() };
}
}
fn poll_status(
mut status: ResMut<TrackingStatus>,
mut changed: MessageWriter<TrackingStatusChanged>,
) {
let current = TrackingStatus::from_i32(unsafe { backend::att_status() });
if current != *status {
*status = current;
changed.write(TrackingStatusChanged(current));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_resolves_status_and_emits_change() {
unsafe { std::env::set_var("BEVY_IOS_FAKE_ATT", "denied") };
let mut app = App::new();
app.add_plugins(MinimalPlugins).add_plugins(AttPlugin);
app.update();
assert_eq!(
*app.world().resource::<TrackingStatus>(),
TrackingStatus::NotDetermined
);
app.world_mut()
.resource_mut::<Messages<RequestTracking>>()
.write(RequestTracking);
app.update(); app.update();
assert_eq!(
*app.world().resource::<TrackingStatus>(),
TrackingStatus::Denied
);
assert!(TrackingStatus::Authorized.is_authorized());
assert!(!TrackingStatus::NotDetermined.is_determined());
unsafe { std::env::remove_var("BEVY_IOS_FAKE_ATT") };
}
}