1#![cfg(target_os = "android")]
2
3use freya_components::theming::{
4 hooks::use_init_theme,
5 themes::light_theme,
6};
7use freya_core::{
8 integration::*,
9 prelude::*,
10};
11use freya_winit::{
12 integration::is_ime_role,
13 plugins::{
14 FreyaPlugin,
15 PluginEvent,
16 PluginHandle,
17 },
18};
19use winit::platform::android::activity::AndroidApp;
20
21mod keyboard;
22mod status_bar;
23
24pub trait AndroidExt {
26 fn set_status_bar_light(&self, light: bool) -> Result<(), jni::errors::Error>;
28
29 fn show_keyboard(&self) -> Result<(), jni::errors::Error>;
31
32 fn hide_keyboard(&self) -> Result<(), jni::errors::Error>;
34}
35
36impl AndroidExt for Platform {
37 fn set_status_bar_light(&self, light: bool) -> Result<(), jni::errors::Error> {
38 if let Some(app) = try_consume_root_context::<AndroidApp>() {
39 status_bar::set_status_bar_light(&app, light)
40 } else {
41 Ok(())
42 }
43 }
44
45 fn show_keyboard(&self) -> Result<(), jni::errors::Error> {
46 if let Some(app) = try_consume_root_context::<AndroidApp>() {
47 keyboard::show_keyboard(&app)
48 } else {
49 Ok(())
50 }
51 }
52
53 fn hide_keyboard(&self) -> Result<(), jni::errors::Error> {
54 if let Some(app) = try_consume_root_context::<AndroidApp>() {
55 keyboard::hide_keyboard(&app)
56 } else {
57 Ok(())
58 }
59 }
60}
61
62pub struct AndroidPlugin {
68 app: AndroidApp,
69}
70
71impl AndroidPlugin {
72 pub fn new(app: AndroidApp) -> Self {
73 Self { app }
74 }
75}
76
77impl FreyaPlugin for AndroidPlugin {
78 fn plugin_id(&self) -> &'static str {
79 "android"
80 }
81
82 fn on_event(&mut self, event: &mut PluginEvent, _handle: PluginHandle) {
83 if let PluginEvent::RunnerCreated { runner } = event {
84 let app = self.app.clone();
85 runner.provide_root_context(move || app);
86 }
87 }
88
89 fn root_component(&self, root: Element) -> Element {
90 AndroidRoot { inner: root }.into_element()
91 }
92}
93
94#[derive(Clone)]
96struct AndroidRoot {
97 inner: Element,
98}
99
100impl PartialEq for AndroidRoot {
101 fn eq(&self, _other: &Self) -> bool {
102 true
103 }
104}
105
106impl Component for AndroidRoot {
107 fn render(&self) -> impl IntoElement {
108 let theme = use_init_theme(light_theme);
109
110 use_side_effect(move || {
112 let platform = Platform::get();
113 let is_light = theme.read().name == "light";
114 if let Err(err) = platform.set_status_bar_light(is_light) {
115 tracing::error!("Failed to set status bar appearance: {err:?}");
116 }
117 });
118
119 use_side_effect(move || {
121 let platform = Platform::get();
122 let focused_node = platform.focused_accessibility_node.read();
123 let result = if is_ime_role(focused_node.role()) {
124 platform.show_keyboard()
125 } else {
126 platform.hide_keyboard()
127 };
128 if let Err(err) = result {
129 tracing::error!("Failed to toggle soft keyboard: {err:?}");
130 }
131 });
132
133 let on_global_pointer_down = move |_: Event<PointerEventData>| {
134 let platform = Platform::get();
135 if let Err(err) = platform.hide_keyboard() {
136 tracing::error!("Failed to hide soft keyboard: {err:?}");
137 }
138 };
139
140 rect()
141 .expanded()
142 .on_global_pointer_down(on_global_pointer_down)
143 .child(self.inner.clone())
144 }
145}