Browse Source

app/menu: grey out the add buttons until name and key are valid

darkfi 1 tháng trước cách đây
mục cha
commit
fb5fc4b665

+ 59 - 21
bin/app/src/app/schema/menu/channel.rs

@@ -37,7 +37,7 @@ use super::{
     super::{append_joined_channel, chat},
     edit_buttons,
     edit_switch::edit_switch,
-    BTN_TEXT_Y, CHANNEL_ITEM_HEIGHT, MENU_BTN_W_L,
+    setup_add_button_validity, valid_key, BTN_TEXT_Y, CHANNEL_ITEM_HEIGHT, MENU_BTN_W_L,
 };
 use crate::{
     app::{
@@ -1015,7 +1015,35 @@ pub async fn make(
     node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    editlayer_node.link(node);
+    node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    editlayer_node.link(node.clone());
+    let btns_bg = node;
+
+    // Grey outline for the invalid state
+    let node = create_vector_art("btns_bg_grey");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
+    node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_outline(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::const_f32(MENU_BTN_W_L + 45.),
+        expr::load_var("h"),
+        CONTENT_OUTLINE_SIZE,
+        COLOR_INACTIVE,
+    );
+
+    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let node =
+        node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
+    editlayer_node.link(node.clone());
+    let btns_bg_grey = node;
 
     let mut node = create_button("addchannel_btn");
     node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
@@ -1049,8 +1077,15 @@ pub async fn make(
     node.set_property_bool(atom, Role::App, "use_i18n", false).unwrap();
 
     let prop = node.get_property("text_color").unwrap();
-    prop.set_default_f32_multi(&[COLOR_CYAN[0], COLOR_CYAN[1], COLOR_CYAN[2], COLOR_CYAN[3]])
-        .unwrap();
+    // The add buttons start greyed out until name and key are valid; the
+    // validity watcher below flips this to cyan.
+    prop.set_default_f32_multi(&[
+        COLOR_INACTIVE[0],
+        COLOR_INACTIVE[1],
+        COLOR_INACTIVE[2],
+        COLOR_INACTIVE[3],
+    ])
+    .unwrap();
 
     let node = node
         .setup(|me| {
@@ -1063,7 +1098,19 @@ pub async fn make(
             )
         })
         .await;
-    editlayer_node.link(node);
+    editlayer_node.link(node.clone());
+    let addchannel_label = node;
+
+    // Grey the button out until name and key are valid
+    setup_add_button_validity(
+        app,
+        &nickedit_node,
+        &secedit_node,
+        addchannel_label,
+        btns_bg,
+        btns_bg_grey,
+        "addchannel_valid",
+    );
 
     // Generate-secret button: mirrors add-channel button, positioned on the left.
     let node = create_layer("gensecret_btn_layer");
@@ -1245,24 +1292,15 @@ pub async fn make(
 
             let channel_name = format!("#{}", name);
 
-            let mut channel = Channel { name: name.to_string(), secret: None };
-
-            // Try to decode secret field if it is set
-            if !secret.is_empty() {
-                let Ok(bytes) = bs58::decode(&secret).into_vec() else {
-                    w!("Failed to decode secret base58");
-                    continue
-                };
-                if bytes.len() != 32 {
-                    w!("Invalid secret length: {} bytes (expected 32)", bytes.len());
-                    continue
-                }
-
-                let mut arr = [0u8; 32];
-                arr.copy_from_slice(&bytes);
-                channel.secret = Some(arr);
+            if !valid_key(&secret) {
+                w!("Invalid channel secret: expected a 32-byte key in base58");
+                continue;
             }
 
+            let mut arr = [0u8; 32];
+            arr.copy_from_slice(&bs58::decode(&secret).into_vec().unwrap());
+            let channel = Channel { name: name.to_string(), secret: Some(arr) };
+
             app_db2.channel_insert(&channel).await.unwrap();
 
             let atom = &mut redraw2.make_guard(gfxtag!("add_channel"));

+ 55 - 11
bin/app/src/app/schema/menu/contact.rs

@@ -37,7 +37,7 @@ use super::{
     super::{append_joined_channel, chat},
     edit_buttons,
     edit_switch::edit_switch,
-    BTN_TEXT_Y, CHANNEL_ITEM_HEIGHT, MENU_BTN_W_L,
+    setup_add_button_validity, valid_key, BTN_TEXT_Y, CHANNEL_ITEM_HEIGHT, MENU_BTN_W_L,
 };
 use crate::{
     app::{
@@ -1236,7 +1236,35 @@ pub async fn make(
     node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    editlayer_node.link(node);
+    node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    editlayer_node.link(node.clone());
+    let btns_bg = node;
+
+    // Grey outline for the invalid state
+    let node = create_vector_art("btns_bg_grey");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
+    node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_outline(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::const_f32(MENU_BTN_W_L + 45.),
+        expr::load_var("h"),
+        CONTENT_OUTLINE_SIZE,
+        COLOR_INACTIVE,
+    );
+
+    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let node =
+        node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
+    editlayer_node.link(node.clone());
+    let btns_bg_grey = node;
 
     let node = create_button("addcontact_btn");
     node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
@@ -1268,8 +1296,15 @@ pub async fn make(
     node.set_property_bool(atom, Role::App, "use_i18n", false).unwrap();
 
     let prop = node.get_property("text_color").unwrap();
-    prop.set_default_f32_multi(&[COLOR_CYAN[0], COLOR_CYAN[1], COLOR_CYAN[2], COLOR_CYAN[3]])
-        .unwrap();
+    // The add buttons start greyed out until name and key are valid; the
+    // validity watcher below flips this to cyan.
+    prop.set_default_f32_multi(&[
+        COLOR_INACTIVE[0],
+        COLOR_INACTIVE[1],
+        COLOR_INACTIVE[2],
+        COLOR_INACTIVE[3],
+    ])
+    .unwrap();
 
     let node = node
         .setup(|me| {
@@ -1282,7 +1317,19 @@ pub async fn make(
             )
         })
         .await;
-    editlayer_node.link(node);
+    editlayer_node.link(node.clone());
+    let addcontact_label = node;
+
+    // Grey the button out until name and key are valid
+    setup_add_button_validity(
+        app,
+        &nickedit_node,
+        &secedit_node,
+        addcontact_label,
+        btns_bg,
+        btns_bg_grey,
+        "addcontact_valid",
+    );
 
     // Create cancel/done edit buttons at bottom of screen
     let btns =
@@ -1404,14 +1451,11 @@ pub async fn make(
                 continue;
             }
 
-            let Ok(public_bytes) = bs58::decode(&public_str).into_vec() else {
-                w!("Failed to decode contact public key base58");
-                continue;
-            };
-            if public_bytes.len() != 32 {
-                w!("Invalid public key length: {} (expected 32)", public_bytes.len());
+            if !valid_key(&public_str) {
+                w!("Invalid contact public key: expected a 32-byte key in base58");
                 continue;
             }
+            let public_bytes = bs58::decode(&public_str).into_vec().unwrap();
             let mut public = [0u8; 32];
             public.copy_from_slice(&public_bytes);
 

+ 49 - 1
bin/app/src/app/schema/menu/mod.rs

@@ -16,7 +16,9 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use bs58;
 use darkfi_serial::{deserialize, Encodable};
+use futures::FutureExt;
 use kvdb_overlay::Database as KvDb;
 use std::io::Write;
 use ui_consts::*;
@@ -30,7 +32,7 @@ use crate::{
     db::AppDbPtr,
     expr,
     gfx::gfxtag,
-    mesh::COLOR_CYAN,
+    mesh::{COLOR_CYAN, COLOR_INACTIVE},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     scene::{SceneNodePtr, Slot},
     sfx, shape,
@@ -635,3 +637,49 @@ pub async fn setup_settings_button(app: &App, chat_layer: SceneNodePtr) {
     let node = node.setup(|me| Button::new(me, renderer, redraw)).await;
     mainlayer_node.link(node);
 }
+
+fn valid_key(s: &str) -> bool {
+    bs58::decode(s).into_vec().is_ok_and(|bytes| bytes.len() == 32)
+}
+
+/// Grey an add button out until its name and key are valid
+pub fn setup_add_button_validity(
+    app: &App,
+    nickedit_node: &SceneNodePtr,
+    secedit_node: &SceneNodePtr,
+    add_label: SceneNodePtr,
+    btns_bg: SceneNodePtr,
+    btns_bg_grey: SceneNodePtr,
+    valid_tag: &'static str,
+) {
+    let name_text = nickedit_node.get_property("text").unwrap();
+    let key_text = secedit_node.get_property("text").unwrap();
+    let name_sub = name_text.subscribe_modify();
+    let key_sub = key_text.subscribe_modify();
+    let redraw = app.redraw_trigger.clone();
+    let nickedit_handle = nickedit_node.clone();
+    let keyedit_handle = secedit_node.clone();
+    let listen_valid = app.ex.spawn(async move {
+        loop {
+            futures::select! {
+                _ = name_sub.receive().fuse() => {},
+                _ = key_sub.receive().fuse() => {},
+            }
+
+            let name = nickedit_handle.get_property_str("text").unwrap();
+            let key = keyedit_handle.get_property_str("text").unwrap();
+            let valid = !name.is_empty() && valid_key(&key);
+
+            let atom = &mut redraw.make_guard(gfxtag!(valid_tag));
+            btns_bg.set_property_bool(atom, Role::App, "is_visible", valid).unwrap();
+            btns_bg_grey.set_property_bool(atom, Role::App, "is_visible", !valid).unwrap();
+            let color = if valid { COLOR_CYAN } else { COLOR_INACTIVE };
+            let prop = add_label.get_property("text_color").unwrap();
+            prop.set_f32(atom, Role::App, 0, color[0]).unwrap();
+            prop.set_f32(atom, Role::App, 1, color[1]).unwrap();
+            prop.set_f32(atom, Role::App, 2, color[2]).unwrap();
+            prop.set_f32(atom, Role::App, 3, color[3]).unwrap();
+        }
+    });
+    app.tasks.lock().push(listen_valid);
+}