1

I have a String that includes mentions with an @ tag and I want to create a richtext, where every tag is included in a map of mentions with usernames and userIds is highlighted and make it clickable.

// dummy class

class Reply {
  List<dynamic> mentions;
  String? text;
  Reply({required this.mentions, required this.text});
}

// sample reply

 Reply sample = Reply(
    mentions: [
      {'username': 'sampleUser1', 'userId': '0x123'},
      {'username': 'sampleUser2', 'userId': '0x321'}
    ],
    text: 'This text mentions @0x123 as well as @0x321',
  );

// replacing all matching ids with the respective usernames

 for (var ment in sample.mentions) {
    sample.text = sample.text!.replaceAll(ment['userId'], ment['username']);
  }

print(sample.text); // This text mentions @sampleUser1 as well as @sampleUser2

This is where I got stuck. I want to create a richtext that highlights the usernames in the text in bold and make it clickable.

RichText(
  text: TextSpan(
      children: 'sample sample' /*sample.text goes here*/
          .split(' ') /*find words that start with '@' and include a username that can also be found in the list of mentions*/
          .map((word) => TextSpan(
              text: word + ' ',
              style: TextStyle(
                  fontWeight:
                      true /*condition if word is found in mentions*/
                          ? FontWeight.bold
                          : FontWeight.normal),
              recognizer: TapGestureRecognizer()
                ..onTap =
                    true /*condition if word is found in mentions*/
                        ? () {
                            /*do something*/
                          }
                        : () {
                            /*do nothing*/
                          }))
          .toList())),

Any help is appreciated, I will keep trying, and update here when I figure it out.

alexn62
  • 51
  • 7
  • check how `buildTextSpan` method is implemented [here](https://stackoverflow.com/a/59773962/2252830) - of course you dont need to extend `TextEditingController` - the only thing you need is a `TextSpan` – pskink Aug 14 '21 at 08:28
  • Thank you! I figured it out using your method! I'll leave a reply in case anyone else is trying to do the same! @pskink – alexn62 Aug 14 '21 at 10:07
  • sure, your welcome – pskink Aug 14 '21 at 11:20

1 Answers1

3

Thanks to @pskink I got it working.

 buildTextSpan(String s, List<dynamic>? mentions) {

    var regularStyle = //regular style
    var mentionStyle = //style with mentions

    List<InlineSpan> children = [];

//return regular text if no users have been mentioned
    if(mentions == null || mentions.isEmpty){
      children.add(TextSpan(text: s, style: regularStyle));
      return TextSpan(children: children);
    }

    s.splitMapJoin(
//only match strings that are prefixed with '@' 
//and are contained in the mentions-map at the 'userId'-key that is passed in
//join the regex with 'or' to include all mentions
      RegExp(mentions.map((e) => '@'+e['userId']!).join('|')),

//if there's a match, re-add the '@' and grab the username to the userId as provided by the mentions-map
//substring(1) to exclude the @-sign when looking up the value in the map
      onMatch: (Match match) {
        children.add(TextSpan(
          text: '@' + mentions.firstWhere((element) => element['userId'] == match[0]!.substring(1))['username']!,
          style: mentionStyle,
//app specific: navigate to user screen and pass the userId
          recognizer: TapGestureRecognizer()..onTap = () => //navigation logic));
//return empty string that will not be used but has to be returned due to null-safety
        return '';
//add regular textspan on no match
      },onNonMatch: (String text) {children.add(TextSpan(
          text: text,
          style: regularStyle
        ));
        return '';}
    );
    return TextSpan(children: children);
  }
alexn62
  • 51
  • 7