use lingxia_platform::traits::app_runtime::{AppRuntime, OpenUrlRequest, OpenUrlTarget};
use lingxia_platform::traits::ui::{PopupPresenter, PopupRequest};
use lingxia_webview::WebViewController;
use lingxia_webview::runtime::destroy_webview;
use lingxia_webview::{NavigationPolicy, NewWindowPolicy, WebTag, WebViewBuilder};
use std::sync::Arc;
use crate::PageLifecycleEvent;
use crate::error::LxAppError;
use crate::lxapp::LxApp;
pub(crate) const WEB_POPUP_PATH: &str = "__web_popup__";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PopupMode {
LxAppPage,
WebPage,
}
#[derive(Debug, Clone)]
pub(crate) enum ActivePopup {
LxAppPage(String),
WebPage,
}
impl LxApp {
fn create_web_popup_webview(self: &Arc<Self>, target_url: &str) -> Result<(), LxAppError> {
let owner_appid_for_nav = self.appid.clone();
let owner_session_for_nav = self.session_id();
let runtime_for_nav = self.runtime.clone();
let owner_appid_for_ready = self.appid.clone();
let owner_session_for_ready = self.session_id();
let target_url_for_ready = target_url.to_string();
let webtag = WebTag::new(&self.appid, WEB_POPUP_PATH, Some(self.session_id()));
let session = WebViewBuilder::browser(webtag)
.on_navigation(move |url| {
let decision = crate::browser::handle_browser_navigation_policy(
crate::browser::BrowserNavigationPolicyRequest {
raw_url: url.to_string(),
has_user_gesture: true,
is_main_frame: true,
},
);
match decision.decision {
crate::browser::BrowserNavigationPolicyDecision::InWebview => {
NavigationPolicy::Allow
}
crate::browser::BrowserNavigationPolicyDecision::OpenExternal => {
let _ = runtime_for_nav.open_url(OpenUrlRequest {
owner_appid: owner_appid_for_nav.clone(),
owner_session_id: owner_session_for_nav,
url: url.to_string(),
target: OpenUrlTarget::External,
});
NavigationPolicy::Cancel
}
crate::browser::BrowserNavigationPolicyDecision::Deny => {
NavigationPolicy::Cancel
}
}
})
.on_new_window(move |url| {
let _ = url;
NewWindowPolicy::LoadInSelf
})
.create();
if let Err(e) = rong::bg::spawn(async move {
match session.wait_ready().await {
Ok(webview) => {
if let Err(load_err) = webview.load_url(&target_url_for_ready) {
crate::warn!(
"[Popup] failed to load external url url={} err={}",
target_url_for_ready,
load_err
);
}
}
Err(wait_err) => {
crate::warn!(
"[Popup] failed to create external popup webview err={}",
wait_err
);
destroy_webview(&WebTag::new(
&owner_appid_for_ready,
WEB_POPUP_PATH,
Some(owner_session_for_ready),
));
}
}
}) {
return Err(LxAppError::Runtime(format!(
"failed to spawn external popup webview task: {}",
e
)));
}
Ok(())
}
pub fn show_popup_with_mode(
self: &Arc<Self>,
mode: PopupMode,
mut request: PopupRequest,
) -> Result<(), LxAppError> {
self.hide_popup()?;
request.app_id = self.appid.clone();
if !request.width_ratio.is_nan() {
request.width_ratio = request.width_ratio.clamp(0.0, 1.0);
}
if !request.height_ratio.is_nan() {
request.height_ratio = request.height_ratio.clamp(0.0, 1.0);
}
let active = match mode {
PopupMode::LxAppPage => {
let resolved = crate::route::resolve_route(self, &request.path)?;
let path = resolved.internal_path();
let query_str = resolved.query.unwrap_or_default();
let popup_page = self.get_or_create_page(&path);
popup_page.mark_active();
if !query_str.is_empty() {
popup_page.set_query(query_str);
}
popup_page.dispatch_lifecycle_event(PageLifecycleEvent::OnLoad);
request.path = path.clone();
ActivePopup::LxAppPage(path)
}
PopupMode::WebPage => {
let target_url = crate::browser::normalize_browser_target_url(&request.path);
let scheme = crate::browser::extract_url_scheme(&target_url);
if !matches!(scheme.as_deref(), Some("http" | "https")) {
return Err(LxAppError::InvalidParameter(format!(
"WebPage popup only supports http/https URLs, got '{}'",
request.path
)));
}
self.create_web_popup_webview(&target_url)?;
request.path = WEB_POPUP_PATH.to_string();
ActivePopup::WebPage
}
};
self.runtime.show_popup(request).map_err(LxAppError::from)?;
if let Ok(mut state) = self.state.lock() {
state.current_popup = Some(active);
}
Ok(())
}
pub fn show_popup(self: &Arc<Self>, request: PopupRequest) -> Result<(), LxAppError> {
self.show_popup_with_mode(PopupMode::LxAppPage, request)
}
pub fn hide_popup(self: &Arc<Self>) -> Result<(), LxAppError> {
let active = {
let mut state = self.state.lock().unwrap();
state.current_popup.take()
};
let Some(active) = active else {
return Ok(());
};
if let ActivePopup::LxAppPage(ref path) = active {
if let Some(page) = self.get_page(path) {
page.dispatch_lifecycle_event(PageLifecycleEvent::OnHide);
page.dispatch_lifecycle_event(PageLifecycleEvent::OnUnload);
}
}
self.runtime
.hide_popup(&self.appid)
.map_err(LxAppError::from)?;
if let ActivePopup::WebPage = active {
destroy_webview(&WebTag::new(
&self.appid,
WEB_POPUP_PATH,
Some(self.session.id),
));
}
Ok(())
}
}