Skip to content

Commit 9c7a08d

Browse files
committed
Suggestions now work - not auto-refreshing though
1 parent 468b265 commit 9c7a08d

File tree

3 files changed

+150
-67
lines changed

3 files changed

+150
-67
lines changed

example/lib/main.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AppProfile {
3535

3636
@override
3737
String toString() {
38-
return 'Profile{$name}';
38+
return name;
3939
}
4040
}
4141

@@ -117,7 +117,7 @@ class MyHomePage extends StatelessWidget {
117117
require: true,
118118
min: 3,
119119
),
120-
FormBuilderInput.dropdown(
120+
/*FormBuilderInput.dropdown(
121121
attribute: "dropdown",
122122
require: true,
123123
label: "Dropdown",
@@ -269,7 +269,7 @@ class MyHomePage extends StatelessWidget {
269269
value: 10,
270270
),
271271
],
272-
),
272+
),*/
273273
],
274274
onChanged: () {
275275
print("Form value changed");

lib/src/chips_input.dart

Lines changed: 145 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
44

55
typedef ChipsInputSuggestions<T> = FutureOr<List<T>> Function(String query);
66
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);
89

910
class ChipsInput<T> extends StatefulWidget {
1011
const ChipsInput({
@@ -28,7 +29,8 @@ class ChipsInput<T> extends StatefulWidget {
2829
ChipsInputState<T> createState() => ChipsInputState<T>();
2930
}
3031

31-
class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient {
32+
class ChipsInputState<T> extends State<ChipsInput<T>>
33+
implements TextInputClient {
3234
static const kObjectReplacementChar = 0xFFFC;
3335

3436
Set<T> _chips = Set<T>();
@@ -38,13 +40,95 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
3840
FocusNode _focusNode;
3941
TextEditingValue _value = TextEditingValue();
4042
TextInputConnection _connection;
43+
_SuggestionsBoxController _suggestionsBoxController;
44+
final LayerLink _layerLink = LayerLink();
4145

4246
String get text => String.fromCharCodes(
43-
_value.text.codeUnits.where((ch) => ch != kObjectReplacementChar),
44-
);
47+
_value.text.codeUnits.where((ch) => ch != kObjectReplacementChar),
48+
);
4549

4650
bool get _hasInputConnection => _connection != null && _connection.attached;
4751

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+
48132
void requestKeyboard() {
49133
if (_focusNode.hasFocus) {
50134
_openInputConnection();
@@ -70,31 +154,6 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
70154
widget.onChanged(_chips.toList(growable: false));
71155
}
72156

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-
98157
void _openInputConnection() {
99158
if (!_hasInputConnection) {
100159
_connection = TextInput.attach(this, TextInputConfiguration());
@@ -113,9 +172,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
113172
@override
114173
Widget build(BuildContext context) {
115174
var chipsChildren = _chips
116-
.map<Widget>(
117-
(data) => widget.chipBuilder(context, this, data),
118-
)
175+
.map<Widget>((data) => widget.chipBuilder(context, this, data))
119176
.toList();
120177

121178
final theme = Theme.of(context);
@@ -141,33 +198,19 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
141198
),
142199
);
143200

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,
169212
),
170-
],
213+
),
171214
);
172215
}
173216

@@ -185,7 +228,9 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
185228
}
186229

187230
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;
189234
}
190235

191236
@override
@@ -194,7 +239,8 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
194239
}
195240

196241
void _updateTextInputState() {
197-
final text = String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
242+
final text =
243+
String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
198244
_value = TextEditingValue(
199245
text: text,
200246
selection: TextSelection.collapsed(offset: text.length),
@@ -207,8 +253,11 @@ class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient
207253
final localId = ++_searchId;
208254
final results = await widget.findSuggestions(value);
209255
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));
211259
}
260+
print(_suggestions);
212261
}
213262
}
214263

@@ -226,7 +275,8 @@ class _TextCaret extends StatefulWidget {
226275
_TextCursorState createState() => _TextCursorState();
227276
}
228277

229-
class _TextCursorState extends State<_TextCaret> with SingleTickerProviderStateMixin {
278+
class _TextCursorState extends State<_TextCaret>
279+
with SingleTickerProviderStateMixin {
230280
bool _displayed = false;
231281
Timer _timer;
232282

@@ -260,4 +310,36 @@ class _TextCursorState extends State<_TextCaret> with SingleTickerProviderStateM
260310
),
261311
);
262312
}
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+
}

lib/src/form_builder.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ class _FormBuilderState extends State<FormBuilder> {
624624
break;
625625
case FormBuilderInput.TYPE_CHIPS_INPUT:
626626
formControlsList.add(SizedBox(
627-
height: 200.0,
627+
// height: 200.0,
628628
child: FormField(
629629
initialValue: formControl.value ?? [],
630630
onSaved: (value) {
@@ -638,6 +638,7 @@ class _FormBuilderState extends State<FormBuilder> {
638638
},
639639
builder: (FormFieldState<dynamic> field) {
640640
return ChipsInput(
641+
// key: GlobalKey<ChipsInputState>(),
641642
decoration: InputDecoration(
642643
// prefixIcon: Icon(Icons.search),
643644
hintText: formControl.hint,

0 commit comments

Comments
 (0)