inset_services/
system_chrome.rs1use inset_embedder::{AppLifecycleState, ApplicationSwitcherDescription, SystemUiOverlayStyle};
6use inset_foundation::{App, Handle, Listener};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum SystemUiOverlay {
13 Top,
16
17 Bottom,
20}
21
22#[derive(Default)]
25struct SystemChromeStyles {
26 pending_style: Option<SystemUiOverlayStyle>,
27 latest_style: Option<SystemUiOverlayStyle>,
28}
29
30pub struct SystemChrome;
33
34impl SystemChrome {
35 pub fn set_application_switcher_description(
41 app: &App,
42 description: &ApplicationSwitcherDescription,
43 ) {
44 if let Some(chrome) = app.platform().system_chrome() {
45 chrome.set_application_switcher_description(description);
46 }
47 }
48
49 pub fn set_system_ui_overlay_style(app: &mut App, style: &SystemUiOverlayStyle) {
69 let styles = app.singleton::<SystemChromeStyles>();
70 if app.get(styles).pending_style.is_some() {
71 app.get_mut(styles).pending_style = Some(*style);
73 return;
74 }
75 if Some(*style) == app.get(styles).latest_style {
76 return;
79 }
80 app.get_mut(styles).pending_style = Some(*style);
81 app.schedule_microtask(Listener::new(move |app| {
82 Self::send_pending_style(app, styles)
83 }));
84 }
85
86 fn send_pending_style(app: &mut App, styles: Handle<SystemChromeStyles>) {
88 let pending = app.get(styles).pending_style;
89 debug_assert!(pending.is_some());
90 if pending != app.get(styles).latest_style {
91 if let (Some(style), Some(chrome)) = (pending, app.platform().system_chrome()) {
92 chrome.set_overlay_style(&style);
93 }
94 app.get_mut(styles).latest_style = pending;
95 }
96 app.get_mut(styles).pending_style = None;
97 }
98
99 pub fn handle_app_lifecycle_state_changed(app: &mut App, state: AppLifecycleState) {
101 if state == AppLifecycleState::Detached {
104 let styles = app.singleton::<SystemChromeStyles>();
105 app.schedule_microtask(Listener::new(move |app| {
106 app.get_mut(styles).latest_style = None;
107 }));
108 }
109 }
110
111 pub fn latest_style(app: &mut App) -> Option<SystemUiOverlayStyle> {
114 let styles = app.singleton::<SystemChromeStyles>();
115 app.get(styles).latest_style
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use inset_foundation::AppCell;
122 use std::cell::RefCell;
123 use std::rc::Rc;
124
125 use inset_embedder::{Brightness, Color, SystemChrome as SystemChromeHost, TargetPlatform};
126 use inset_test::TestPlatform;
127
128 use super::*;
129
130 #[derive(Default)]
132 struct RecordingChrome {
133 styles: RefCell<Vec<SystemUiOverlayStyle>>,
134 }
135
136 impl SystemChromeHost for RecordingChrome {
137 fn set_overlay_style(&self, style: &SystemUiOverlayStyle) {
138 self.styles.borrow_mut().push(*style);
139 }
140
141 fn set_application_switcher_description(
142 &self,
143 _description: &ApplicationSwitcherDescription,
144 ) {
145 }
146 }
147
148 fn recording_app() -> (Rc<RecordingChrome>, Rc<AppCell>) {
149 let chrome = Rc::new(RecordingChrome::default());
150 let platform = TestPlatform::new()
151 .on(TargetPlatform::IOS)
152 .with_system_chrome(chrome.clone());
153 (chrome, AppCell::with_platform(Rc::new(platform)))
154 }
155
156 #[test]
157 fn light_and_dark_differ_only_in_the_two_status_bar_brightnesses() {
158 assert_eq!(
159 SystemUiOverlayStyle::LIGHT.status_bar_icon_brightness,
160 Some(Brightness::Light)
161 );
162 assert_eq!(
163 SystemUiOverlayStyle::LIGHT.status_bar_brightness,
164 Some(Brightness::Dark)
165 );
166 assert_eq!(
167 SystemUiOverlayStyle::DARK.status_bar_icon_brightness,
168 Some(Brightness::Dark)
169 );
170 assert_eq!(
171 SystemUiOverlayStyle::DARK.status_bar_brightness,
172 Some(Brightness::Light)
173 );
174 assert_eq!(
175 SystemUiOverlayStyle::LIGHT.system_navigation_bar_color,
176 SystemUiOverlayStyle::DARK.system_navigation_bar_color
177 );
178 assert_ne!(SystemUiOverlayStyle::LIGHT, SystemUiOverlayStyle::DARK);
179 }
180
181 #[test]
182 fn copy_with_replaces_only_what_it_is_given() {
183 let style = SystemUiOverlayStyle::LIGHT
184 .copy_with()
185 .status_bar_color(Color::new(0xFF112233));
186 assert_eq!(style.status_bar_color, Some(Color::new(0xFF112233)));
187 assert_eq!(
188 style.status_bar_icon_brightness,
189 SystemUiOverlayStyle::LIGHT.status_bar_icon_brightness
190 );
191 assert_eq!(SystemUiOverlayStyle::LIGHT.status_bar_color, None);
192 }
193
194 #[test]
196 fn only_the_last_style_of_a_turn_reaches_the_host() {
197 let (chrome, cell) = recording_app();
198 let mut app = cell.borrow_mut();
199 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
200 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::DARK);
201 assert!(
202 chrome.styles.borrow().is_empty(),
203 "nothing is sent before the microtask runs"
204 );
205 assert_eq!(SystemChrome::latest_style(&mut app), None);
206
207 app.drain_microtasks();
208 assert_eq!(
209 chrome.styles.borrow().as_slice(),
210 [SystemUiOverlayStyle::DARK]
211 );
212 assert_eq!(
213 SystemChrome::latest_style(&mut app),
214 Some(SystemUiOverlayStyle::DARK)
215 );
216 }
217
218 #[test]
219 fn a_style_already_in_effect_queues_nothing() {
220 let (chrome, cell) = recording_app();
221 let mut app = cell.borrow_mut();
222 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
223 app.drain_microtasks();
224
225 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
226 app.drain_microtasks();
227 assert_eq!(
228 chrome.styles.borrow().as_slice(),
229 [SystemUiOverlayStyle::LIGHT],
230 "an unchanged style does not reach the host again"
231 );
232
233 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::DARK);
234 app.drain_microtasks();
235 assert_eq!(
236 chrome.styles.borrow().as_slice(),
237 [SystemUiOverlayStyle::LIGHT, SystemUiOverlayStyle::DARK]
238 );
239 }
240
241 #[test]
243 fn the_style_is_sent_again_after_the_app_was_detached() {
244 let (chrome, cell) = recording_app();
245 let mut app = cell.borrow_mut();
246 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
247 app.drain_microtasks();
248 assert_eq!(chrome.styles.borrow().len(), 1);
249
250 SystemChrome::handle_app_lifecycle_state_changed(&mut app, AppLifecycleState::Detached);
252 app.drain_microtasks();
253 SystemChrome::handle_app_lifecycle_state_changed(&mut app, AppLifecycleState::Resumed);
254 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
255 app.drain_microtasks();
256 assert_eq!(chrome.styles.borrow().len(), 2);
257 }
258
259 #[test]
260 fn a_style_that_returns_to_the_latest_within_the_turn_sends_nothing() {
261 let (chrome, cell) = recording_app();
262 let mut app = cell.borrow_mut();
263 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
264 app.drain_microtasks();
265 chrome.styles.borrow_mut().clear();
266
267 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::DARK);
268 SystemChrome::set_system_ui_overlay_style(&mut app, &SystemUiOverlayStyle::LIGHT);
269 app.drain_microtasks();
270 assert!(chrome.styles.borrow().is_empty());
271 assert_eq!(
272 SystemChrome::latest_style(&mut app),
273 Some(SystemUiOverlayStyle::LIGHT)
274 );
275 }
276}