Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Crazelu/fluttertagger
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: Tweetoshi/fluttertagger
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 4 commits
  • 2 files changed
  • 1 contributor

Commits on Jan 25, 2024

  1. tags getter

    MaTeS72 committed Jan 25, 2024
    Copy the full SHA
    b744f9f View commit details
  2. improvements

    MaTeS72 committed Jan 25, 2024
    Copy the full SHA
    70a866a View commit details
  3. fix constructor

    MaTeS72 committed Jan 25, 2024
    Copy the full SHA
    0c13682 View commit details

Commits on Jan 26, 2024

  1. clear only if empty

    MaTeS72 committed Jan 26, 2024
    Copy the full SHA
    bb25159 View commit details
Showing with 162 additions and 98 deletions.
  1. +79 −0 lib/src/regex.dart
  2. +83 −98 lib/src/tagger.dart
79 changes: 79 additions & 0 deletions lib/src/regex.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
final kRelayUrlRegex = RegExp(
r'^(wss?:\/\/)([0-9]{1,3}(?:\.[0-9]{1,3}){3}|[^:]+):?([0-9]{1,5})?$',
);

final kUrlRegex = RegExp(
r'((((http?:www\.)|(http?:\/\/)|(https?:www\.)|(https?:\/\/)|(www\.))[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9]{1,63}(\/[-a-zA-Z0-9()@:%_\+,.~#?\-$&\/=]*)?))|(youtu.be\/[-a-zA-Z0-9()@:%_\+,.~#?\-$&\/=]*)',
);

final kReferenceRegex = RegExp(r'(#\[[0-9]+])');

final kMentionRegex = RegExp(
r'(?<![\w])@[\S]*\b',
caseSensitive: false,
unicode: true,
);

final kMentionRegexWrite = RegExp(
r'(?<![\w])@npub1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kNoteRegexWrite = RegExp(
r'(?<![\w])@note1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kMentionNostrRegexWrite = RegExp(
r'(?<![\w])nostr:npub1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kProfileNostrRegexWrite = RegExp(
r'(?<![\w])nostr:nprofile1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kNoteNostrRegexWrite = RegExp(
r'(?<![\w])nostr:note1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kEventNostrRegexWrite = RegExp(
r'(?<![\w])nostr:nevent1[023456789acdefghjklmnpqrstuvwxyz]*\b',
caseSensitive: false,
unicode: true,
);

final kHashtagRegex = RegExp(
r'#[^\s]+',
caseSensitive: false,
unicode: true,
);

final kLnInvoiceRegex = RegExp(
'(,*?((lnbc)([0-9]{1,}[a-z0-9]+){1}))',
unicode: true,
);

final kEmojiRegex = RegExp(
r'/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g',
);

final kNip08Regex = RegExp(
r'(#\[(\d+)\])|(@#\[(\d+)\])',
);

final listOfWritingRegex = [
kUrlRegex,
kMentionRegexWrite,
kMentionRegex,
kLnInvoiceRegex,
kProfileNostrRegexWrite,
kHashtagRegex,
];
181 changes: 83 additions & 98 deletions lib/src/tagger.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:fluttertagger/src/regex.dart';
import 'package:fluttertagger/src/tagged_text.dart';
import 'package:fluttertagger/src/trie.dart';

@@ -8,6 +9,16 @@ typedef FlutterTaggerWidgetBuilder = Widget Function(
GlobalKey key,
);

class _TextPart {
_TextPart(
this.content, {
this.isRegexMatch = false,
});

String content;
bool isRegexMatch;
}

