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    /// Open in the compact in-app browser as an API-managed aside tab. It uses
40    /// the one-row toolbar without address editing or user tab creation.
41    AsideBrowser = 3,
42}
43
44impl OpenUrlTarget {
45    pub fn parse(raw: Option<&str>) -> Self {
46        match raw.map(|v| v.trim().to_ascii_lowercase()) {
47            Some(v) if v == "self" => Self::SelfTarget,
48            Some(v) if v == "new_browser_tab" => Self::NewBrowserTab,
49            Some(v) if v == "aside" => Self::AsideBrowser,
50            Some(v) if v == "external" => Self::External,
51            Some(v) => {
52                log::warn!("Invalid openURL target='{}', fallback to external", v);
53                Self::External
54            }
55            None => Self::External,
56        }
57    }
58}
59
60#[derive(Debug, Clone)]
61pub struct OpenUrlRequest {
62    pub owner_appid: String,
63    pub owner_session_id: u64,
64    pub url: String,
65    pub target: OpenUrlTarget,
66}
67
68impl From<i32> for AnimationType {
69    fn from(value: i32) -> Self {
70        match value {
71            1 => AnimationType::Forward,
72            2 => AnimationType::Backward,
73            _ => AnimationType::None,
74        }
75    }
76}
77
78pub trait AppRuntime:
79    Send
80    + Sync
81    + MediaInteraction
82    + MediaRuntime
83    + Network
84    + SurfacePresenter
85    + Device
86    + DeviceHardware
87    + SecureStore
88    + ShareService
89    + FileService
90    + Location
91    + UIUpdate
92    + UpdateService
93    + UserFeedback
94    + Wifi
95    + 'static
96{
97    /// Reads an asset file as a streaming reader.
98    fn read_asset<'a>(&'a self, path: &str) -> Result<Box<dyn Read + 'a>, PlatformError>;
99
100    /// Iterates over files in an asset directory.
101    fn asset_dir_iter<'a>(
102        &'a self,
103        asset_dir: &str,
104    ) -> Box<dyn Iterator<Item = Result<AssetFileEntry<'a>, PlatformError>> + 'a>;
105
106    /// Returns the app's data directory path.
107    fn app_data_dir(&self) -> PathBuf;
108
109    /// Returns the app's cache directory path.
110    fn app_cache_dir(&self) -> PathBuf;
111
112    /// Obtains the application identifier.
113    fn get_app_identifier(&self) -> Result<String, PlatformError>;
114
115    /// Copies media from the system album to a local file.
116    fn copy_album_media_to_file(
117        &self,
118        uri: &str,
119        dest_path: &Path,
120        kind: MediaKind,
121    ) -> Result<(), PlatformError> {
122        MediaRuntime::copy_album_media_to_file(self, uri, dest_path, kind)
123    }
124
125    /// Returns the current system locale.
126    fn get_system_locale(&self) -> &str;
127
128    /// Show the UI container for the given LxApp and route.
129    fn show_lxapp(
130        &self,
131        appid: String,
132        path: String,
133        session_id: u64,
134        open_mode: LxAppOpenMode,
135        panel_id: String,
136    ) -> Result<(), PlatformError>;
137
138    /// Hide the UI container for the given LxApp (does not destroy its runtime state).
139    fn hide_lxapp(&self, appid: String, session_id: u64) -> Result<(), PlatformError>;
140
141    /// Exits the host app.
142    fn exit(&self) -> Result<(), PlatformError>;
143
144    // Tray / badge chrome. These are cosmetic enhancements, so platforms that
145    // lack the chrome (e.g. no menu-bar tray on mobile) no-op rather than error —
146    // portable code can call them unconditionally. A supporting platform returns
147    // Err only on genuine failure.
148
149    /// Set the tray (menu-bar / system-tray) badge. Desktop only; no-op elsewhere.
150    fn set_tray_badge(&self, _text: &str) -> Result<(), PlatformError> {
151        Ok(())
152    }
153
154    /// Set the tray icon (a resource path). Desktop only; no-op elsewhere.
155    fn set_tray_icon(&self, _icon: &str) -> Result<(), PlatformError> {
156        Ok(())
157    }
158
159    /// Replace the resolved shell activator render list. Desktop skins only
160    /// render presentation metadata and report stable ids.
161    fn set_shell_activators(
162        &self,
163        _items: &[lingxia_shell::ResolvedShellActivator],
164    ) -> Result<(), PlatformError> {
165        Ok(())
166    }
167
168    /// Replace the ordered mixed user Pin list. Platform skins resolve visual
169    /// metadata only; target identity and the eight-item limit are shell-owned.
170    fn set_shell_pins(&self, _items: &[lingxia_shell::ShellPin]) -> Result<(), PlatformError> {
171        Ok(())
172    }
173
174    /// Set the tray title (text beside the icon, macOS). Desktop only; no-op elsewhere.
175    fn set_tray_title(&self, _text: &str) -> Result<(), PlatformError> {
176        Ok(())
177    }
178
179    /// Set the app-icon badge: dock (macOS) / taskbar (Windows) / launcher icon
180    /// (iOS, Android). No-op on platforms where it is not yet wired.
181    fn set_app_badge(&self, _text: &str) -> Result<(), PlatformError> {
182        Ok(())
183    }
184
185    /// Whether the app is registered to launch at system startup. Only reached
186    /// on macOS/Windows — `lx.app.autostart` is not registered elsewhere — so
187    /// the default is an error, not a no-op: a false answer here would be a lie.
188    fn autostart_is_enabled(&self) -> Result<bool, PlatformError> {
189        Err(PlatformError::NotSupported("autostart".to_string()))
190    }
191
192    /// Register or unregister the app as a per-user startup item.
193    fn autostart_set_enabled(&self, _enabled: bool) -> Result<(), PlatformError> {
194        Err(PlatformError::NotSupported("autostart".to_string()))
195    }
196
197    /// Replace the tray dropdown menu. `items_json` is a JSON array of
198    /// `{ label?, separator?, enabled?, checked? }`. Item clicks are delivered
199    /// back to JS by index. Desktop only; no-op elsewhere.
200    fn set_tray_menu(&self, _items_json: &str) -> Result<(), PlatformError> {
201        Ok(())
202    }
203
204    /// Show or hide the tray status item itself. Desktop only; no-op elsewhere.
205    fn set_tray_visible(&self, _visible: bool) -> Result<(), PlatformError> {
206        Ok(())
207    }
208
209    /// When intercepting, a left-click on the tray is delivered only to JS
210    /// (`lx.tray.onClick`) and does not run the tray's configured surface action.
211    /// Desktop only; no-op elsewhere.
212    fn set_tray_click_intercept(&self, _intercept: bool) -> Result<(), PlatformError> {
213        Ok(())
214    }
215
216    /// Navigates within the given LxApp using an animation.
217    fn navigate(
218        &self,
219        appid: String,
220        path: String,
221        animation_type: AnimationType,
222    ) -> Result<(), PlatformError>;
223
224    /// Opens the given URL according to the host policy for the requested target.
225    fn open_url(&self, req: OpenUrlRequest) -> Result<(), PlatformError>;
226
227    /// Gets the capsule button bounding rect in screen coordinates.
228    /// Returns JSON: {"width": f64, "height": f64, "top": f64, "right": f64, "bottom": f64, "left": f64}
229    fn get_capsule_rect(
230        &self,
231    ) -> impl std::future::Future<Output = Result<String, PlatformError>> + Send;
232}
233
234#[cfg(test)]
235mod tests {
236    use super::OpenUrlTarget;
237
238    #[test]
239    fn parse_supports_new_browser_tab() {
240        assert_eq!(
241            OpenUrlTarget::parse(Some("new_browser_tab")),
242            OpenUrlTarget::NewBrowserTab
243        );
244    }
245
246    #[test]
247    fn parse_unknown_falls_back_to_external() {
248        assert_eq!(
249            OpenUrlTarget::parse(Some("foobar")),
250            OpenUrlTarget::External
251        );
252    }
253}