use std::sync::{
Arc, Mutex, OnceLock,
atomic::{AtomicU64, Ordering},
};
use cranpose_core::{State, rememberEventStream};
use crate::registry::ServiceRegistry;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MicrophonePermission {
NotAsked,
Granted,
Denied,
Blocked,
}
pub trait MicrophoneAccess: Send + Sync {
fn permission(&self) -> MicrophonePermission;
fn request(&self);
fn open_settings(&self);
fn prepare_recording(&self) -> Result<(), String> {
Ok(())
}
}
static PLATFORM: ServiceRegistry<dyn MicrophoneAccess> = ServiceRegistry::new();
pub fn set_platform_microphone_access(access: Option<Arc<dyn MicrophoneAccess>>) {
match access {
Some(access) => PLATFORM.set(access),
None => PLATFORM.clear(),
}
publish_microphone_permission();
}
pub fn microphone_permission() -> MicrophonePermission {
PLATFORM
.get()
.map_or(MicrophonePermission::Granted, |access| access.permission())
}
pub fn request_microphone_permission() {
if let Some(access) = PLATFORM.get()
&& matches!(
access.permission(),
MicrophonePermission::NotAsked | MicrophonePermission::Denied
)
{
access.request();
}
}
pub fn open_microphone_settings() {
if let Some(access) = PLATFORM.get() {
access.open_settings();
}
}
pub fn prepare_microphone() -> Result<(), String> {
PLATFORM
.get()
.map_or(Ok(()), |access| access.prepare_recording())
}
type Observer = Arc<dyn Fn(MicrophonePermission) + Send + Sync>;
static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
fn observers() -> &'static Mutex<Vec<(u64, Observer)>> {
static OBSERVERS: OnceLock<Mutex<Vec<(u64, Observer)>>> = OnceLock::new();
OBSERVERS.get_or_init(|| Mutex::new(Vec::new()))
}
pub fn publish_microphone_permission() {
let permission = microphone_permission();
let listeners: Vec<Observer> = observers()
.lock()
.map(|observers| observers.iter().map(|(_, o)| Arc::clone(o)).collect())
.unwrap_or_default();
for observer in listeners {
observer(permission);
}
}
pub struct MicrophonePermissionObserver {
id: u64,
}
impl Drop for MicrophonePermissionObserver {
fn drop(&mut self) {
if let Ok(mut observers) = observers().lock() {
observers.retain(|(id, _)| *id != self.id);
}
}
}
pub fn observe_microphone_permission(
observer: impl Fn(MicrophonePermission) + Send + Sync + 'static,
) -> MicrophonePermissionObserver {
let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
let observer: Observer = Arc::new(observer);
if let Ok(mut observers) = observers().lock() {
observers.push((id, Arc::clone(&observer)));
}
observer(microphone_permission());
MicrophonePermissionObserver { id }
}
#[expect(non_snake_case)]
#[track_caller]
pub fn rememberMicrophonePermission() -> State<MicrophonePermission> {
let lifecycle = crate::rememberLifecycleState().get();
let updates = rememberEventStream(lifecycle, move |sender| {
observe_microphone_permission(move |permission| sender.send(permission))
});
cranpose_core::collectAsState(updates, lifecycle, microphone_permission())
}
#[cfg(test)]
#[path = "tests/microphone_permission_tests.rs"]
mod tests;