daohoangson / flutter_widget_from_html

Flutter package to render html as widgets that supports hyperlink, image, audio, video, iframe and many other tags.
https://pub.dev/packages/flutter_widget_from_html
MIT License
641 stars 241 forks source link

single click select callback #673

Closed bksubhuti closed 1 year ago

bksubhuti commented 2 years ago

Use case

We have an app that has single click to get a word selected and then a callback with that word. isSelectable and onSelectionChange are good, but we would really like to capture the word with a tap and a mouse click for easy dictionary lookups.

The big work around is to place url links surrounding each word.. but this is heavy .

Proposal

Below is the code that we use to generate a single click callback in WebView widget which we are trying to migrate out of. var pageContent = document.getElementById("page_content"); pageContent.addEventListener('click', function(e) { // e.preventDefault();

var caret, range;
if (document.caretRangeFromPoint) { // webkit/chrome
// alert('browser is webkit');
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
} else if (document.caretPositionFromPoint) { // gecko/firefox
    caret = document.caretPositionFromPoint(e.clientX, e.clientY);
    range = document.createRange();
    range.setStart(caret.offsetNode, caret.offset); // DOM element and position
}

// get word
if (range) { // chrome and firefox
    selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
    // selection.modify('move', 'backward', 'word');
    // selection.modify('extend', 'forward', 'word');

    var part_one = "";
    var part_two = "";
    var click_word;
    part_one = selection.toString();

    while(! /\s/.test(part_one)){
        selection.modify('extend', 'backward', 'character');
        part_one = selection.toString();
    }
    part_one = part_one.trim();
    selection.modify('move', 'forward', 'character');

    //extend selection to forward until founding space
    selection.modify('extend', 'forward', 'character');
    part_two = selection.toString();

    while(! /\s/.test(part_two)){
        selection.modify('extend', 'forward', 'character');
        part_two = selection.toString();
    }
    part_two = part_two.trim();
    click_word = part_one + part_two;

    // alert(click_word);
    selection.removeAllRanges();
    // for callback
    if (click_word){
        Define.postMessage(click_word);
    }
    // Define.postMessage('ကုသလာ');
    selection.collapse(selection.anchorNode, 0);

} else if (document.body.createTextRange) {
    // internet explorer
    // get word
    range = document.body.createTextRange();
    range.moveToPoint(event.clientX, event.clientY);
    range.select();
    range.expand('word');
    alert(range.text);
}

}, false);

daohoangson commented 2 years ago

So technically you don't need the text to be selectable, you just need to capture the word when it is tapped?

bksubhuti commented 2 years ago

yes.. exactly. Tap to capture word. It is very useful for a "reading" program that is dictionary intensive.
Imagine that you read a paragraph of religious text and you need a dictionary for 5 words of that paragraph to help you remember. It is a lot of work, to long_click but not soooo bad. (I really appreciate the onSelectionChange.. it is much better than using a menu popup). But a single tap would be much better. Currently that is how it works. Here is video of an older JS electron app called tipitaka pali projector. https://youtu.be/64PwmEf3utI?t=435 7 minutes 10 sec shows a little bit and here https://youtu.be/3rDYs1u9T-8?t=207 3:27

I don't have a video for the flutter (replacement) app.
but it is released already on the android play and ios app store. Tipitaka Pali Reader You can see how it works.

apple ios https://apps.apple.com/us/developer/path-nirvana-foundation/id1434955292

android https://play.google.com/store/apps/details?id=com.paauk.tipitakapalireader&hl=en&gl=US

daohoangson commented 1 year ago

You can use hitTest to obtain the text under the tap. Something like this:

          GestureDetector(
            child: HtmlWidget(html, key: key),
            onTapUp: (details) {
              final box = key.currentContext!.findRenderObject()! as RenderBox;
              final result = BoxHitTestResult();
              final offset = box.globalToLocal(details.globalPosition);
              if (!box.hitTest(result, position: offset)) {
                return;
              }

              for (final entry in result.path) {
                final target = entry.target;
                if (entry is! BoxHitTestEntry || target is! RenderParagraph) {
                  continue;
                }

                final p = target.getPositionForOffset(entry.localPosition);
                final text = target.text.toPlainText();
                print(text.substring(p.offset, p.offset + 5));
              }
            },
          )

With the text and offset, you should be able to figure out the tapped word.

bksubhuti commented 1 year ago

ven pndazza (the main programmer), said this code now works. This helps us a real lot with the ios problem. Keep open .. I'll report about the single click issue later.

bksubhuti commented 1 year ago

To answer your question about needing selectable.. we do need the text to be selectable as well. Some issues exist with some words not getting highlighted -- selectable, but we can capture the word. We will research this more.

daohoangson commented 1 year ago

We already have a proposed solution so I'm closing this for now. Feel free to reopen if you want to revisit this.