diff --git a/example/lib/main.dart b/example/lib/main.dart index 0c0636d..db7115f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -41,7 +41,7 @@ class _MyHomePageState extends State { body: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - RaisedButton( + ElevatedButton( child: Text('Get Text'), onPressed: () { print(key.currentState!.controller!.markupText); @@ -92,26 +92,31 @@ class _MyHomePageState extends State { }, ], matchAll: false, - suggestionBuilder: (data) { - return Container( - padding: EdgeInsets.all(10.0), - child: Row( - children: [ - CircleAvatar( - backgroundImage: NetworkImage( - data['photo'], - ), - ), - SizedBox( - width: 20.0, - ), - Column( + suggestionBuilder: (data, onTap) { + return Material( + child: InkWell( + onTap: () => onTap.call(data), + child: Container( + padding: EdgeInsets.all(10.0), + child: Row( children: [ - Text(data['full_name']), - Text('@${data['display']}'), + CircleAvatar( + backgroundImage: NetworkImage( + data['photo'], + ), + ), + SizedBox( + width: 20.0, + ), + Column( + children: [ + Text(data['full_name']), + Text('@${data['display']}'), + ], + ) ], - ) - ], + ), + ), ), ); }), diff --git a/lib/flutter_mentions.dart b/lib/flutter_mentions.dart index f932628..6b43759 100644 --- a/lib/flutter_mentions.dart +++ b/lib/flutter_mentions.dart @@ -10,3 +10,4 @@ part 'src/annotation_editing_controller.dart'; part 'src/mention_view.dart'; part 'src/models.dart'; part 'src/option_list.dart'; +part 'src/mentions_to_annotations_converter.dart'; diff --git a/lib/src/mention_view.dart b/lib/src/mention_view.dart index ec8be89..0bf10c5 100644 --- a/lib/src/mention_view.dart +++ b/lib/src/mention_view.dart @@ -50,6 +50,9 @@ class FlutterMentions extends StatefulWidget { this.appendSpaceOnAdd = true, this.hideSuggestionList = false, this.onSuggestionVisibleChanged, + this.suggestionListWidth, + this.controller, + this.sugggestionContainerBuilder, }) : super(key: key); final bool hideSuggestionList; @@ -84,6 +87,8 @@ class FlutterMentions extends StatefulWidget { /// Defaults to `300.0` final double suggestionListHeight; + final double? suggestionListWidth; + /// A Functioned which is triggered when ever the input changes /// but with the markup of the selected mentions /// @@ -204,7 +209,7 @@ class FlutterMentions extends StatefulWidget { /// /// This setting is only honored on iOS devices. /// - /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness]. + /// If unset, defaults to the brightness of [ThemeData.brightness].]. final Brightness? keyboardAppearance; /// {@macro flutter.widgets.editableText.scrollPadding} @@ -241,6 +246,9 @@ class FlutterMentions extends StatefulWidget { /// {@macro flutter.services.autofill.autofillHints} final Iterable? autofillHints; + final AnnotationEditingController? controller; + final Widget Function(Widget child, BoxConstraints boxConstraints)? + sugggestionContainerBuilder; @override FlutterMentionsState createState() => FlutterMentionsState(); } @@ -251,47 +259,6 @@ class FlutterMentionsState extends State { LengthMap? _selectedMention; String _pattern = ''; - Map mapToAnotation() { - final data = {}; - - // Loop over all the mention items and generate a suggestions matching list - widget.mentions.forEach((element) { - // if matchAll is set to true add a general regex patteren to match with - if (element.matchAll) { - data['${element.trigger}([A-Za-z0-9])*'] = Annotation( - style: element.style, - id: null, - display: null, - trigger: element.trigger, - disableMarkup: element.disableMarkup, - markupBuilder: element.markupBuilder, - ); - } - - element.data.forEach( - (e) => data["${element.trigger}${e['display']}"] = e['style'] != null - ? Annotation( - style: e['style'], - id: e['id'], - display: e['display'], - trigger: element.trigger, - disableMarkup: element.disableMarkup, - markupBuilder: element.markupBuilder, - ) - : Annotation( - style: element.style, - id: e['id'], - display: e['display'], - trigger: element.trigger, - disableMarkup: element.disableMarkup, - markupBuilder: element.markupBuilder, - ), - ); - }); - - return data; - } - void addMention(Map value, [Mention? list]) { final selectedMention = _selectedMention!; @@ -372,9 +339,13 @@ class FlutterMentionsState extends State { @override void initState() { - final data = mapToAnotation(); - - controller = AnnotationEditingController(data); + if (widget.controller == null) { + final data = MentionsToAnnotationsConverter.convert(widget.mentions); + print(data); + controller = AnnotationEditingController(data); + } else { + controller = widget.controller; + } if (widget.defaultText != null) { controller!.text = widget.defaultText!; @@ -399,8 +370,9 @@ class FlutterMentionsState extends State { @override void didUpdateWidget(widget) { super.didUpdateWidget(widget); - - controller!.mapping = mapToAnotation(); + controller ??= widget.controller; + controller!.mapping = + MentionsToAnnotationsConverter.convert(widget.mentions); } @override @@ -412,21 +384,26 @@ class FlutterMentionsState extends State { : widget.mentions[0]; return Container( - child: PortalEntry( - portalAnchor: widget.suggestionPosition == SuggestionPosition.Bottom - ? Alignment.topCenter - : Alignment.bottomCenter, - childAnchor: widget.suggestionPosition == SuggestionPosition.Bottom - ? Alignment.bottomCenter - : Alignment.topCenter, - portal: ValueListenableBuilder( + child: PortalTarget( + anchor: Aligned( + follower: widget.suggestionPosition == SuggestionPosition.Bottom + ? Alignment.topCenter + : Alignment.bottomCenter, + target: widget.suggestionPosition == SuggestionPosition.Bottom + ? Alignment.bottomCenter + : Alignment.topCenter, + ), + portalFollower: ValueListenableBuilder( valueListenable: showSuggestions, builder: (BuildContext context, bool show, Widget? child) { return show && !widget.hideSuggestionList ? OptionList( suggestionListHeight: widget.suggestionListHeight, + suggestionListWidth: widget.suggestionListWidth, suggestionBuilder: list.suggestionBuilder, suggestionListDecoration: widget.suggestionListDecoration, + suggestionContainerBuilder: + widget.sugggestionContainerBuilder, data: list.data.where((element) { final ele = element['display'].toLowerCase(); final str = _selectedMention!.str diff --git a/lib/src/mentions_to_annotations_converter.dart b/lib/src/mentions_to_annotations_converter.dart new file mode 100644 index 0000000..3f6b37a --- /dev/null +++ b/lib/src/mentions_to_annotations_converter.dart @@ -0,0 +1,44 @@ +part of flutter_mentions; + +class MentionsToAnnotationsConverter { + static Map convert(List mentions) { + final data = {}; + + // Loop over all the mention items and generate a suggestions matching list + mentions.forEach((element) { + // if matchAll is set to true add a general regex patteren to match with + if (element.matchAll) { + data['${element.trigger}([A-Za-z0-9])*'] = Annotation( + style: element.style, + id: null, + display: null, + trigger: element.trigger, + disableMarkup: element.disableMarkup, + markupBuilder: element.markupBuilder, + ); + } + + element.data.forEach( + (e) => data["${element.trigger}${e['display']}"] = e['style'] != null + ? Annotation( + style: e['style'], + id: e['id'], + display: e['display'], + trigger: element.trigger, + disableMarkup: element.disableMarkup, + markupBuilder: element.markupBuilder, + ) + : Annotation( + style: element.style, + id: e['id'], + display: e['display'], + trigger: element.trigger, + disableMarkup: element.disableMarkup, + markupBuilder: element.markupBuilder, + ), + ); + }); + + return data; + } +} diff --git a/lib/src/models.dart b/lib/src/models.dart index b90ca8f..61a7a4a 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -45,7 +45,8 @@ class Mention { final bool disableMarkup; /// Build Custom suggestion widget using this builder. - final Widget Function(Map)? suggestionBuilder; + final Widget Function(Map mentionData, + Function(Map) onTap)? suggestionBuilder; /// Allows to set custom markup for the mentioned item. final String Function(String trigger, String mention, String value)? diff --git a/lib/src/option_list.dart b/lib/src/option_list.dart index 5c1439a..0726878 100644 --- a/lib/src/option_list.dart +++ b/lib/src/option_list.dart @@ -7,50 +7,59 @@ class OptionList extends StatelessWidget { required this.suggestionListHeight, this.suggestionBuilder, this.suggestionListDecoration, + this.suggestionListWidth, + this.suggestionContainerBuilder, }); - final Widget Function(Map)? suggestionBuilder; + final Widget Function(Map mentionData, + Function(Map) onTap)? suggestionBuilder; + final Widget Function(Widget child, BoxConstraints boxConstraints)? + suggestionContainerBuilder; final List> data; final Function(Map) onTap; final double suggestionListHeight; + final double? suggestionListWidth; final BoxDecoration? suggestionListDecoration; @override Widget build(BuildContext context) { + final boxConstraints = BoxConstraints( + maxHeight: suggestionListHeight, + minHeight: 0, + maxWidth: suggestionListWidth ?? double.infinity, + ); + final child = ListView.builder( + itemCount: data.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return suggestionBuilder != null + ? suggestionBuilder!(data[index], onTap) + : GestureDetector( + onTap: () => onTap(data[index]), + child: Container( + color: Colors.blue, + padding: EdgeInsets.all(20.0), + child: Text( + data[index]['display'], + style: TextStyle(fontSize: 12), + ), + ), + ); + }, + ); return data.isNotEmpty - ? Container( - decoration: - suggestionListDecoration ?? BoxDecoration(color: Colors.white), - constraints: BoxConstraints( - maxHeight: suggestionListHeight, - minHeight: 0, - ), - child: ListView.builder( - itemCount: data.length, - shrinkWrap: true, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - onTap(data[index]); - }, - child: suggestionBuilder != null - ? suggestionBuilder!(data[index]) - : Container( - color: Colors.blue, - padding: EdgeInsets.all(20.0), - child: Text( - data[index]['display'], - style: TextStyle(fontSize: 12), - ), - ), - ); - }, - ), - ) + ? suggestionContainerBuilder != null + ? suggestionContainerBuilder!(child, boxConstraints) + : Container( + decoration: suggestionListDecoration ?? + BoxDecoration(color: Colors.white), + constraints: boxConstraints, + child: child, + ) : Container(); } } diff --git a/pubspec.lock b/pubspec.lock index af9486f..83a0ec9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,51 +5,50 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.10.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.2.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -59,35 +58,55 @@ packages: dependency: "direct main" description: name: flutter_portal - url: "https://pub.dartlang.org" + sha256: ec5bd9a0aa7efeea5a95abf54ce91039c812fe6cc8e30d499eaa45ce387e482e + url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "1.1.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" + source: hosted + version: "0.12.13" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.8.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -97,58 +116,58 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "0.4.16" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=1.21.0" diff --git a/pubspec.yaml b/pubspec.yaml index 29556c3..bbcdcf0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,12 +4,12 @@ version: 2.0.1 homepage: https://github.com/fayeed/flutter_mentions environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - flutter_portal: ^0.4.0 + flutter_portal: ^1.1.3 dev_dependencies: flutter_test: