Browse Source

app: add back button alt+left on win/linux, compose+left mac and back android

darkfi 1 week ago
parent
commit
60e2fdc84f

+ 33 - 5
bin/app/src/app/schema/mod.rs

@@ -26,8 +26,8 @@ use kvdb_overlay::Database as KvDb;
 use crate::{
     app::{
         node::{
-            create_button, create_layer, create_text, create_text_scramble, create_vector_art,
-            create_video,
+            create_button, create_layer, create_shortcut, create_text, create_text_scramble,
+            create_vector_art, create_video,
         },
         App,
     },
@@ -37,7 +37,9 @@ use crate::{
     prop::{PropertyAtomicGuard, PropertyEnum, PropertyFloat32, PropertyStr, Role},
     scene::{SceneNodePtr, Slot},
     sfx, shape,
-    ui::{emoji_picker, Button, Layer, Text, TextScramble, VectorArt, VectorShape, Video},
+    ui::{
+        emoji_picker, Button, Layer, Shortcut, Text, TextScramble, VectorArt, VectorShape, Video,
+    },
     util::{clipboard, i18n::I18nBabelFish},
 };
 
@@ -677,9 +679,9 @@ pub async fn make(
     });
     app.tasks.lock().unwrap().push(reconnect_task);
 
-    let node =
+    let reconnect_btn =
         node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    netlayer_node.link(node);
+    netlayer_node.link(reconnect_btn.clone());
 
     // Overlay layer toggled by the netstatus logo. Sits on top of everything
     // except the header strip, so the logo stays visible and clickable.
@@ -700,6 +702,32 @@ pub async fn make(
         .await;
     chat_layer.link(overlay_node.clone());
 
+    // Back shortcut: when the overlay is shown, pressing back just hides it
+    // instead of navigating back. The overlay layer has a higher priority
+    // than the subscreen layers, so this swallows the key first. It reuses
+    // the reconnect button's click handler so the hide path stays shared.
+    let node = create_shortcut("back_shortcut");
+    #[cfg(target_os = "android")]
+    node.set_property_str(atom, Role::App, "key", "back").unwrap();
+    #[cfg(target_os = "macos")]
+    node.set_property_str(atom, Role::App, "key", "logo+left").unwrap();
+    #[cfg(all(not(target_os = "android"), not(target_os = "macos")))]
+    node.set_property_str(atom, Role::App, "key", "alt+left").unwrap();
+    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
+
+    let (slot, recvr) = Slot::new("back_pressed");
+    node.register("shortcut", slot).unwrap();
+    let reconnect_btn_clone = reconnect_btn.clone();
+    let listen_back = ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            reconnect_btn_clone.trigger("click", vec![]).await.unwrap();
+        }
+    });
+    overlay_node.push_task(listen_back);
+
+    let node = node.setup(|me| Shortcut::new(me)).await;
+    overlay_node.link(node);
+
     // Placeholder single-color background filling the whole overlay
     let node = create_vector_art("overlay_bg");
     let prop = node.get_property("rect").unwrap();

+ 2 - 0
bin/app/src/app/schema/wallet/receive.rs

