@@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
4
4
5
5
typedef ChipsInputSuggestions <T > = FutureOr <List <T >> Function (String query);
6
6
typedef ChipSelected <T > = void Function (T data, bool selected);
7
- typedef ChipsBuilder <T > = Widget Function (BuildContext context, ChipsInputState <T > state, T data);
7
+ typedef ChipsBuilder <T > = Widget Function (
8
+ BuildContext context, ChipsInputState <T > state, T data);
8
9
9
10
class ChipsInput <T > extends StatefulWidget {
10
11
const ChipsInput ({
@@ -28,7 +29,8 @@ class ChipsInput<T> extends StatefulWidget {
28
29
ChipsInputState <T > createState () => ChipsInputState <T >();
29
30
}
30
31
31
- class ChipsInputState <T > extends State <ChipsInput <T >> implements TextInputClient {
32
+ class ChipsInputState <T > extends State <ChipsInput <T >>
33
+ implements TextInputClient {
32
34
static const kObjectReplacementChar = 0xFFFC ;
33
35
34
36
Set <T > _chips = Set <T >();
@@ -38,13 +40,95 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
38
40
FocusNode _focusNode;
39
41
TextEditingValue _value = TextEditingValue ();
40
42
TextInputConnection _connection;
43
+ _SuggestionsBoxController _suggestionsBoxController;
44
+ final LayerLink _layerLink = LayerLink ();
41
45
42
46
String get text => String .fromCharCodes (
43
- _value.text.codeUnits.where ((ch) => ch != kObjectReplacementChar),
44
- );
47
+ _value.text.codeUnits.where ((ch) => ch != kObjectReplacementChar),
48
+ );
45
49
46
50
bool get _hasInputConnection => _connection != null && _connection.attached;
47
51
52
+ @override
53
+ void initState () {
54
+ super .initState ();
55
+ this ._focusNode = FocusNode ();
56
+ this ._suggestionsBoxController = _SuggestionsBoxController (context);
57
+
58
+ (() async {
59
+ await this ._initOverlayEntry ();
60
+ this ._focusNode.addListener (_onFocusChanged);
61
+ // in case we already missed the focus event
62
+ if (this ._focusNode.hasFocus) {
63
+ this ._suggestionsBoxController.open ();
64
+ }
65
+ })();
66
+ }
67
+
68
+ void _onFocusChanged () {
69
+ if (_focusNode.hasFocus) {
70
+ _openInputConnection ();
71
+ this ._suggestionsBoxController.open ();
72
+ } else {
73
+ _closeInputConnectionIfNeeded ();
74
+ this ._suggestionsBoxController.close ();
75
+ }
76
+ setState (() {
77
+ /*rebuild so that _TextCursor is hidden.*/
78
+ });
79
+ }
80
+
81
+ Future <void > _initOverlayEntry () async {
82
+ RenderBox renderBox = context.findRenderObject ();
83
+
84
+ while (renderBox == null ) {
85
+ await Future .delayed (Duration (milliseconds: 10 ));
86
+
87
+ renderBox = context.findRenderObject ();
88
+ }
89
+
90
+ while (! renderBox.hasSize) {
91
+ await Future .delayed (Duration (milliseconds: 10 ));
92
+ }
93
+
94
+ var size = renderBox.size;
95
+ var offset = renderBox.localToGlobal (Offset .zero);
96
+
97
+ this ._suggestionsBoxController._overlayEntry = OverlayEntry (
98
+ builder: (context) {
99
+ return Positioned (
100
+ width: size.width,
101
+ left: offset.dx,
102
+ top: offset.dy + size.height + 5.0 ,
103
+ /*child: CompositedTransformFollower(
104
+ link: this._layerLink,
105
+ showWhenUnlinked: false,
106
+ offset: Offset(0.0, size.height + 5.0),
107
+ */
108
+ child: Material (
109
+ elevation: 4.0 ,
110
+ child: ListView .builder (
111
+ shrinkWrap: true ,
112
+ itemCount: _suggestions? .length ?? 0 ,
113
+ itemBuilder: (BuildContext context, int index) {
114
+ return widget.suggestionBuilder (
115
+ context, this , _suggestions[index]);
116
+ },
117
+ ),
118
+ ),
119
+ //),
120
+ );
121
+ },
122
+ );
123
+ }
124
+
125
+ @override
126
+ void dispose () {
127
+ _focusNode? .dispose ();
128
+ _closeInputConnectionIfNeeded ();
129
+ super .dispose ();
130
+ }
131
+
48
132
void requestKeyboard () {
49
133
if (_focusNode.hasFocus) {
50
134
_openInputConnection ();
@@ -70,31 +154,6 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
70
154
widget.onChanged (_chips.toList (growable: false ));
71
155
}
72
156
73
- @override
74
- void initState () {
75
- super .initState ();
76
- _focusNode = FocusNode ();
77
- _focusNode.addListener (_onFocusChanged);
78
- }
79
-
80
- void _onFocusChanged () {
81
- if (_focusNode.hasFocus) {
82
- _openInputConnection ();
83
- } else {
84
- _closeInputConnectionIfNeeded ();
85
- }
86
- setState (() {
87
- // rebuild so that _TextCursor is hidden.
88
- });
89
- }
90
-
91
- @override
92
- void dispose () {
93
- _focusNode? .dispose ();
94
- _closeInputConnectionIfNeeded ();
95
- super .dispose ();
96
- }
97
-
98
157
void _openInputConnection () {
99
158
if (! _hasInputConnection) {
100
159
_connection = TextInput .attach (this , TextInputConfiguration ());
@@ -113,9 +172,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
113
172
@override
114
173
Widget build (BuildContext context) {
115
174
var chipsChildren = _chips
116
- .map <Widget >(
117
- (data) => widget.chipBuilder (context, this , data),
118
- )
175
+ .map <Widget >((data) => widget.chipBuilder (context, this , data))
119
176
.toList ();
120
177
121
178
final theme = Theme .of (context);
@@ -141,33 +198,19 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
141
198
),
142
199
);
143
200
144
- return Column (
145
- crossAxisAlignment: CrossAxisAlignment .stretch,
146
- //mainAxisSize: MainAxisSize.min,
147
- children: < Widget > [
148
- GestureDetector (
149
- behavior: HitTestBehavior .opaque,
150
- onTap: requestKeyboard,
151
- child: InputDecorator (
152
- decoration: widget.decoration,
153
- isFocused: _focusNode.hasFocus,
154
- isEmpty: _value.text.length == 0 ,
155
- child: Wrap (
156
- children: chipsChildren,
157
- spacing: 4.0 ,
158
- runSpacing: 4.0 ,
159
- ),
160
- ),
161
- ),
162
- Expanded (
163
- child: ListView .builder (
164
- itemCount: _suggestions? .length ?? 0 ,
165
- itemBuilder: (BuildContext context, int index) {
166
- return widget.suggestionBuilder (context, this , _suggestions[index]);
167
- },
168
- ),
201
+ return GestureDetector (
202
+ behavior: HitTestBehavior .opaque,
203
+ onTap: requestKeyboard,
204
+ child: InputDecorator (
205
+ decoration: widget.decoration,
206
+ isFocused: _focusNode.hasFocus,
207
+ isEmpty: _value.text.length == 0 ,
208
+ child: Wrap (
209
+ children: chipsChildren,
210
+ spacing: 4.0 ,
211
+ runSpacing: 4.0 ,
169
212
),
170
- ] ,
213
+ ) ,
171
214
);
172
215
}
173
216
@@ -185,7 +228,9 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
185
228
}
186
229
187
230
int _countReplacements (TextEditingValue value) {
188
- return value.text.codeUnits.where ((ch) => ch == kObjectReplacementChar).length;
231
+ return value.text.codeUnits
232
+ .where ((ch) => ch == kObjectReplacementChar)
233
+ .length;
189
234
}
190
235
191
236
@override
@@ -194,7 +239,8 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
194
239
}
195
240
196
241
void _updateTextInputState () {
197
- final text = String .fromCharCodes (_chips.map ((_) => kObjectReplacementChar));
242
+ final text =
243
+ String .fromCharCodes (_chips.map ((_) => kObjectReplacementChar));
198
244
_value = TextEditingValue (
199
245
text: text,
200
246
selection: TextSelection .collapsed (offset: text.length),
@@ -207,8 +253,11 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
207
253
final localId = ++ _searchId;
208
254
final results = await widget.findSuggestions (value);
209
255
if (_searchId == localId && mounted) {
210
- setState (() => _suggestions = results.where ((profile) => ! _chips.contains (profile)).toList (growable: false ));
256
+ setState (() => _suggestions = results
257
+ .where ((profile) => ! _chips.contains (profile))
258
+ .toList (growable: false ));
211
259
}
260
+ print (_suggestions);
212
261
}
213
262
}
214
263
@@ -226,7 +275,8 @@ class _TextCaret extends StatefulWidget {
226
275
_TextCursorState createState () => _TextCursorState ();
227
276
}
228
277
229
- class _TextCursorState extends State <_TextCaret > with SingleTickerProviderStateMixin {
278
+ class _TextCursorState extends State <_TextCaret >
279
+ with SingleTickerProviderStateMixin {
230
280
bool _displayed = false ;
231
281
Timer _timer;
232
282
@@ -260,4 +310,36 @@ class _TextCursorState extends State<_TextCaret> with SingleTickerProviderStateM
260
310
),
261
311
);
262
312
}
263
- }
313
+ }
314
+
315
+ class _SuggestionsBoxController {
316
+ final BuildContext context;
317
+
318
+ OverlayEntry _overlayEntry;
319
+
320
+ bool _isOpened = false ;
321
+
322
+ _SuggestionsBoxController (this .context);
323
+
324
+ open () {
325
+ if (this ._isOpened) return ;
326
+ assert (this ._overlayEntry != null );
327
+ Overlay .of (context).insert (this ._overlayEntry);
328
+ this ._isOpened = true ;
329
+ }
330
+
331
+ close () {
332
+ if (! this ._isOpened) return ;
333
+ assert (this ._overlayEntry != null );
334
+ this ._overlayEntry.remove ();
335
+ this ._isOpened = false ;
336
+ }
337
+
338
+ toggle () {
339
+ if (this ._isOpened) {
340
+ this .close ();
341
+ } else {
342
+ this .open ();
343
+ }
344
+ }
345
+ }
0 commit comments