feat: Mobile PWA improvements

- Implemented responsive card layout for mobile devices
- Fixed Android bottom navigation bar color matching
- Improved mobile context menu (iOS/Android touch fixes)
- Cleaned up toolbar and updated theme icon
This commit is contained in:
spinline
2026-02-01 15:24:06 +03:00
parent 4286c8f3e3
commit 8064d6ae74
6 changed files with 73 additions and 53 deletions

View File

@@ -1,41 +1,3 @@
use leptos::*;
use leptos::html::Div;
use wasm_bindgen::JsCast;
pub fn use_click_outside(
target: NodeRef<Div>,
callback: impl Fn() + Clone + 'static,
) {
create_effect(move |_| {
if let Some(_) = target.get() {
let handle_click = {
let callback = callback.clone();
let target = target.clone();
move |ev: web_sys::MouseEvent| {
if let Some(el) = target.get() {
let ev_target = ev.target().unwrap().unchecked_into::<web_sys::Node>();
let el_node = el.unchecked_ref::<web_sys::Node>();
if !el_node.contains(Some(&ev_target)) {
callback();
}
}
}
};
let window = web_sys::window().unwrap();
let closure = wasm_bindgen::closure::Closure::<dyn FnMut(_)>::new(handle_click);
let _ = window.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref());
// Cleanup
on_cleanup(move || {
let window = web_sys::window().unwrap();
let _ = window.remove_event_listener_with_callback("click", closure.as_ref().unchecked_ref());
});
}
});
}
#[component]
pub fn ContextMenu(
position: (i32, i32),
@@ -53,21 +15,19 @@ pub fn ContextMenu(
on_close.call(()); // Close menu AFTER
};
let target = create_node_ref::<Div>();
use_click_outside(target, move || {
if visible {
on_close.call(());
}
});
if !visible {
return view! {}.into_view();
}
view! {
// Backdrop to catch clicks outside
<div
class="fixed inset-0 z-[99] cursor-default"
on:click=move |_| on_close.call(())
on:contextmenu=move |e| e.prevent_default()
></div>
<div
node_ref=target
class="fixed z-[100] min-w-[200px] animate-in fade-in zoom-in-95 duration-100"
style=format!("left: {}px; top: {}px", position.0, position.1)
on:contextmenu=move |e| e.prevent_default()

View File

@@ -40,6 +40,18 @@ pub fn StatusBar() -> impl IntoView {
on:click=move |_| {
let doc = web_sys::window().unwrap().document().unwrap();
let _ = doc.document_element().unwrap().set_attribute("data-theme", theme);
// Update theme-color meta tag to match new theme
if let Some(meta) = doc.query_selector("meta[name='theme-color']").unwrap() {
let window = web_sys::window().unwrap();
// Force a style recalc by reading a property or just wait for next tick?
// Usually get_computed_style forces it.
if let Ok(Some(style)) = window.get_computed_style(&doc.body().unwrap()) {
if let Ok(color) = style.get_property_value("background-color") {
let _ = meta.set_attribute("content", &color);
}
}
}
}
>
{theme}

View File

@@ -247,7 +247,7 @@ pub fn TorrentTable() -> impl IntoView {
</table>
</div>
<div class="md:hidden grid grid-cols-1 gap-3 p-3 pb-20 overflow-y-auto h-full">
<div class="md:hidden grid grid-cols-1 content-start gap-3 p-3 pb-20 overflow-y-auto h-full">
{move || filtered_torrents().into_iter().map(|t| {
let progress_class = if t.percent_complete >= 100.0 { "progress-success" } else { "progress-primary" };
let status_str = format!("{:?}", t.status);
@@ -271,11 +271,12 @@ pub fn TorrentTable() -> impl IntoView {
class=move || {
let base = "card card-compact bg-base-100 shadow-sm border border-base-200 transition-transform active:scale-[0.99]";
if is_selected_fn() {
format!("{} ring-2 ring-primary", base)
format!("{} ring-2 ring-primary select-none", base)
} else {
base.to_string()
format!("{} select-none", base)
}
}
style="user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;"
on:contextmenu={
let t_hash = t_hash_ctx.clone();
move |e: web_sys::MouseEvent| handle_context_menu(e, t_hash.clone())