Skip to content

Commit d68e087

Browse files
committed
Improve performance, provide more config options, and update example
1 parent 7361df0 commit d68e087

File tree

11 files changed

+369
-102
lines changed

11 files changed

+369
-102
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [1.1.0] - 2020-03-07
2+
3+
* Improve performance, since `Theme.of(context)` in default choice widget is moved outside, so this only fired once
4+
* `ChipsChoiceBuilder` parameter `select` function now only need one parameter `bool selected`
5+
* Add `meta` to `ChipsChoiceOption`, this useful with custom choice widget
6+
* Add more option to `ChipsChoiceItemConfig`
7+
* Add more usage example
8+
19
## [1.0.0] - 2020-01-24
210

311
* Initial release

LICENSE

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
1-
TODO: Add your license here.
1+
Copyright (c) 2019 Irfan Vigma Taufik
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ Lite version of [smart_select](https://pub.dev/packages/smart_select) package, z
1717

1818
* Select single or multiple choice
1919
* Display in scrollable or wrapped List
20-
* Customizable choice input
2120
* Build choice option from any List
21+
* Customizable choice widget
2222

2323
## Usage
2424

@@ -81,10 +81,10 @@ ChipsChoice<String>.multiple(
8181
ChipsChoice<T>.single/multiple(
8282
...,
8383
...,
84-
options: <ChipsChoiceOption<int>>[
85-
ChipsChoiceOption<int>(value: 1, label: 'Entertainment'),
86-
ChipsChoiceOption<int>(value: 2, label: 'Education'),
87-
ChipsChoiceOption<int>(value: 3, label: 'Fashion'),
84+
options: <ChipsChoiceOption<T>>[
85+
ChipsChoiceOption<T>(value: 1, label: 'Entertainment'),
86+
ChipsChoiceOption<T>(value: 2, label: 'Education'),
87+
ChipsChoiceOption<T>(value: 3, label: 'Fashion'),
8888
],
8989
);
9090
```

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
In most cases you can leave this as-is, but you if you want to provide
66
additional functionality it is fine to subclass or reimplement
77
FlutterApplication and put your custom class here. -->
8+
<uses-permission android:name="android.permission.INTERNET"/>
89
<application
910
android:name="io.flutter.app.FlutterApplication"
1011
android:label="ChipsChoice"

example/art/ChipsChoice.apk

359 KB
Binary file not shown.

example/art/screencast.gif

2.59 MB
Loading

example/lib/main.dart

Lines changed: 190 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:chips_choice/chips_choice.dart';
3+
import 'package:async/async.dart';
4+
import 'package:dio/dio.dart';
35

46
void main() => runApp(MyApp());
57

@@ -34,6 +36,22 @@ class _MyHomePageState extends State<MyHomePage> {
3436
'Science',
3537
];
3638

39+
String user;
40+
final usersMemoizer = AsyncMemoizer<List<ChipsChoiceOption<String>>>();
41+
42+
Future<List<ChipsChoiceOption<String>>> getUsers() async {
43+
String url = "https://randomuser.me/api/?inc=gender,name,nat,picture,email&results=25";
44+
Response res = await Dio().get(url);
45+
return ChipsChoiceOption.listFrom<String, dynamic>(
46+
source: res.data['results'],
47+
value: (index, item) => item['email'],
48+
label: (index, item) => item['name']['first'] + ' ' + item['name']['last'],
49+
avatar: (index, item) => CircleAvatar(
50+
backgroundImage: NetworkImage(item['picture']['thumbnail']),
51+
),
52+
)..insert(0, ChipsChoiceOption<String>(value: 'all', label: 'All'));
53+
}
54+
3755
@override
3856
Widget build(BuildContext context) {
3957
return Scaffold(
@@ -100,7 +118,7 @@ class _MyHomePageState extends State<MyHomePage> {
100118
),
101119
),
102120
Content(
103-
title: 'Disabled Choice item',
121+
title: 'Disabled Choice Item',
104122
child: ChipsChoice<int>.single(
105123
value: tag,
106124
options: ChipsChoiceOption.listFrom<int, String>(
@@ -114,7 +132,7 @@ class _MyHomePageState extends State<MyHomePage> {
114132
),
115133
),
116134
Content(
117-
title: 'Hidden Choice item',
135+
title: 'Hidden Choice Item',
118136
child: ChipsChoice<String>.multiple(
119137
value: tags,
120138
options: ChipsChoiceOption.listFrom<String, String>(
@@ -128,19 +146,146 @@ class _MyHomePageState extends State<MyHomePage> {
128146
),
129147
),
130148
Content(
131-
title: 'Custom Choice item',
149+
title: 'Append an Item to Options',
150+
child: ChipsChoice<int>.single(
151+
value: tag,
152+
options: ChipsChoiceOption.listFrom<int, String>(
153+
source: options,
154+
value: (i, v) => i,
155+
label: (i, v) => v,
156+
)..insert(0, ChipsChoiceOption<int>(value: -1, label: 'All')),
157+
onChanged: (val) => setState(() => tag = val),
158+
),
159+
),
160+
Content(
161+
title: 'Selected without Checkmark and Brightness Dark',
162+
child: ChipsChoice<int>.single(
163+
value: tag,
164+
onChanged: (val) => setState(() => tag = val),
165+
options: ChipsChoiceOption.listFrom<int, String>(
166+
source: options,
167+
value: (i, v) => i,
168+
label: (i, v) => v,
169+
)..insert(0, ChipsChoiceOption<int>(value: -1, label: 'All')),
170+
itemConfig: const ChipsChoiceItemConfig(
171+
showCheckmark: false,
172+
selectedBrightness: Brightness.dark,
173+
// unselectedBrightness: Brightness.dark,
174+
),
175+
),
176+
),
177+
Content(
178+
title: 'Async Options and Brightness Dark',
179+
child: FutureBuilder<List<ChipsChoiceOption<String>>>(
180+
initialData: [],
181+
future: usersMemoizer.runOnce(getUsers),
182+
builder: (context, snapshot) {
183+
if (snapshot.connectionState == ConnectionState.waiting) {
184+
return Padding(
185+
padding: const EdgeInsets.all(20),
186+
child: Center(
187+
child: SizedBox(
188+
width: 20,
189+
height: 20,
190+
child: CircularProgressIndicator(
191+
strokeWidth: 2,
192+
)
193+
),
194+
),
195+
);
196+
} else {
197+
if (!snapshot.hasError) {
198+
return ChipsChoice<String>.single(
199+
value: user,
200+
options: snapshot.data,
201+
onChanged: (val) => setState(() => user = val),
202+
itemConfig: ChipsChoiceItemConfig(
203+
selectedColor: Colors.green,
204+
unselectedColor: Colors.blueGrey,
205+
selectedBrightness: Brightness.dark,
206+
unselectedBrightness: Brightness.dark,
207+
showCheckmark: false,
208+
),
209+
);
210+
} else {
211+
return Container(
212+
padding: const EdgeInsets.all(25),
213+
child: Text(
214+
snapshot.error.toString(),
215+
style: const TextStyle(color: Colors.red),
216+
),
217+
);
218+
}
219+
}
220+
},
221+
),
222+
),
223+
Content(
224+
title: 'Works with FormField and Validator',
225+
child: FormField<List<String>>(
226+
autovalidate: true,
227+
initialValue: tags,
228+
validator: (value) {
229+
if (value.isEmpty) {
230+
return 'Please select some categories';
231+
}
232+
if (value.length > 5) {
233+
return "Can't select more than 5 categories";
234+
}
235+
return null;
236+
},
237+
builder: (state) {
238+
return Column(
239+
children: <Widget>[
240+
Container(
241+
alignment: Alignment.centerLeft,
242+
child: ChipsChoice<String>.multiple(
243+
value: state.value,
244+
options: ChipsChoiceOption.listFrom<String, String>(
245+
source: options,
246+
value: (i, v) => v,
247+
label: (i, v) => v,
248+
),
249+
onChanged: (val) => state.didChange(val),
250+
itemConfig: ChipsChoiceItemConfig(
251+
selectedColor: Colors.indigo,
252+
selectedBrightness: Brightness.dark,
253+
unselectedColor: Colors.indigo,
254+
unselectedBorderOpacity: .3,
255+
),
256+
isWrapped: true,
257+
),
258+
),
259+
Container(
260+
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
261+
alignment: Alignment.centerLeft,
262+
child: Text(
263+
state.errorText ?? state.value.length.toString() + '/5 selected',
264+
style: TextStyle(
265+
color: state.hasError
266+
? Colors.redAccent
267+
: Colors.green
268+
),
269+
)
270+
)
271+
],
272+
);
273+
},
274+
),
275+
),
276+
Content(
277+
title: 'Custom Choice Widget',
132278
child: ChipsChoice<String>.multiple(
133279
value: tags,
134280
options: ChipsChoiceOption.listFrom<String, String>(
135281
source: options,
136282
value: (i, v) => v,
137283
label: (i, v) => v,
138284
),
139-
itemBuilder: (item, selected, onSelect) {
140-
return CustomChip(item.value, item.label, selected, onSelect);
285+
itemBuilder: (item, selected, select) {
286+
return CustomChip(item.label, selected, select);
141287
},
142288
onChanged: (val) => setState(() => tags = val),
143-
isWrapped: true,
144289
),
145290
),
146291
],
@@ -149,15 +294,13 @@ class _MyHomePageState extends State<MyHomePage> {
149294
}
150295
}
151296

152-
class CustomChip<T> extends StatelessWidget {
297+
class CustomChip extends StatelessWidget {
153298

154-
final T value;
155299
final String label;
156300
final bool selected;
157-
final Function(T value, bool selected) onSelect;
301+
final Function(bool selected) onSelect;
158302

159303
CustomChip(
160-
this.value,
161304
this.label,
162305
this.selected,
163306
this.onSelect,
@@ -167,28 +310,50 @@ class CustomChip<T> extends StatelessWidget {
167310
@override
168311
Widget build(BuildContext context) {
169312
return AnimatedContainer(
170-
margin: EdgeInsets.symmetric(vertical: 5),
313+
height: 100,
314+
width: 70,
315+
margin: EdgeInsets.symmetric(
316+
vertical: 15,
317+
horizontal: 5,
318+
),
171319
duration: Duration(milliseconds: 300),
172320
decoration: BoxDecoration(
173321
color: selected ? Colors.green : Colors.transparent,
322+
borderRadius: BorderRadius.circular(15),
174323
border: Border.all(
175324
color: selected ? Colors.green : Colors.grey,
176325
width: 1,
177326
),
178327
),
179328
child: InkWell(
180-
onTap: () => onSelect(value, !selected),
181-
child: Padding(
182-
padding: EdgeInsets.symmetric(
183-
vertical: 7,
184-
horizontal: 9,
185-
),
186-
child: Text(
187-
label,
188-
style: TextStyle(
189-
color: selected ? Colors.white : Colors.black45,
329+
onTap: () => onSelect(!selected),
330+
child: Stack(
331+
alignment: Alignment.center,
332+
children: <Widget>[
333+
Visibility(
334+
visible: selected,
335+
child: Icon(
336+
Icons.check_circle_outline,
337+
color: Colors.white,
338+
size: 32,
339+
)
190340
),
191-
),
341+
Positioned(
342+
left: 9,
343+
right: 9,
344+
bottom: 7,
345+
child: Container(
346+
child: Text(
347+
label,
348+
maxLines: 1,
349+
overflow: TextOverflow.ellipsis,
350+
style: TextStyle(
351+
color: selected ? Colors.white : Colors.black45,
352+
),
353+
),
354+
),
355+
),
356+
],
192357
),
193358
),
194359
);
@@ -244,7 +409,7 @@ void _about(BuildContext context) {
244409
ListTile(
245410
title: Text(
246411
'chips_choice',
247-
style: Theme.of(context).textTheme.headline.merge(TextStyle(color: Colors.black87)),
412+
style: Theme.of(context).textTheme.headline.copyWith(color: Colors.black87),
248413
),
249414
subtitle: Text('by davigmacode'),
250415
trailing: IconButton(
@@ -261,13 +426,13 @@ void _about(BuildContext context) {
261426
children: <Widget>[
262427
Text(
263428
'Easy way to provide a single or multiple choice chips.',
264-
style: Theme.of(context).textTheme.body1.merge(TextStyle(color: Colors.black54)),
429+
style: Theme.of(context).textTheme.body1.copyWith(color: Colors.black54),
265430
),
266431
Container(height: 15),
267432
],
268433
),
269434
),
270-
)
435+
),
271436
],
272437
),
273438
),

0 commit comments

Comments
 (0)