Skip to main content

lingxia_platform/traits/
app_runtime.rs

1use std::io::Read;
2use std::path::{Path, PathBuf};
3
4use crate::AssetFileEntry;
5use crate::error::PlatformError;
6
7use super::device::{Device, DeviceHardware};
8use super::file::FileService;
9use super::location::Location;
10use super::media_interaction::{MediaInteraction, MediaKind};
11use super::media_runtime::MediaRuntime;
12use super::network::Network;
13use super::secure_store::SecureStore;
14use super::share::ShareService;
15use super::ui::{SurfacePresenter, UIUpdate, UserFeedback};
16use super::update::UpdateService;
17use super::wifi::Wifi;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum AnimationType {
21    None = 0,
22    Forward = 1,
23    Backward = 2,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub enum LxAppOpenMode {
28    #[default]
29    Normal = 0,
30    Panel = 1,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum OpenUrlTarget {
35    External = 0,
36    SelfTarget = 1,
37    /// Open a new browser tab unconditionally (skips "navigate current tab" heuristic).
38    NewBrowserTab = 2,
39}
40
41impl OpenUrlTarget {
42    pub fn parse(raw: Option<&str>) -> Self {
43        match raw.map(|v| v.trim().to_ascii_lowercase()) {
44            Some(v) if v == "self" => Self::SelfTarget,
45            Some(v) if v == "new_browser_tab" => Self::NewBrowserTab,
46            Some(v) if v == "external" => Self::External,
47            Some(v) => {
48                log::warn!("Invalid openURL target='{}', fallback to external", v);
49                Self::External
50            }
51            None => Self::External,
52        }
53    }
54}
55
56#[derive(Debug, Clone)]
57pub struct OpenUrlRequest {
58    pub owner_appid: String,
59    pub owner_session_id: u64,
60    pub url: String,
61    pub target: OpenUrlTarget,
62}
63
64impl From<i32> for AnimationType {
65    fn from(value: i32) -> Self {
66        match value {
67            1 => AnimationType::Forward,
68            2 => AnimationType::Backward,
69            _ => AnimationType::None,
70        }
71    }
72}
73
74pub trait AppRuntime:
75    Send
76    + Sync
77    + MediaInteraction
78    + MediaRuntime
79    + Network
80    + SurfacePresenter
81    + Device
82    + DeviceHardware
83    + SecureStore
84    + ShareService
85    + FileService
86    + Location
87    + UIUpdate
88    + UpdateService
89    + UserFeedback
90    + Wifi
91    + 'static
92{
93    /// Reads an asset file as a streaming reader.
94    fn read_asset<'a>(&'a self, path: &str) -> Result<Box<dyn Read + 'a>, PlatformError>;
95
96    /// Iterates over files in an asset directory.
97    fn asset_dir_iter<'a>(
98        &'a self,
99        asset_dir: &str,
100    ) -> Box<dyn Iterator<Item = Result<AssetFileEntry<'a>, PlatformError>> + 'a>;
101
102    /// Returns the app's data directory path.
103    fn app_data_dir(&self) -> PathBuf;
104
105    /// Returns the app's cache directory path.
106    fn app_cache_dir(&self) -> PathBuf;
107
108    /// Obtains the application identifier.
109    fn get_app_identifier(&self) -> Result<String, PlatformError>;
110
111    /// Copies media from the system album to a local file.
112    fn copy_album_media_to_file(
113        &self,
114        uri: &str,
115        dest_path: &Path,
116        kind: MediaKind,
117    ) -> Result<(), PlatformError> {
118        MediaRuntime::copy_album_media_to_file(self, uri, dest_path, kind)
119    }
120
121    /// Returns the current system locale.
122    fn get_system_locale(&self) -> &str;
123
124    /// Show the UI container for the given LxApp and route.
125    fn show_lxapp(
126        &self,
127        appid: String,
128        path: String,
129        session_id: u64,
130        open_mode: LxAppOpenMode,
131        panel_id: String,
132    ) -> Result<(), PlatformError>;
133
134    /// Hide the UI container for the given LxApp (does not destroy its runtime state).
135    fn hide_lxapp(&self, appid: String, session_id: u64) -> Result<(), PlatformError>;
136
137    /// Exits the host app.
138    fn exit(&self) -> Result<(), PlatformError>;
139
140    // Tray / badge chrome. These are cosmetic enhancements, so platforms that
141    // lack the chrome (e.g. no menu-bar tray on mobile) no-op rather than error —
142    // portable code can call them unconditionally. A supporting platform returns
143    // Err only on genuine failure.
144
145    /// Set the tray (menu-bar / system-tray) badge. Desktop only; no-op elsewhere.
146    fn set_tray_badge(&self, _text: &str) -> Result<(), PlatformError> {
147        Ok(())
148    }
149
150    /// Set the tray icon (a resource path). Desktop only; no-op elsewhere.
151    fn set_tray_icon(&self, _icon: &str) -> Result<(), PlatformError> {
152        Ok(())
153    }
154
155    /// Set the tray title (text beside the icon, macOS). Desktop only; no-op elsewhere.
156    fn set_tray_title(&self, _text: &str) -> Result<(), PlatformError> {
157        Ok(())
158    }
159
160    /// Set the app-icon badge: dock (macOS) / taskbar (Windows) / launcher icon
161    /// (iOS, Android). No-op on platforms where it is not yet wired.
162    fn set_app_badge(&self, _text: &str) -> Result<(), PlatformError> {
163        Ok(())
164    }
165
166    /// Replace the tray dropdown menu. `items_json` is a JSON array of
167    /// `{ label?, separator?, enabled?, checked? }`. Item clicks are delivered
168    /// back to JS by index. Desktop only; no-op elsewhere.
169    fn set_tray_menu(&self, _items_json: &str) -> Result<(), PlatformError> {
170        Ok(())
171    }
172
173    /// Show or hide the tray status item itself. Desktop only; no-op elsewhere.
174    fn set_tray_visible(&self, _visible: bool) -> Result<(), PlatformError> {
175        Ok(())
176    }
177
178    /// When intercepting, a left-click on the tray is delivered only to JS
179    /// (`lx.tray.onClick`) and does not run the tray's configured surface action.
180    /// Desktop only; no-op elsewhere.
181    fn set_tray_click_intercept(&self, _intercept: bool) -> Result<(), PlatformError> {
182        Ok(())
183    }
184
185    /// Navigates within the given LxApp using an animation.
186    fn navigate(
187        &self,
188        appid: String,
189        path: String,
190        animation_type: AnimationType,
191    ) -> Result<(), PlatformError>;
192
193    /// Opens the given URL according to the host policy for the requested target.
194    fn open_url(&self, req: OpenUrlRequest) -> Result<(), PlatformError>;
195
196    /// Gets the capsule button bounding rect in screen coordinates.
197    /// Returns JSON: {"width": f64, "height": f64, "top": f64, "right": f64, "bottom": f64, "left": f64}
198    fn get_capsule_rect(
199        &self,
200    ) -> impl std::future::Future<Output = Result<String, PlatformError>> + Send;
201}
202
203#[cfg(test)]
204mod tests {
205    use super::OpenUrlTarget;
206
207    #[test]
208    fn parse_supports_new_browser_tab() {
209        assert_eq!(
210            OpenUrlTarget::parse(Some("new_browser_tab")),
211            OpenUrlTarget::NewBrowserTab
212        );
213    }
214
215    #[test]
216    fn parse_unknown_falls_back_to_external() {
217        assert_eq!(
218            OpenUrlTarget::parse(Some("foobar")),
219            OpenUrlTarget::External
220        );
221    }
222}