Skip to content
Open

V3 #129

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
include: package:lints/recommended.yaml

linter:
rules:
- always_declare_return_types
- always_require_non_null_named_parameters
- annotate_overrides
- avoid_empty_else
- avoid_init_to_null
Expand All @@ -23,7 +24,6 @@ linter:
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_contains
- prefer_equal_for_default_values
- prefer_final_fields
- prefer_for_elements_to_map_fromIterable
- prefer_generic_function_type_aliases
Expand Down
176 changes: 89 additions & 87 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_mentions/flutter_mentions.dart';

void main() {
runApp(MyApp());
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
Expand All @@ -15,18 +17,18 @@ class MyApp extends StatelessWidget {
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);
const MyHomePage({super.key, this.title});

final String? title;

@override
_MyHomePageState createState() => _MyHomePageState();
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
Expand All @@ -36,99 +38,99 @@ class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title!),
title: Text(widget.title ?? ''),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
RaisedButton(
child: Text('Get Text'),
ElevatedButton(
onPressed: () {
print(key.currentState!.controller!.markupText);
},
child: const Text('Get Text'),
),
Container(
child: FlutterMentions(
key: key,
suggestionPosition: SuggestionPosition.Top,
maxLines: 5,
minLines: 1,
decoration: InputDecoration(hintText: 'hello'),
mentions: [
Mention(
trigger: '@',
style: TextStyle(
color: Colors.amber,
),
data: [
{
'id': '61as61fsa',
'display': 'fayeedP',
'full_name': 'Fayeed Pawaskar',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': '61asasgasgsag6a',
'display': 'khaled',
'full_name': 'DJ Khaled',
'style': TextStyle(color: Colors.purple),
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': 'asfgasga41',
'display': 'markT',
'full_name': 'Mark Twain',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': 'asfsaf451a',
'display': 'JhonL',
'full_name': 'Jhon Legend',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
],
matchAll: false,
suggestionBuilder: (data) {
return Container(
padding: EdgeInsets.all(10.0),
child: Row(
FlutterMentions(
key: key,
suggestionPosition: SuggestionPosition.top,
maxLines: 5,
minLines: 1,
decoration: const InputDecoration(hintText: 'hello'),
mentions: [
Mention(
trigger: '@',
style: const TextStyle(
color: Colors.amber,
),
data: [
{
'id': '61as61fsa',
'display': 'fayeedP',
'full_name': 'Fayeed Pawaskar',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': '61asasgasgsag6a',
'display': 'khaled',
'full_name': 'DJ Khaled',
'style': const TextStyle(color: Colors.purple),
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': 'asfgasga41',
'display': 'markT',
'full_name': 'Mark Twain',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
{
'id': 'asfsaf451a',
'display': 'JhonL',
'full_name': 'Jhon Legend',
'photo':
'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
},
],
matchAll: false,
suggestionBuilder: (data) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
data['photo'],
),
),
const SizedBox(
width: 20.0,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
data['photo'],
),
),
SizedBox(
width: 20.0,
),
Column(
children: <Widget>[
Text(data['full_name']),
Text('@${data['display']}'),
],
)
Text(data['full_name']),
Text('@${data['display']}'),
],
),
);
}),
Mention(
trigger: '#',
disableMarkup: true,
style: TextStyle(
color: Colors.blue,
),
data: [
{'id': 'reactjs', 'display': 'reactjs'},
{'id': 'javascript', 'display': 'javascript'},
],
matchAll: true,
)
],
),
)
],
),
);
},
),
Mention(
trigger: '#',
disableMarkup: true,
style: const TextStyle(
color: Colors.blue,
),
data: [
{'id': 'reactjs', 'display': 'reactjs'},
{'id': 'javascript', 'display': 'javascript'},
],
matchAll: true,
)
],
),
],
),
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
sdk: '>=2.12.0 <3.0.0'
sdk: ^3.0.0

dependencies:
flutter:
Expand All @@ -28,7 +28,7 @@ dependencies:

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
cupertino_icons: ^1.0.6