@@ -59,6 +59,8 @@ pub async fn make(
         .await;
     wallet_layer.link(receive_layer.clone());
 
+    create_back_shortcut(app, atom, &receive_layer).await;
+
     create_bg_mesh(app, atom, &receive_layer, "receive_bg").await;
     create_header_bg(app, atom, &receive_layer, "receive_header_bg").await;
 

+ 1 - 0
bin/app/src/app/schema/wallet/send_step1.rs

@@ -72,6 +72,7 @@ pub async fn make(
         .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
     wallet_layer.link(send_step1_layer.clone());
+    create_back_shortcut(app, atom, &send_step1_layer).await;
     let step1_is_visible =
         PropertyBool::wrap(&send_step1_layer, Role::App, "is_visible", 0).unwrap();
 

+ 1 - 0
bin/app/src/app/schema/wallet/send_step2.rs

@@ -103,6 +103,7 @@ pub async fn make(
         .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
     wallet_layer.link(send_step2_layer.clone());
+    create_back_shortcut(app, atom, &send_step2_layer).await;
     let step2_is_visible =
         PropertyBool::wrap(&send_step2_layer, Role::App, "is_visible", 0).unwrap();
 

+ 1 - 0
bin/app/src/app/schema/wallet/send_step3.rs

@@ -74,6 +74,7 @@ pub async fn make(
         .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
     wallet_layer.link(send_step3_layer.clone());
+    create_back_shortcut(app, atom, &send_step3_layer).await;
     let step3_is_visible =
         PropertyBool::wrap(&send_step3_layer, Role::App, "is_visible", 0).unwrap();
 

+ 1 - 0
bin/app/src/app/schema/wallet/send_step4.rs

@@ -72,6 +72,7 @@ pub async fn make(
         .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
     wallet_layer.link(send_step4_layer.clone());
+    create_back_shortcut(app, atom, &send_step4_layer).await;
     let step4_is_visible =
         PropertyBool::wrap(&send_step4_layer, Role::App, "is_visible", 0).unwrap();
 

+ 1 - 0
bin/app/src/app/schema/wallet/tx_status.rs

@@ -107,6 +107,7 @@ pub async fn make(
         .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
     wallet_layer.link(tx_status_layer.clone());
+    create_back_shortcut(app, atom, &tx_status_layer).await;
     let tx_status_is_visible =
         PropertyBool::wrap(&tx_status_layer, Role::App, "is_visible", 0).unwrap();
 

+ 34 - 3
bin/app/src/app/schema/wallet/util.rs

@@ -25,7 +25,7 @@ use smol::channel::unbounded;
 
 use crate::{
     app::{
-        node::{create_button, create_layer, create_text, create_vector_art},
+        node::{create_button, create_layer, create_shortcut, create_text, create_vector_art},
         schema::COLOR_SCHEME,
         App,
     },
@@ -33,9 +33,9 @@ use crate::{
     gfx::{gfxtag, Renderer},
     mesh::{COLOR_CYAN, COLOR_TEAL},
     prop::{PropertyAtomicGuard, PropertyFloat32, Role},
-    scene::{Pimpl, SceneNodePtr},
+    scene::{Pimpl, SceneNodePtr, Slot},
     text,
-    ui::{Button, Layer, RedrawTrigger, Text, VectorArt, VectorShape},
+    ui::{Button, Layer, RedrawTrigger, Shortcut, Text, VectorArt, VectorShape},
     util::i18n::I18nBabelFish,
 };
 
@@ -109,6 +109,37 @@ pub async fn update_amount_screen(
     balance
 }
 
+/// Creates a back shortcut on a wallet subscreen layer. Pressing back
+/// hides the layer and returns to the main wallet screen.
+pub async fn create_back_shortcut(app: &App, atom: &mut PropertyAtomicGuard, layer: &SceneNodePtr) {
+    let node = create_shortcut("back_shortcut");
+    #[cfg(target_os = "android")]
+    node.set_property_str(atom, Role::App, "key", "back").unwrap();
+    #[cfg(target_os = "macos")]
+    node.set_property_str(atom, Role::App, "key", "logo+left").unwrap();
+    #[cfg(all(not(target_os = "android"), not(target_os = "macos")))]
+    node.set_property_str(atom, Role::App, "key", "alt+left").unwrap();
+    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
+
+    let (slot, recvr) = Slot::new("back_pressed");
+    node.register("shortcut", slot).unwrap();
+    let sg_root = app.sg_root.clone();
+    let redraw = app.redraw_trigger.clone();
+    let layer_clone = layer.clone();
+    let listen_back = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            let atom = &mut redraw.make_guard(gfxtag!("wallet back shortcut"));
+            layer_clone.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+            let main_layer = sg_root.lookup_node("/window/content/wallet/main_layer").unwrap();
+            main_layer.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_back);
+
+    let node = node.setup(|me| Shortcut::new(me)).await;
+    layer.link(node);
+}
+
 /// Creates a title text node with separator line.
 /// Returns the text node after setup, linked to the layer
 pub async fn create_title(

+ 2 - 7
bin/app/src/ui/chatview/mod.rs

@@ -315,13 +315,8 @@ impl ChatView {
             is_at_bottom,
             hi_bg_color: PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color")
                 .expect("chatview hi_bg_color"),
-            wheel_page_frac: PropertyFloat32::wrap(
-                node_ref,
-                Role::Internal,
-                "wheel_page_frac",
-                0,
-            )
-            .expect("chatview wheel_page_frac"),
+            wheel_page_frac: PropertyFloat32::wrap(node_ref, Role::Internal, "wheel_page_frac", 0)
+                .expect("chatview wheel_page_frac"),
             channel_prop: crate::prop::PropertyStr::wrap(node_ref, Role::Internal, "channel", 0)
                 .expect("chatview channel"),
 

+ 2 - 1
bin/app/src/ui/chatview/msg/filemsg.rs

@@ -216,7 +216,8 @@ impl FileMsgNode {
         let hash = url.host_str().unwrap_or("???");
         let short = if hash.chars().count() >= 12 {
             let head: String = hash.chars().take(4).collect();
-            let tail: String = hash.chars().rev().take(4).collect::<Vec<_>>().into_iter().rev().collect();
+            let tail: String =
+                hash.chars().rev().take(4).collect::<Vec<_>>().into_iter().rev().collect();
             format!("{head}...{tail}")
         } else {
             hash.to_string()

+ 12 - 6
bin/app/src/ui/chatview/msg/privmsg.rs

@@ -928,8 +928,11 @@ fn sanitize_url(raw: &str) -> Option<String> {
     if trimmed.chars().any(|c| c.is_control()) {
         return None
     }
-    let candidate =
-        if trimmed.starts_with("www.") { format!("https://{trimmed}") } else { trimmed.to_string() };
+    let candidate = if trimmed.starts_with("www.") {
+        format!("https://{trimmed}")
+    } else {
+        trimmed.to_string()
+    };
     let url = Url::parse(&candidate).ok()?;
     match url.scheme() {
         "http" | "https" | "fud" => Some(url.to_string()),
@@ -1415,12 +1418,15 @@ mod tests {
     fn url_sanitization() {
         use super::sanitize_url;
         assert_eq!(sanitize_url("https://example.com/").as_deref(), Some("https://example.com/"));
-        assert_eq!(sanitize_url("https://example.com/path.").as_deref(), Some("https://example.com/path"));
-        assert_eq!(sanitize_url("https://example.com/a,b!").as_deref(), Some("https://example.com/a,b"));
         assert_eq!(
-            sanitize_url("www.example.com/x").as_deref(),
-            Some("https://www.example.com/x")
+            sanitize_url("https://example.com/path.").as_deref(),
+            Some("https://example.com/path")
+        );
+        assert_eq!(
+            sanitize_url("https://example.com/a,b!").as_deref(),
+            Some("https://example.com/a,b")
         );
+        assert_eq!(sanitize_url("www.example.com/x").as_deref(), Some("https://www.example.com/x"));
         // Interior control characters (incl. NUL): rejected. A trailing
         // one is trimmed — the cleaned URL stays usable.
         assert_eq!(sanitize_url("https://evil.com/\u{0}x"), None);