Просмотр исходного кода

wallet: editbox handles android typing suggestions

darkfi 1 год назад
Родитель
Сommit
969c965c8d

+ 20 - 8
bin/darkwallet/java/autosuggest/CustomInputConnection.java

@@ -35,8 +35,13 @@ public class CustomInputConnection extends BaseInputConnection {
     native static void onCommitText(String text);
     native static void onEndEdit(String text);
 
+    // Android is sending commit("foo") then edit("foo") events which is confusing.
+    // We use this to skip edit("foo") when proceeded by the commit.
+    private String lastCommitText;
+
     public CustomInputConnection(View view, boolean fullEditor) {
         super(view, fullEditor);
+        lastCommitText = null;
         setup();
     }
 
@@ -57,12 +62,7 @@ public class CustomInputConnection extends BaseInputConnection {
         } else if (action == KeyEvent.ACTION_DOWN && keycode == KeyEvent.KEYCODE_FORWARD_DEL) {
             deleteSurroundingText(0, 1);
         } else if (action == KeyEvent.ACTION_DOWN && keycode == KeyEvent.KEYCODE_ENTER) {
-            setComposingText("", 0);
-
-            // Chromium does this but the above seems to work too.
-            //beginBatchEdit();
-            //finishComposingText();
-            //endBatchEdit();
+            reset();
         }
         return true;
     }
@@ -70,7 +70,8 @@ public class CustomInputConnection extends BaseInputConnection {
     @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
         //Log.i("darkfi", String.format("commitText(%s, %d)", text.toString(), newCursorPosition));
-        onCommitText(text.toString());
+        lastCommitText = text.toString();
+        onCommitText(lastCommitText);
         return super.commitText(text, newCursorPosition);
     }
 
@@ -78,8 +79,19 @@ public class CustomInputConnection extends BaseInputConnection {
     public boolean endBatchEdit() {
         //Log.i("darkfi", "endBatchEdit: " + curr);
         String text = getTextBeforeCursor(100, 0).toString();
-        onEndEdit(text);
+        if (!text.equals(lastCommitText))
+            onEndEdit(text);
+        lastCommitText = null;
         return super.endBatchEdit();
     }
+
+    public void reset() {
+        setComposingText("", 0);
+
+        // Chromium does this but the above seems to work too.
+        //beginBatchEdit();
+        //finishComposingText();
+        //endBatchEdit();
+    }
 }
 

+ 16 - 2
bin/darkwallet/src/android.rs

