use std::sync::{Arc, OnceLock, RwLock};
pub use super::accessibility::Priority;
use super::accessibility::{AccessibilityManager, Announcement};
fn get_accessibility() -> &'static Arc<RwLock<AccessibilityManager>> {
static ACCESSIBILITY: OnceLock<Arc<RwLock<AccessibilityManager>>> = OnceLock::new();
ACCESSIBILITY.get_or_init(|| Arc::new(RwLock::new(AccessibilityManager::new())))
}
pub fn announce(message: impl Into<String>) {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
manager.announce_polite(message);
}
pub fn announce_now(message: impl Into<String>) {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
manager.announce_assertive(message);
}
pub fn take_announcements() -> Vec<Announcement> {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
let announcements = manager.pending_announcements().to_vec();
manager.clear_announcements();
announcements
}
pub fn has_announcements() -> bool {
let manager = get_accessibility().read().unwrap_or_else(|e| {
crate::log_warn!("Accessibility read lock poisoned: {e}");
e.into_inner()
});
!manager.pending_announcements().is_empty()
}
pub fn set_reduced_motion(enabled: bool) {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
manager.set_reduce_motion(enabled);
}
pub fn prefers_reduced_motion() -> bool {
let manager = get_accessibility().read().unwrap_or_else(|e| {
crate::log_warn!("Accessibility read lock poisoned: {e}");
e.into_inner()
});
manager.prefers_reduced_motion()
}
pub fn set_high_contrast(enabled: bool) {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
manager.set_high_contrast(enabled);
}
pub fn is_high_contrast() -> bool {
let manager = get_accessibility().read().unwrap_or_else(|e| {
crate::log_warn!("Accessibility read lock poisoned: {e}");
e.into_inner()
});
manager.is_high_contrast()
}
pub fn set_accessibility_enabled(enabled: bool) {
let mut manager = get_accessibility().write().unwrap_or_else(|e| {
crate::log_error!("Accessibility lock poisoned: {e}");
e.into_inner()
});
manager.set_enabled(enabled);
}
pub fn is_accessibility_enabled() -> bool {
let manager = get_accessibility().read().unwrap_or_else(|e| {
crate::log_warn!("Accessibility read lock poisoned: {e}");
e.into_inner()
});
manager.is_enabled()
}
pub fn announce_button_clicked(label: &str) {
announce(format!("{} activated", label));
}
pub fn announce_checkbox_changed(label: &str, checked: bool) {
let state = if checked { "checked" } else { "unchecked" };
announce(format!("{}, {}", label, state));
}
pub fn announce_list_selection(label: &str, index: usize, total: usize) {
announce(format!("{}, {} of {}", label, index + 1, total));
}
pub fn announce_tab_changed(label: &str, index: usize, total: usize) {
announce(format!("Tab: {}, {} of {}", label, index + 1, total));
}
pub fn announce_error(message: &str) {
announce_now(format!("Error: {}", message));
}
pub fn announce_success(message: &str) {
announce(format!("Success: {}", message));
}
pub fn announce_loading(context: &str) {
announce(format!("Loading {}", context));
}
pub fn announce_loaded(context: &str, count: Option<usize>) {
match count {
Some(n) => announce(format!("{} loaded, {} items", context, n)),
None => announce(format!("{} loaded", context)),
}
}
pub fn announce_dialog_opened(title: &str) {
announce_now(format!("Dialog: {}", title));
}
pub fn announce_dialog_closed() {
announce("Dialog closed");
}
pub fn announce_validation_error(field: &str, message: &str) {
announce_now(format!("{}: {}", field, message));
}
pub fn announce_focus_region(region: &str) {
announce(format!("Moved to {}", region));
}
pub fn announce_progress(percent: u8, context: &str) {
announce(format!("{}: {}%", context, percent));
}
pub fn announce_progress_complete(context: &str) {
announce(format!("{}: Complete", context));
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
fn setup() {
set_accessibility_enabled(true);
let _ = take_announcements(); set_reduced_motion(false);
set_high_contrast(false);
}
#[test]
#[serial]
fn test_announce() {
setup();
announce("Test message");
let announcements = take_announcements();
assert_eq!(announcements.len(), 1);
assert_eq!(announcements[0].message, "Test message");
assert_eq!(announcements[0].priority, Priority::Polite);
}
#[test]
#[serial]
fn test_announce_now() {
setup();
announce_now("Urgent message");
let announcements = take_announcements();
assert_eq!(announcements.len(), 1);
assert_eq!(announcements[0].message, "Urgent message");
assert_eq!(announcements[0].priority, Priority::Assertive);
}
#[test]
#[serial]
fn test_take_clears_queue() {
setup();
announce("Message 1");
announce("Message 2");
let first = take_announcements();
assert_eq!(first.len(), 2);
let second = take_announcements();
assert!(second.is_empty());
}
#[test]
#[serial]
fn test_reduced_motion() {
setup();
set_reduced_motion(true);
assert!(prefers_reduced_motion());
set_reduced_motion(false);
assert!(!prefers_reduced_motion());
}
#[test]
#[serial]
fn test_high_contrast() {
setup();
set_high_contrast(true);
assert!(is_high_contrast());
set_high_contrast(false);
assert!(!is_high_contrast());
}
#[test]
#[serial]
fn test_disabled_no_announcements() {
setup();
set_accessibility_enabled(false);
announce("Should not appear");
let announcements = take_announcements();
assert!(announcements.is_empty());
set_accessibility_enabled(true);
}
#[test]
#[serial]
fn test_button_clicked() {
setup();
announce_button_clicked("Submit");
let announcements = take_announcements();
assert_eq!(announcements[0].message, "Submit activated");
}
#[test]
#[serial]
fn test_checkbox_changed() {
setup();
announce_checkbox_changed("Accept terms", true);
let announcements = take_announcements();
assert_eq!(announcements[0].message, "Accept terms, checked");
}
#[test]
#[serial]
fn test_list_selection() {
setup();
announce_list_selection("Item A", 0, 5);
let announcements = take_announcements();
assert_eq!(announcements[0].message, "Item A, 1 of 5");
}
#[test]
#[serial]
fn test_error_announcement() {
setup();
announce_error("Invalid email");
let announcements = take_announcements();
assert_eq!(announcements[0].message, "Error: Invalid email");
assert_eq!(announcements[0].priority, Priority::Assertive);
}
#[test]
#[serial]
fn test_progress_announcement() {
setup();
announce_progress(50, "Upload");
let announcements = take_announcements();
assert_eq!(announcements[0].message, "Upload: 50%");
}
}