From 8064d6ae74976a4a6d1f197cc6591fe2edbc48af Mon Sep 17 00:00:00 2001 From: spinline Date: Sun, 1 Feb 2026 15:24:06 +0300 Subject: [PATCH] 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 --- frontend/index.html | 40 ++++++++++++++- frontend/manifest.json | 4 +- frontend/public/tailwind.css | 9 ++++ frontend/src/components/context_menu.rs | 54 +++------------------ frontend/src/components/layout/statusbar.rs | 12 +++++ frontend/src/components/torrent/table.rs | 7 +-- 6 files changed, 73 insertions(+), 53 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 9d78b43..88844fa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -26,10 +26,48 @@ if (t === "Light") t = "light"; if (t === "Dark" || t === "Midnight") t = "dark"; - document.documentElement.setAttribute("data-theme", t.toLowerCase()); + var theme = t.toLowerCase(); + document.documentElement.setAttribute("data-theme", theme); if (!localTheme) { localStorage.setItem("vibetorrent_theme", "Dark"); } + + var meta = document.querySelector('meta[name="theme-color"]'); + if (meta) { + var colorMap = { + "light": "#ffffff", + "cupcake": "#faf7f5", + "bumblebee": "#ffffff", + "emerald": "#ffffff", + "corporate": "#ffffff", + "synthwave": "#2d1b69", + "retro": "#ece3ca", + "cyberpunk": "#ffee00", + "valentine": "#f0d6e8", + "halloween": "#212121", + "garden": "#e9e7e7", + "forest": "#171212", + "aqua": "#345da7", + "lofi": "#ffffff", + "pastel": "#ffffff", + "fantasy": "#ffffff", + "wireframe": "#ffffff", + "black": "#000000", + "luxury": "#09090b", + "dracula": "#282a36", + "cmyk": "#ffffff", + "autumn": "#8C0327", + "business": "#202020", + "acid": "#fafafa", + "lemonade": "#F1F8E8", + "night": "#0f1729", + "coffee": "#20161f", + "winter": "#ffffff", + "dark": "#1d232a" + }; + var color = colorMap[theme] || "#1d232a"; + meta.setAttribute("content", color); + } })(); diff --git a/frontend/manifest.json b/frontend/manifest.json index 95064c0..9fad16c 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -3,8 +3,8 @@ "short_name": "VibeTorrent", "start_url": "/", "display": "standalone", - "background_color": "#111827", - "theme_color": "#000000", + "background_color": "#1d232a", + "theme_color": "#1d232a", "orientation": "any", "icons": [ { diff --git a/frontend/public/tailwind.css b/frontend/public/tailwind.css index d50bfae..f6be0df 100644 --- a/frontend/public/tailwind.css +++ b/frontend/public/tailwind.css @@ -1211,6 +1211,9 @@ .z-\[1\] { z-index: 1; } + .z-\[99\] { + z-index: 99; + } .z-\[100\] { z-index: 100; } @@ -1673,6 +1676,9 @@ .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } + .cursor-default { + cursor: default; + } .cursor-pointer { cursor: pointer; } @@ -1685,6 +1691,9 @@ .flex-col { flex-direction: column; } + .content-start { + align-content: flex-start; + } .items-center { align-items: center; } diff --git a/frontend/src/components/context_menu.rs b/frontend/src/components/context_menu.rs index d24c16f..9018495 100644 --- a/frontend/src/components/context_menu.rs +++ b/frontend/src/components/context_menu.rs @@ -1,41 +1,3 @@ -use leptos::*; -use leptos::html::Div; -use wasm_bindgen::JsCast; - -pub fn use_click_outside( - target: NodeRef
, - 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::(); - let el_node = el.unchecked_ref::(); - if !el_node.contains(Some(&ev_target)) { - callback(); - } - } - } - }; - - let window = web_sys::window().unwrap(); - let closure = wasm_bindgen::closure::Closure::::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::
(); - - use_click_outside(target, move || { - if visible { - on_close.call(()); - } - }); - if !visible { return view! {}.into_view(); } view! { + // Backdrop to catch clicks outside +
+
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} diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs index 2cedf26..fbe21ac 100644 --- a/frontend/src/components/torrent/table.rs +++ b/frontend/src/components/torrent/table.rs @@ -247,7 +247,7 @@ pub fn TorrentTable() -> impl IntoView {
-
+
{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())