flutterwebviewselectiongesture-recognitionwebview-flutter

Flutter: how to select text in a WebView (WebViewWidget) and do something with it in a function?


I want the users of my app to be able to select text (words, but also whole sentences) in a WebViewWidget, and have an audio of that text played to them (via tts). I only want this to work with u̲n̲d̲e̲r̲l̲i̲n̲e̲d̲ text (or text visually highlighted and set apart from the rest in any other way).

This is part of my Widget tree, where I have tested a few gestureRecognizers (please read the comments):

child: GestureDetector(
  //gestureRecognizers of WebViewWidget don't do anything without this GestureDetector,
  //see: https://stackoverflow.com/questions/58811375/tapgesturerecognizer-not-working-in-flutter-webview#answer-59298134
  onTap: () => print('this line doesnt get printed'),
  child: WebViewWidget(
    controller: _controller,
    gestureRecognizers: {
      Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()
        ..onLongPress = () {
          print('onLongPress');
        }
        ..onLongPressStart = (LongPressStartDetails details) {
          print('onLongPressStart, ${details.globalPosition}, ${details.localPosition}');
        }),
      Factory<TapGestureRecognizer>(() => TapGestureRecognizer()
        ..onTap = () {
          print('onTap'); //this doesn't get printed at all
        }
        ..onTapDown = (TapDownDetails details) {
          //comment: this doesn't get printed unless I tap for a little long time (not as long as longPress)
          print('onTapDown, details: $details');
          /*TODO: I want to get a text selection and play audio (with tts)
             String selectedText = ...?
             TTSHelper.speak(text: selectedText);
           */
        }),
    },
  ),
),

Another approach to a possible solution for what I want

I have an initialization function, where I take html (without the html, body & head tags...) from firestore and wrap it in the html, body & head tags along with some custom CSS.

If there is no way to achieve my goal with Dart code, maybe there is a way for me to add JavaScript to the html, so that underlined words/sentences can be tapped to play audio via tts, or at least send that text over to my Dart code somehow?

  String _prepareHTML(String content) {
    return """<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
  body {background-color: ${getCSSColor(context, CustomColors.mainAppBG)};}
  h1, h2, h3, h4, h5, h6 {color: ${getCSSColor(context, CustomColors.blueWhiteText)};}
  p {color: ${getCSSColor(context, CustomColors.text)};}
  table { 
    width: 100%;
    table-layout: fixed;
    word-wrap: break-word;
  }
  table, tr, th, td {
    color: ${getCSSColor(context, CustomColors.text)};
    border: 1px solid ${getCSSColor(context, CustomColors.text)};
    border-collapse: collapse;
  }
  th, td {
    padding: 6px 12px;
    font-weight: bold;
    font-size: 1.25em;
  }
</style>
</head>
<body>
  $content
</body>
</html>""";
  }

Solution

  • I think you might be able to achieve what you want by using some javascript and a javascriptChannel on the WebViewWidget. General idea below:

    var controller = WebViewController();
    controller.addJavascriptChannel(
      JavascriptChannel(
        name: 'underlinedTextSelectChannel',
        onMessageReceived: (message) {
          var data = json.decode(message.message);
          var text = data.text as String;
    
          TTSHelper.speak(text: selectedText);
        }
      )
    )
    
    ...
    
    var html = """
    <html>
    ...
    <body>
      $content
      # Within content:
      This is some text <p class="underlined-text">Say this!</p> Don't say this.
      <script>
        var underlinedTexts = document.querySelectorAll('.underlined-text');
        for(var t of underlinedTexts) {
          t.addEventListener('onClick', (e) => {
            underlinedTextSelectChannel.postMessage(JSON.stringify({
            text: e.target.innerText
           }))
          })
        }
      </script>
    </body>
    </html>
    """