1
+ import 'package:example/src/chat_message_list.dart' ;
2
+ import 'package:example/src/chat_message_text_field.dart' ;
3
+ import 'package:example/src/data.dart' ;
4
+ import 'package:example/src/options/options.dart' ;
1
5
import 'package:flutter/material.dart' ;
6
+ import 'package:google_fonts/google_fonts.dart' ;
2
7
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart' ;
3
8
4
9
void main () => runApp (const MyApp ());
@@ -13,173 +18,113 @@ class MyApp extends StatelessWidget {
13
18
child: MaterialApp (
14
19
title: 'Flutter Demo' ,
15
20
theme: ThemeData (
16
- primarySwatch: Colors .teal,
21
+ platform: TargetPlatform .iOS,
22
+ textTheme: GoogleFonts .robotoMonoTextTheme (
23
+ Theme .of (context).textTheme,
24
+ ),
17
25
),
18
26
home: const MyHomePage (),
19
27
),
20
28
);
21
29
}
22
30
}
23
31
24
- class MyHomePage extends StatelessWidget {
32
+ class MyHomePage extends StatefulWidget {
25
33
const MyHomePage ({Key ? key}) : super (key: key);
26
34
27
- final mentionData = const [
28
- 'Sahil' ,
29
- 'Avni' ,
30
- 'Trapti' ,
31
- 'Gaurav' ,
32
- 'Prateek' ,
33
- 'Amit' ,
34
- 'Ayush' ,
35
- 'Shubham' ,
36
- ];
35
+ @override
36
+ State <MyHomePage > createState () => _MyHomePageState ();
37
+ }
37
38
38
- final hashtagData = const [
39
- 'love' ,
40
- 'instagood' ,
41
- 'photooftheday' ,
42
- 'fashion' ,
43
- 'beautiful' ,
44
- 'happy' ,
45
- 'cute' ,
46
- 'tbt' ,
47
- ];
39
+ class _MyHomePageState extends State <MyHomePage > {
40
+ final messages = [...sampleGroupConversation];
48
41
49
42
@override
50
43
Widget build (BuildContext context) {
51
44
return Scaffold (
45
+ backgroundColor: Colors .white,
52
46
appBar: AppBar (
53
- title: const Text ('Multi Trigger Autocomplete' ),
47
+ toolbarHeight: 60 ,
48
+ backgroundColor: const Color (0xFF5B61B9 ),
49
+ title: const Text (
50
+ 'Multi Trigger Autocomplete' ,
51
+ style: TextStyle (
52
+ fontSize: 16 ,
53
+ fontWeight: FontWeight .bold,
54
+ ),
55
+ ),
54
56
centerTitle: true ,
55
- ),
56
- body: Padding (
57
- padding: const EdgeInsets .all (8.0 ),
58
- child: MultiTriggerAutocomplete (
59
- debounceDuration: Duration .zero,
60
- autocompleteTriggers: [
61
- AutocompleteTrigger (
62
- trigger: '@' ,
63
- optionsViewBuilder: (_, query, controller, closeOptions) {
64
- return OptionsView (
65
- data: mentionData,
66
- controller: controller,
67
- autocompleteQuery: query,
68
- closeOptions: closeOptions,
69
- );
70
- },
71
- ),
72
- AutocompleteTrigger (
73
- trigger: '#' ,
74
- optionsViewBuilder: (_, query, controller, closeOptions) {
75
- return OptionsView (
76
- data: hashtagData,
77
- controller: controller,
78
- autocompleteQuery: query,
79
- closeOptions: closeOptions,
80
- );
81
- },
82
- ),
83
- ],
84
- fieldViewBuilder: (context, controller, focusNode) {
85
- return TextField (
86
- controller: controller,
87
- focusNode: focusNode,
88
- decoration: const InputDecoration (
89
- border: OutlineInputBorder (),
90
- labelText: 'Write something...' ,
91
- ),
92
- );
93
- },
57
+ shape: const RoundedRectangleBorder (
58
+ borderRadius: BorderRadius .only (
59
+ bottomLeft: Radius .circular (24 ),
60
+ bottomRight: Radius .circular (24 ),
61
+ ),
94
62
),
95
63
),
96
- );
97
- }
98
- }
99
-
100
- class OptionsView <T extends Object > extends StatelessWidget {
101
- const OptionsView ({
102
- super .key,
103
- required this .data,
104
- required this .autocompleteQuery,
105
- required this .controller,
106
- required this .closeOptions,
107
- });
108
-
109
- final Iterable <T > data;
110
- final AutocompleteQuery autocompleteQuery;
111
- final TextEditingController controller;
112
- final VoidCallback closeOptions;
113
-
114
- @override
115
- Widget build (BuildContext context) {
116
- final options = data.where ((it) {
117
- final normalizedOption = it.toString ().toLowerCase ();
118
- final normalizedQuery = autocompleteQuery.query.toLowerCase ();
119
- return normalizedOption.contains (normalizedQuery);
120
- });
121
-
122
- if (options.isEmpty) return const SizedBox .shrink ();
123
-
124
- return Card (
125
- margin: const EdgeInsets .all (8 ),
126
- elevation: 2 ,
127
- // color: _streamChatTheme.colorTheme.barsBg,
128
- shape: RoundedRectangleBorder (
129
- borderRadius: BorderRadius .circular (8 ),
130
- ),
131
- clipBehavior: Clip .hardEdge,
132
- child: Column (
133
- mainAxisSize: MainAxisSize .min,
64
+ body: Column (
134
65
children: [
135
- const ListTile (
136
- dense: true ,
137
- horizontalTitleGap: 0 ,
138
- title: Text ('Matching Options...' ),
139
- ),
140
- const Divider (height: 0 ),
141
- LimitedBox (
142
- maxHeight: MediaQuery .of (context).size.height * 0.5 ,
143
- child: ListView .builder (
144
- padding: EdgeInsets .zero,
145
- shrinkWrap: true ,
146
- itemCount: options.length,
147
- itemBuilder: (context, i) {
148
- final option = options.elementAt (i);
149
- return ListTile (
150
- dense: true ,
151
- leading: CircleAvatar (
152
- radius: 16 ,
153
- child: Text (option.toString ()[0 ]),
154
- ),
155
- title: Text (option.toString ()),
156
- onTap: () {
157
- final text = controller.text;
158
- final querySelection = autocompleteQuery.selection;
159
-
160
- final queryEndsWithSpace =
161
- text.substring (querySelection.end).startsWith (' ' );
162
-
163
- final newText = text.substring (0 , querySelection.start) +
164
- option.toString () +
165
- (queryEndsWithSpace ? '' : ' ' ) +
166
- text.substring (querySelection.end);
167
-
168
- final newCursorPosition =
169
- querySelection.start + option.toString ().length + 1 ;
170
-
171
- controller.value = TextEditingValue (
172
- text: newText,
173
- selection: TextSelection .collapsed (
174
- offset: newCursorPosition,
175
- ),
176
- );
177
-
178
- closeOptions ();
66
+ Expanded (child: ChatMessageList (messages: messages)),
67
+ MultiTriggerAutocomplete (
68
+ optionsAlignment: OptionsAlignment .above,
69
+ autocompleteTriggers: [
70
+ AutocompleteTrigger (
71
+ trigger: '@' ,
72
+ optionsViewBuilder: (context, autocompleteQuery, controller) {
73
+ return MentionAutocompleteOptions (
74
+ query: autocompleteQuery.query,
75
+ onMentionUserTap: (user) {
76
+ final autocomplete = MultiTriggerAutocomplete .of (context);
77
+ return autocomplete.acceptAutocompleteOption (user.id);
78
+ },
79
+ );
80
+ },
81
+ ),
82
+ AutocompleteTrigger (
83
+ trigger: '#' ,
84
+ optionsViewBuilder: (context, autocompleteQuery, controller) {
85
+ return HashtagAutocompleteOptions (
86
+ query: autocompleteQuery.query,
87
+ onHashtagTap: (hashtag) {
88
+ final autocomplete = MultiTriggerAutocomplete .of (context);
89
+ return autocomplete
90
+ .acceptAutocompleteOption (hashtag.name);
91
+ },
92
+ );
93
+ },
94
+ ),
95
+ AutocompleteTrigger (
96
+ trigger: ':' ,
97
+ optionsViewBuilder: (context, autocompleteQuery, controller) {
98
+ return EmojiAutocompleteOptions (
99
+ query: autocompleteQuery.query,
100
+ onEmojiTap: (emoji) {
101
+ final autocomplete = MultiTriggerAutocomplete .of (context);
102
+ return autocomplete.acceptAutocompleteOption (
103
+ emoji.char,
104
+ // Passing false as we don't want the trigger [:] to
105
+ // get prefixed to the option in case of emoji.
106
+ keepTrigger: false ,
107
+ );
108
+ },
109
+ );
110
+ },
111
+ ),
112
+ ],
113
+ fieldViewBuilder: (context, controller, focusNode) {
114
+ return Padding (
115
+ padding: const EdgeInsets .all (8.0 ),
116
+ child: ChatMessageTextField (
117
+ focusNode: focusNode,
118
+ controller: controller,
119
+ onSend: (message) {
120
+ controller.clear ();
121
+ setState (() {
122
+ messages.add (message);
123
+ });
179
124
},
180
- );
181
- },
182
- ) ,
125
+ ),
126
+ );
127
+ } ,
183
128
),
184
129
],
185
130
),
0 commit comments