How to change text color according to valid(@mentions) and all hashtags?

333

From what I've understood, you want to highlight hashtags and mentions (pre stored in a list) in a String.

Let me break this into 2 parts, the first would be to extract the hashtags & mentions from the text.

List<String> getAllHashtags(String text) {
  final regexp = RegExp(r'\#[a-zA-Z0-9]+\b()');

  List<String> hashtags = [];

  regexp.allMatches(text).forEach((element) {
    if (element.group(0) != null) {
      hashtags.add(element.group(0).toString());
    }
  });

  return hashtags;
}

List<String> getAllMentions(String text) {
  final regexp = RegExp(r'\@[a-zA-Z0-9]+\b()');

  List<String> mentions = [];

  regexp.allMatches(text).forEach((element) {
    if (element.group(0) != null) {
      mentions.add(element.group(0).toString());
    }
  });

  return mentions;
}

The above code snippet will successfully extract hashtags & mentions from the given sentence and return it as a list.

The next step would be to build the RichText with the different TextSpans.

RichText buildHighlightedText(String text) {
  // clean the text
  text = cleanText(text);

  List<String> validMentions = ["@mention1", "@mention2"];
  
  List<String> hashtags = getAllHashtags(text);
  List<String> mentions = getAllMentions(text);

  List<TextSpan> textSpans = [];

  text.split(" ").forEach((value) {
    if (hashtags.contains(value)) {
      textSpans.add(TextSpan(
        text: '$value ',
        style: TextStyle(color: Colors.amber, fontWeight: FontWeight.bold),
      ));
    } else if (mentions.contains(value) && validMentions.contains(value)) {
      textSpans.add(TextSpan(
        text: '$value ',
        style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
      ));
    } else {
      textSpans.add(TextSpan(text: '$value '));
    }
  });

  return RichText(text: TextSpan(children: textSpans));
}

The text has been split by empty spaces, and filters hashtags/mentions and returns differently styled TextSpans for each. This is a more concise and cleaner way of doing what you're looking for.

Here's an example:

running the application with an example

Edit:

In order to parse hashtags without spaces, we need to add each instance with a space in front.

String cleanText(String text) {
  text = text.replaceAllMapped(
      RegExp(r'\w#+'), (Match m) => "${m[0]?.split('').join(" ")}");

  text = text.replaceAllMapped(
      RegExp(r'\w@+'), (Match m) => "${m[0]?.split('').join(" ")}");

  return text;
}
Share:
333
kanwar manraj
Author by

kanwar manraj

Updated on December 30, 2022

Comments

  • kanwar manraj
    kanwar manraj over 1 year

    I wanted to display hashtags and valid mentions with different colors in the text.


    I got helped with this code which works only for hashtags

    RichText _convertHashtag(String text) {
      List<String> split = text.split(RegExp("#"));
      List<String> hashtags = split.getRange(1, split.length).fold([], (t, e) {
        var texts = e.split(" ");
        if (texts.length > 1) {
          return List.from(t)
            ..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
        }
        return List.from(t)..add("#${texts.first}");
      });
      return RichText(
        text: TextSpan(
          children: [TextSpan(text: split.first)]..addAll(hashtags
              .map((text) => text.contains("#")
                  ? TextSpan(text: text, style: TextStyle(color: Colors.blue))
                  : TextSpan(text: text))
              .toList()),
        ),
      );
    }
    

    I modified it like:

    
      List valid_mentions = ['@mention1', '@mention2'];//these are the valid mention
    
      RichText _convertHashtag(String text) {
        List<String> split = text.split(RegExp("#|@"));
        List<String> hashtags = split.getRange(1, split.length).fold([], (t, e) {
          var texts = e.split(" ");
    
          //here adding `@` sign and `#` sign to the given texts and storing them in the `hashtags` list
          if (texts.length > 1) {
            if (valid_mentions.contains(texts.first))
              return List.from(t)
                ..addAll(["@${texts.first}", "${e.substring(texts.first.length)}"]);
            else if (text.contains('@${texts.first}')) {
              return List.from(t)
                ..addAll(["@${texts.first}", "${e.substring(texts.first.length)}"]);
            } else
              return List.from(t)
                ..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
          } else {
            if (valid_mentions.contains(texts.first))
              return List.from(t)..addAll(["@${texts.first}"]);
            else if (text.contains('@${texts.first}')) {
              return List.from(t)..addAll(["@${texts.first}"]);
            } else
              return List.from(t)..addAll(["#${texts.first}"]);
          }
        });
        return RichText(
          text: TextSpan(
            children: [TextSpan(text: split.first)]..addAll(hashtags.map((text) {
                return text.contains("@")
                    ? valid_mentions.contains(text)
                        ? //checking if the mention is valid
                        TextSpan(
                            text: text,
                            recognizer: TapGestureRecognizer()
                              ..onTap = () {
                                print(text);
                              },
                            style: TextStyle(color: Colors.blue))
                        : TextSpan(
                            text: text,
                          )
                    : text.contains("#")
                        ? TextSpan(
                            text: text,
                            recognizer: TapGestureRecognizer()
                              ..onTap = () {
                                print(text);
                              },
                            style: TextStyle(color: Colors.blue))
                        : TextSpan(
                            text: text,
                          );
              }).toList()),
          ),
        );
      }
    
    

    I am able to make the required changes, but i belive its not an optimized way and there is a lot of boiler plate code. How can i optimize it?

    input:"I love #flutter #android @mention1 @mention2 @mention3 "

    output:"I love #flutter #android @mention1 @mention2 @mention3 "

    here @mention3 is not hyperlinked because its not an valid mention.

  • kanwar manraj
    kanwar manraj almost 3 years
    Thanks 😊 , that worked pretty well. One more thing, what changes need to be made if we want #hello#world or @mention1@mention2 even to be the valid hashtags and mention. As we are using text.split(" ").forEach((value) , they wouldn't split and will not be considered.
  • Akassharjun Shanmugarajah
    Akassharjun Shanmugarajah almost 3 years
    That's true. In that case the text has to be edited to have spaces in front of the values. I've edited my answer. @kanwarmanraj