@@ -53,7 +53,7 @@ pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCommitText(
     text: ndk_sys::jobject,
 ) {
     let text = ndk_utils::get_utf_str!(env, text);
-    debug!(target: "android", "onCommitText: {text}");
+    //debug!(target: "android", "onCommitText({text})");
     if let Some(sender) = &GLOBALS.lock().unwrap().sender {
         let _ = sender.try_send(AndroidSuggestEvent::CommitText(text.to_string()));
     }
@@ -66,7 +66,7 @@ pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onEndEdit(
     text: ndk_sys::jobject,
 ) {
     let text = ndk_utils::get_utf_str!(env, text);
-    debug!(target: "android", "onEditText: {text}");
+    //debug!(target: "android", "onEditText({text})");
     if let Some(sender) = &GLOBALS.lock().unwrap().sender {
         let _ = sender.try_send(AndroidSuggestEvent::EditText(text.to_string()));
     }
@@ -75,3 +75,17 @@ pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onEndEdit(
 pub fn set_sender(sender: async_channel::Sender<AndroidSuggestEvent>) {
     GLOBALS.lock().unwrap().sender = Some(sender);
 }
+
+pub fn reset_autosuggest() {
+    let env = unsafe { android::attach_jni_env() };
+    let mut globals = GLOBALS.lock().unwrap();
+
+    if globals.inp_conn.is_null() {
+        error!(target: "android", "InputConnection is not ready!");
+        return
+    }
+
+    unsafe {
+        ndk_utils::call_void_method!(env, globals.inp_conn, "reset", "()V");
+    }
+}

+ 7 - 0
bin/darkwallet/src/app/node.rs

@@ -202,6 +202,13 @@ pub fn create_editbox(name: &str) -> SceneNode {
     let mut prop = Property::new("selected", PropertyType::Uint32, PropertySubType::Color);
     prop.set_array_len(2);
     prop.allow_null_values();
+    prop.set_defaults_null().unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("underline", PropertyType::Uint32, PropertySubType::Color);
+    prop.set_array_len(2);
+    prop.allow_null_values();
+    prop.set_defaults_null().unwrap();
     node.add_property(prop).unwrap();
 
     let mut prop = Property::new("cursor_blink_time", PropertyType::Uint32, PropertySubType::Null);

+ 3 - 0
bin/darkwallet/src/prop/mod.rs

@@ -546,6 +546,9 @@ impl Property {
     }
     pub fn is_null(&self, i: usize) -> Result<bool> {
         let val = self.get_raw_value(i)?;
+        if val.is_unset() {
+            return Ok(self.defaults[i].is_null())
+        }
         Ok(val.is_null())
     }
 

+ 128 - 3
bin/darkwallet/src/ui/editbox.rs

@@ -174,6 +174,7 @@ pub struct EditBox {
     cursor_descent: PropertyFloat32,
     hi_bg_color: PropertyColor,
     selected: PropertyPtr,
+    underline: PropertyPtr,
     z_index: PropertyUint32,
     debug: PropertyBool,
 
@@ -215,6 +216,7 @@ impl EditBox {
             PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_descent", 0).unwrap();
         let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
         let selected = node_ref.get_property("selected").unwrap();
+        let underline = node_ref.get_property("underline").unwrap();
         let cursor_blink_time =
             PropertyUint32::wrap(node_ref, Role::Internal, "cursor_blink_time", 0).unwrap();
         let cursor_idle_time =
@@ -238,6 +240,8 @@ impl EditBox {
                 self_.cursor_pos.set(0);
                 self_.selected.set_null(Role::Internal, 0).unwrap();
                 self_.selected.set_null(Role::Internal, 1).unwrap();
+                self_.underline.set_null(Role::Internal, 0).unwrap();
+                self_.underline.set_null(Role::Internal, 1).unwrap();
                 self_.scroll.set(0.);
                 self_.regen_glyphs().await;
                 self_.redraw().await;
@@ -323,6 +327,7 @@ impl EditBox {
                 cursor_descent,
                 hi_bg_color,
                 selected,
+                underline,
                 z_index,
                 debug,
 
@@ -899,6 +904,86 @@ impl EditBox {
         }
     }
 
+    fn set_underline_text(&self, suggest_text: &str) {
+        if self.underline.is_null(0).unwrap() {
+            assert!(self.underline.is_null(1).unwrap());
+            debug!(target: "ui::editbox", "underline is null");
+
+            // Underline is not set. Lets insert text before cursor_pos.
+            let mut cursor_pos = self.cursor_pos.get();
+            let glyphs = self.glyphs.lock().unwrap().clone();
+
+            let mut text = String::new();
+            if glyphs.is_empty() {
+                text.push_str(suggest_text);
+            }
+            for (i, glyph) in glyphs.iter().enumerate() {
+                if cursor_pos == i as u32 {
+                    text.push_str(suggest_text);
+                }
+                text.push_str(&glyph.substr);
+            }
+            // Append to the end
+            if cursor_pos == glyphs.len() as u32 {
+                text.push_str(suggest_text);
+            }
+
+            debug!(target: "ui::editbox", "setting text = {text}");
+            self.text.set(text);
+
+            self.underline.set_u32(Role::Internal, 0, cursor_pos).unwrap();
+            cursor_pos += suggest_text.len() as u32;
+            self.underline.set_u32(Role::Internal, 1, cursor_pos).unwrap();
+            self.cursor_pos.set(cursor_pos);
+        } else {
+            assert!(!self.underline.is_null(1).unwrap());
+            debug!(target: "ui::editbox", "underline is NOT null");
+
+            // We are going to delete the current underline text and replace it with our new one.
+            let mut cursor_pos = self.cursor_pos.get();
+            let glyphs = self.glyphs.lock().unwrap().clone();
+            // Start pos remains unchanged.
+            let underline_start = self.underline.get_u32(0).unwrap() as usize;
+            let underline_end = self.underline.get_u32(1).unwrap() as usize;
+
+            debug!(target: "ui::editbox", "inserting underline text at {underline_start}");
+
+            let mut text = String::new();
+            if glyphs.is_empty() {
+                text.push_str(suggest_text);
+            }
+            for (i, glyph) in glyphs.iter().enumerate() {
+                if underline_start == i {
+                    text.push_str(suggest_text);
+                }
+                if underline_start <= i && i <= underline_end {
+                    continue
+                }
+                text.push_str(&glyph.substr);
+            }
+            // Append to the end
+            if underline_start == glyphs.len() {
+                text.push_str(suggest_text);
+            }
+
+            debug!(target: "ui::editbox", "setting text = {text}");
+            self.text.set(text);
+
+            let underline_end = (underline_start + suggest_text.len()) as u32;
+            self.underline.set_u32(Role::Internal, 1, underline_end).unwrap();
+            // Put cursor after inserted text
+            self.cursor_pos.set(underline_end);
+        }
+    }
+
+    fn reset_android_autosuggest(&self) {
+        #[cfg(target_os = "android")]
+        crate::android::reset_autosuggest();
+
+        self.underline.set_null(Role::Internal, 0).unwrap();
+        self.underline.set_null(Role::Internal, 1).unwrap();
+    }
+
     fn delete_highlighted(&self) {
         assert!(!self.selected.is_null(0).unwrap());
         assert!(!self.selected.is_null(1).unwrap());
@@ -1185,6 +1270,8 @@ impl UIObject for EditBox {
             return false
         }
 
+        self.reset_android_autosuggest();
+
         if mods.ctrl || mods.alt {
             if repeat {
                 return false
@@ -1215,6 +1302,8 @@ impl UIObject for EditBox {
             return false
         }
 
+        self.reset_android_autosuggest();
+
         let actions = {
             let mut repeater = self.key_repeat.lock().unwrap();
             repeater.key_down(PressedKey::Key(key), repeat)
@@ -1231,7 +1320,7 @@ impl UIObject for EditBox {
 
     async fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_pos: Point) -> bool {
         if !self.is_active.get() {
-            return true
+            return false
         }
 
         self.handle_click_down(btn, mouse_pos).await
@@ -1239,7 +1328,7 @@ impl UIObject for EditBox {
 
     async fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_pos: Point) -> bool {
         if !self.is_active.get() {
-            return true
+            return false
         }
 
         self.handle_click_up(btn, mouse_pos)
@@ -1255,9 +1344,11 @@ impl UIObject for EditBox {
 
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
         if !self.is_active.get() {
-            return true
+            return false
         }
 
+        self.reset_android_autosuggest();
+
         // Ignore multi-touch
         if id != 0 {
             return false
@@ -1271,6 +1362,40 @@ impl UIObject for EditBox {
             TouchPhase::Cancelled => false,
         }
     }
+
+    async fn handle_edit_text(&self, suggest_text: &str) -> bool {
+        debug!(target: "ui::editbox", "handle_edit_text({suggest_text})");
+
+        if !self.is_active.get() {
+            return false
+        }
+
+        self.set_underline_text(suggest_text);
+
+        self.regen_glyphs().await;
+        //self.apply_cursor_scrolling();
+        self.redraw().await;
+
+        true
+    }
+    async fn handle_commit_text(&self, suggest_text: &str) -> bool {
+        debug!(target: "ui::editbox", "handle_commit_text({suggest_text})");
+
+        if !self.is_active.get() {
+            return false
+        }
+
+        self.set_underline_text(suggest_text);
+
+        self.underline.set_null(Role::Internal, 0).unwrap();
+        self.underline.set_null(Role::Internal, 1).unwrap();
+
+        self.regen_glyphs().await;
+        //self.apply_cursor_scrolling();
+        self.redraw().await;
+
+        true
+    }
 }
 
 /// Filter these char events from being handled since we handle them

+ 26 - 0
bin/darkwallet/src/ui/layer.rs

@@ -265,4 +265,30 @@ impl UIObject for Layer {
         }
         false
     }
+    async fn handle_edit_text(&self, suggest_text: &str) -> bool {
+        if !self.is_visible.get() {
+            return false
+        }
+        for child in self.get_children() {
+            let obj = get_ui_object3(&child);
+            if obj.handle_edit_text(suggest_text).await {
+                //debug!(target: "layer", "handle_edit_text({suggest_text}) swallowed by {child:?}");
+                return true
+            }
+        }
+        false
+    }
+    async fn handle_commit_text(&self, suggest_text: &str) -> bool {
+        if !self.is_visible.get() {
+            return false
+        }
+        for child in self.get_children() {
+            let obj = get_ui_object3(&child);
+            if obj.handle_commit_text(suggest_text).await {
+                //debug!(target: "layer", "handle_edit_text({suggest_text}) swallowed by {child:?}");
+                return true
+            }
+        }
+        false
+    }
 }