use std::io::Read;
use std::path::{Path, PathBuf};
use crate::AssetFileEntry;
use crate::error::PlatformError;
use super::PlatformFuture;
use super::clipboard::ClipboardService;
use super::device::{Device, DeviceHardware};
use super::file::FileService;
use super::location::Location;
use super::media_interaction::{MediaInteraction, MediaKind};
use super::media_runtime::MediaRuntime;
use super::network::Network;
use super::secure_store::SecureStore;
use super::share::ShareService;
use super::ui::{SurfacePresenter, UIUpdate, UserFeedback};
use super::update::UpdateService;
use super::wifi::Wifi;
pub const ACTIVATION_ENVELOPE: &str = "lxnotify:v1:";
pub fn wrap_activation(token: &str) -> String {
format!("{ACTIVATION_ENVELOPE}{token}")
}
pub fn unwrap_activation(payload: &str) -> Option<&str> {
payload
.trim()
.strip_prefix(ACTIVATION_ENVELOPE)
.filter(|token| !token.is_empty())
}
#[derive(Debug, Clone)]
pub struct LocalNotificationShow {
pub id: String,
pub title: String,
pub body: String,
pub activation_token: String,
pub deliver_at_ms: Option<u64>,
pub silent: bool,
}
#[derive(Debug, Clone)]
pub struct DesktopBannerAction {
pub id: String,
pub label: String,
pub style: DesktopBannerActionStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DesktopBannerActionStyle {
Default,
Primary,
Destructive,
}
impl DesktopBannerActionStyle {
pub fn as_str(self) -> &'static str {
match self {
Self::Default => "default",
Self::Primary => "primary",
Self::Destructive => "destructive",
}
}
pub fn parse(value: &str) -> Option<Self> {
match value {
"default" => Some(Self::Default),
"primary" => Some(Self::Primary),
"destructive" => Some(Self::Destructive),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum DesktopBannerBackground {
#[default]
System,
Light,
Dark,
Color {
r: u8,
g: u8,
b: u8,
a: u8,
},
}
#[derive(Debug, Clone)]
pub struct DesktopBannerShow {
pub id: String,
pub title: String,
pub body: String,
pub actions: Vec<DesktopBannerAction>,
pub timeout_ms: Option<u64>,
pub background: DesktopBannerBackground,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DesktopBannerOutcome {
Action { id: String, action: String },
Dismissed { id: String },
TimedOut { id: String },
Replaced { id: String },
}
impl DesktopBannerOutcome {
pub fn id(&self) -> &str {
match self {
Self::Action { id, .. }
| Self::Dismissed { id }
| Self::TimedOut { id }
| Self::Replaced { id } => id,
}
}
pub fn reason(&self) -> Option<&'static str> {
match self {
Self::Action { .. } => None,
Self::Dismissed { .. } => Some("dismissed"),
Self::TimedOut { .. } => Some("timeout"),
Self::Replaced { .. } => Some("replaced"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocalNotificationStatus {
Posted,
Scheduled,
Suppressed,
}
impl LocalNotificationStatus {
pub fn as_str(self) -> &'static str {
match self {
Self::Posted => "posted",
Self::Scheduled => "scheduled",
Self::Suppressed => "suppressed",
}
}
pub fn from_native(value: &str) -> Option<Self> {
match value {
"posted" => Some(Self::Posted),
"scheduled" => Some(Self::Scheduled),
"suppressed" => Some(Self::Suppressed),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationType {
None = 0,
Forward = 1,
Backward = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LxAppOpenMode {
#[default]
Normal = 0,
Panel = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenUrlTarget {
External = 0,
SelfTarget = 1,
NewBrowserTab = 2,
AsideBrowser = 3,
}
impl OpenUrlTarget {
pub fn parse(raw: Option<&str>) -> Self {
match raw.map(|v| v.trim().to_ascii_lowercase()) {
Some(v) if v == "self" => Self::SelfTarget,
Some(v) if v == "new_browser_tab" => Self::NewBrowserTab,
Some(v) if v == "aside" => Self::AsideBrowser,
Some(v) if v == "external" => Self::External,
Some(v) => {
log::warn!("Invalid openURL target='{}', fallback to external", v);
Self::External
}
None => Self::External,
}
}
}
#[derive(Debug, Clone)]
pub struct OpenUrlRequest {
pub owner_appid: String,
pub owner_session_id: u64,
pub url: String,
pub target: OpenUrlTarget,
pub want_tab_id: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct OpenUrlResult {
pub tab_id: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinBrowserPage {
Downloads = 1,
}
impl From<i32> for AnimationType {
fn from(value: i32) -> Self {
match value {
1 => AnimationType::Forward,
2 => AnimationType::Backward,
_ => AnimationType::None,
}
}
}
pub trait AppRuntime:
Send
+ Sync
+ MediaInteraction
+ MediaRuntime
+ Network
+ SurfacePresenter
+ ClipboardService
+ Device
+ DeviceHardware
+ SecureStore
+ ShareService
+ FileService
+ Location
+ UIUpdate
+ UpdateService
+ UserFeedback
+ Wifi
+ 'static
{
fn read_asset<'a>(&'a self, path: &str) -> Result<Box<dyn Read + 'a>, PlatformError>;
fn asset_dir_iter<'a>(
&'a self,
asset_dir: &str,
) -> Box<dyn Iterator<Item = Result<AssetFileEntry<'a>, PlatformError>> + 'a>;
fn app_data_dir(&self) -> PathBuf;
fn app_cache_dir(&self) -> PathBuf;
fn get_app_identifier(&self) -> Result<String, PlatformError>;
fn copy_album_media_to_file(
&self,
uri: &str,
dest_path: &Path,
kind: MediaKind,
) -> Result<(), PlatformError> {
MediaRuntime::copy_album_media_to_file(self, uri, dest_path, kind)
}
fn get_system_locale(&self) -> &str;
fn show_lxapp(
&self,
appid: String,
title: String,
path: String,
webtag: String,
session_id: u64,
open_mode: LxAppOpenMode,
panel_id: String,
) -> Result<(), PlatformError>;
fn request_lxapp_main_activation(&self, _appid: &str) {}
fn hide_lxapp(&self, appid: String, session_id: u64) -> Result<(), PlatformError>;
fn exit(&self) -> Result<(), PlatformError>;
fn set_tray_badge(&self, _text: &str) -> Result<bool, PlatformError> {
Ok(false)
}
fn set_tray_icon(&self, _icon: &str) -> Result<(), PlatformError> {
Ok(())
}
fn set_shell_sidebar_actions(
&self,
_items: &[lingxia_shell::ResolvedShellSidebarAction],
) -> Result<(), PlatformError> {
Ok(())
}
fn set_shell_pins(&self, _items: &[lingxia_shell::ShellPin]) -> Result<(), PlatformError> {
Ok(())
}
fn set_control_session_indicator(&self, _active: bool) -> Result<(), PlatformError> {
Ok(())
}
fn set_tray_title(&self, _text: &str) -> Result<(), PlatformError> {
Ok(())
}
fn set_app_badge(&self, _text: &str) -> Result<bool, PlatformError> {
Ok(false)
}
fn autostart_is_enabled(&self) -> Result<bool, PlatformError> {
Err(PlatformError::NotSupported("autostart".to_string()))
}
fn autostart_set_enabled(&self, _enabled: bool) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported("autostart".to_string()))
}
fn notification_permission(&self) -> Result<String, PlatformError> {
Err(PlatformError::NotSupported("notification".to_string()))
}
fn notification_request_permission(&self) -> Result<String, PlatformError> {
Err(PlatformError::NotSupported("notification".to_string()))
}
fn notification_show(
&self,
_request: &LocalNotificationShow,
) -> Result<LocalNotificationStatus, PlatformError> {
Err(PlatformError::NotSupported("notification".to_string()))
}
fn notification_cancel(&self, _id: &str) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported("notification".to_string()))
}
fn notification_cancel_all(&self) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported("notification".to_string()))
}
fn banner_show(
&self,
_request: &DesktopBannerShow,
) -> Result<DesktopBannerOutcome, PlatformError> {
Err(PlatformError::NotSupported("banner".to_string()))
}
fn banner_dismiss(&self, _id: &str) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported("banner".to_string()))
}
fn set_tray_menu(&self, _items_json: &str) -> Result<(), PlatformError> {
Ok(())
}
fn set_tray_visible(&self, _visible: bool) -> Result<(), PlatformError> {
Ok(())
}
fn set_tray_click_intercept(&self, _intercept: bool) -> Result<(), PlatformError> {
Ok(())
}
fn navigate(
&self,
appid: String,
path: String,
webtag: String,
animation_type: AnimationType,
) -> Result<(), PlatformError>;
fn open_url(&self, req: OpenUrlRequest) -> Result<OpenUrlResult, PlatformError>;
fn close_browser_tab(&self, _tab_id: &str) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported("browser tab".to_string()))
}
fn activate_browser_tab(&self, _tab_id: String) -> PlatformFuture {
Box::pin(async { Err(PlatformError::NotSupported("browser tab".to_string())) })
}
fn open_builtin_browser_page(&self, _page: BuiltinBrowserPage) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported(
"built-in browser pages".to_string(),
))
}
}
#[cfg(test)]
mod envelope_tests {
use super::{ACTIVATION_ENVELOPE, unwrap_activation, wrap_activation};
#[test]
fn the_envelope_round_trips_and_rejects_anything_else() {
assert_eq!(unwrap_activation(&wrap_activation("abc")), Some("abc"));
assert_eq!(unwrap_activation("https://example.com/x"), None);
assert_eq!(unwrap_activation(ACTIVATION_ENVELOPE), None);
assert_eq!(unwrap_activation(""), None);
}
}
#[cfg(test)]
mod tests {
use super::OpenUrlTarget;
#[test]
fn parse_supports_new_browser_tab() {
assert_eq!(
OpenUrlTarget::parse(Some("new_browser_tab")),
OpenUrlTarget::NewBrowserTab
);
}
#[test]
fn parse_unknown_falls_back_to_external() {
assert_eq!(
OpenUrlTarget::parse(Some("foobar")),
OpenUrlTarget::External
);
}
}