///Formatter for tags in the [TextField] associated
///with [FlutterTagger].
typedef TagTextFormatter = String Function(
@@ -48,6 +59,7 @@ class FlutterTagger extends StatefulWidget {
this.triggerCharacterAndStyles = const {},
this.onFormattedTextChanged,
this.searchRegex,
required this.tagStyle,
this.triggerCharactersRegex,
this.tagTextFormatter,
this.animationController,
@@ -118,6 +130,7 @@ class FlutterTagger extends StatefulWidget {
///trigger character.
final Map<String, TextStyle> triggerCharacterAndStyles;

final TextStyle tagStyle;
@override
State<FlutterTagger> createState() => _FlutterTaggerState();
}
@@ -789,6 +802,7 @@ class _FlutterTaggerState extends State<FlutterTagger> {
debugPrint(trace.toString());
}
}


@override
void initState() {
@@ -797,11 +811,12 @@ class _FlutterTaggerState extends State<FlutterTagger> {
controller._setDeferCallback(() => _defer = true);
controller._setTags(_tags);
controller._setTriggerCharactersRegExpPattern(_triggerCharactersPattern);
controller._setTagStyles(widget.triggerCharacterAndStyles);
controller._setTagStyle(widget.tagStyle);
controller.addListener(_tagListener);
controller._onClear(() {
if(controller.text.isEmpty){
_tags.clear();
_tagTrie.clear();
_tagTrie.clear();}
});
controller._onDismissOverlay(() {
_shouldHideOverlay(true);
@@ -835,10 +850,12 @@ class FlutterTaggerController extends TextEditingController {
late final Trie _trie = Trie();
late Map<TaggedText, String> _tags;

late Map<String, TextStyle> _tagStyles;
Map<TaggedText, String> get tags => _tags;

late TextStyle _tagStyle;

void _setTagStyles(Map<String, TextStyle> tagStyles) {
_tagStyles = tagStyles;
void _setTagStyle(TextStyle tagStyle) {
_tagStyle = tagStyle;
}

RegExp? _triggerCharsPattern;
@@ -1004,117 +1021,85 @@ class FlutterTaggerController extends TextEditingController {
_addTagCallback = callback;
}

@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
assert(!value.composing.isValid ||
!withComposing ||
value.isComposingRangeValid);

return _buildTextSpan(style);
}

///Parses [text] and styles nested tagged texts using style from [_tagStyles].
List<TextSpan> _getNestedSpans(String text, int startIndex) {
if (text.isEmpty) return [];


List<TextSpan> spans = [];
int start = startIndex;
List<_TextPart> _splitTextByRegexList(List<RegExp> regexList, String input) {
final result = <_TextPart>[];
var previousMatchEnd = 0;

final nestedWords = text.splitWithDelim(_triggerCharactersPattern);
bool startsWithTrigger = text[0].contains(_triggerCharactersPattern) &&
nestedWords.first.isNotEmpty;
// Create a list to keep track of all matches across regex patterns
final allMatches = <Match>[];

String triggerChar = "";
int triggerCharIndex = 0;
for (final regex in regexList) {
allMatches.addAll(regex.allMatches(input).toList());
}

for (int i = 0; i < nestedWords.length; i++) {
final nestedWord = nestedWords[i];
// Sort allMatches based on the start index
allMatches.sort((a, b) => a.start.compareTo(b.start));

if (nestedWord.contains(_triggerCharactersPattern)) {
if (triggerChar.isNotEmpty && triggerCharIndex == i - 2) {
spans.add(TextSpan(text: triggerChar));
start += triggerChar.length;
triggerChar = "";
triggerCharIndex = i;
continue;
}
triggerChar = nestedWord;
triggerCharIndex = i;
for (final match in allMatches) {
// Check if this match overlaps with a previous match
if (match.start < previousMatchEnd) {
// If it does, ignore this match
continue;
}

String word;
if (i == 0) {
word = startsWithTrigger ? "$triggerChar$nestedWord" : nestedWord;
} else {
word = "$triggerChar$nestedWord";
}

TaggedText? taggedText;

if (word.isNotEmpty) {
taggedText = _trie.search(word, start);
}

if (taggedText == null) {
spans.add(TextSpan(text: word));
} else if (taggedText.startIndex == start) {
String suffix = word.substring(taggedText.text.length);

spans.add(
TextSpan(
text: taggedText.text,
style: _tagStyles[triggerChar],
// Add the non-matched text before the match
if (match.start > previousMatchEnd) {
final nonMatchedText = input.substring(previousMatchEnd, match.start);
result.add(
_TextPart(
nonMatchedText,
),
);
if (suffix.isNotEmpty) spans.add(TextSpan(text: suffix));
} else {
spans.add(TextSpan(text: word));
}

start += word.length;
triggerChar = "";
}

return spans;
}
// Add the matched text
final matchedText = match.group(0)!;
result.add(_TextPart(matchedText, isRegexMatch: true));

///Builds text value with tagged texts styled using styles from [_tagStyles].
TextSpan _buildTextSpan(TextStyle? style) {
if (text.isEmpty) return const TextSpan();

final splitText = text.split(" ");
previousMatchEnd = match.end;
}

List<TextSpan> spans = [];
int start = 0;
int end = splitText.first.length;
// Add the rest of the non-matched text after the last match, if any
if (previousMatchEnd < input.length) {
final nonMatchedText = input.substring(previousMatchEnd);
result.add(
_TextPart(
nonMatchedText,
),
);
}

for (int i = 0; i < splitText.length; i++) {
final currentText = splitText[i];
return result;
}

if (currentText.contains(_triggerCharactersPattern)) {
final nestedSpans = _getNestedSpans(currentText, start);
spans.addAll(nestedSpans);
spans.add(const TextSpan(text: " "));
@override
TextSpan buildTextSpan({
BuildContext? context,
TextStyle? style,
bool? withComposing,
}) {
final split = _splitTextByRegexList(
listOfWritingRegex,
text,
);

start = end + 1;
if (i + 1 < splitText.length) {
end = start + splitText[i + 1].length;
}
} else {
start = end + 1;
if (i + 1 < splitText.length) {
end = start + splitText[i + 1].length;
}
spans.add(TextSpan(text: "$currentText "));
}
}
return TextSpan(children: spans, style: style);
return TextSpan(
children: split.map(
(e) {
return TextSpan(
text: e.content,
style: e.isRegexMatch
? _tagStyle
: style,
);
},
).toList(),
style: style,
);
}

}

extension _RegExpExtension on RegExp {