dev_dependencies:
flutter_test:
Expand Down
112 changes: 65 additions & 47 deletions lib/src/annotation_editing_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,78 +6,96 @@ class AnnotationEditingController extends TextEditingController {
Map<String, Annotation> _mapping;
String? _pattern;

// Generate the Regex pattern for matching all the suggestions in one.
/// Generate the Regex pattern for matching all the suggestions in one.
AnnotationEditingController(this._mapping)
: _pattern = _mapping.keys.isNotEmpty
? "(${_mapping.keys.map((key) => RegExp.escape(key)).join('|')})"
: null;

/// Finds the annotation for a given match string.
/// Returns null if no matching annotation is found.
Annotation? _findAnnotation(String matchText) {
// First try direct lookup
final directMatch = _mapping[matchText];
if (directMatch != null) return directMatch;

// Then try regex matching
for (final key in _mapping.keys) {
final reg = RegExp(key);
if (reg.hasMatch(matchText)) {
return _mapping[key];
}
}

return null;
}

/// Can be used to get the markup from the controller directly.
String get markupText {
final someVal = _mapping.isEmpty
? text
: text.splitMapJoin(
RegExp('$_pattern'),
onMatch: (Match match) {
final mention = _mapping[match[0]!] ??
_mapping[_mapping.keys.firstWhere((element) {
final reg = RegExp(element);

return reg.hasMatch(match[0]!);
})]!;

// Default markup format for mentions
if (!mention.disableMarkup) {
return mention.markupBuilder != null
? mention.markupBuilder!(
mention.trigger, mention.id!, mention.display!)
: '${mention.trigger}[__${mention.id}__](__${mention.display}__)';
} else {
return match[0]!;
}
},
onNonMatch: (String text) {
return text;
},
);

return someVal;
}
if (_mapping.isEmpty || _pattern == null || _pattern == '()') {
return text;
}

return text.splitMapJoin(
RegExp('$_pattern'),
onMatch: (Match match) {
final matchText = match[0]!;
final mention = _findAnnotation(matchText);

Map<String, Annotation> get mapping {
return _mapping;
if (mention == null) return matchText;

// Default markup format for mentions
if (!mention.disableMarkup) {
return mention.markupBuilder != null
? mention.markupBuilder!(
mention.trigger, mention.id ?? '', mention.display ?? '')
: '${mention.trigger}[__${mention.id}__](__${mention.display}__)';
} else {
return matchText;
}
},
onNonMatch: (String text) => text,
);
}

set mapping(Map<String, Annotation> _mapping) {
this._mapping = _mapping;
/// Returns the current mapping of patterns to annotations.
Map<String, Annotation> get mapping => _mapping;

_pattern = "(${_mapping.keys.map((key) => RegExp.escape(key)).join('|')})";
/// Updates the mapping and regenerates the pattern.
set mapping(Map<String, Annotation> newMapping) {
_mapping = newMapping;
_pattern = _mapping.keys.isNotEmpty
? "(${_mapping.keys.map((key) => RegExp.escape(key)).join('|')})"
: null;
}

@override
TextSpan buildTextSpan({BuildContext? context, TextStyle? style, bool? withComposing}) {
var children = <InlineSpan>[];
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
final children = <InlineSpan>[];

if (_pattern == null || _pattern == '()') {
if (_pattern == null || _pattern == '()' || _mapping.isEmpty) {
children.add(TextSpan(text: text, style: style));
} else {
text.splitMapJoin(
RegExp('$_pattern'),
onMatch: (Match match) {
if (_mapping.isNotEmpty) {
final mention = _mapping[match[0]!] ??
_mapping[_mapping.keys.firstWhere((element) {
final reg = RegExp(element);

return reg.hasMatch(match[0]!);
})]!;
final matchText = match[0]!;
final mention = _findAnnotation(matchText);

if (mention != null) {
children.add(
TextSpan(
text: match[0],
style: style!.merge(mention.style),
text: matchText,
style: style?.merge(mention.style) ?? mention.style,
),
);
} else {
// Fallback if no mention found - just use regular style
children.add(TextSpan(text: matchText, style: style));
}

return '';
Expand Down
Loading