Skip to content

Commit 89726c8

Browse files
committed
Initial commit
0 parents  commit 89726c8

File tree

9 files changed

+604
-0
lines changed

9 files changed

+604
-0
lines changed

.gitignore

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.buildlog/
9+
.history
10+
.svn/
11+
12+
# IntelliJ related
13+
*.iml
14+
*.ipr
15+
*.iws
16+
.idea/
17+
18+
# The .vscode folder contains launch configuration and tasks you configure in
19+
# VS Code which you may wish to be included in version control, so this line
20+
# is commented out by default.
21+
#.vscode/
22+
23+
# Flutter/Dart/Pub related
24+
**/doc/api/
25+
.dart_tool/
26+
.flutter-plugins
27+
.flutter-plugins-dependencies
28+
.packages
29+
.pub-cache/
30+
.pub/
31+
build/
32+
33+
# Android related
34+
**/android/**/gradle-wrapper.jar
35+
**/android/.gradle
36+
**/android/captures/
37+
**/android/gradlew
38+
**/android/gradlew.bat
39+
**/android/local.properties
40+
**/android/**/GeneratedPluginRegistrant.java
41+
42+
# iOS/XCode related
43+
**/ios/**/*.mode1v3
44+
**/ios/**/*.mode2v3
45+
**/ios/**/*.moved-aside
46+
**/ios/**/*.pbxuser
47+
**/ios/**/*.perspectivev3
48+
**/ios/**/*sync/
49+
**/ios/**/.sconsign.dblite
50+
**/ios/**/.tags*
51+
**/ios/**/.vagrant/
52+
**/ios/**/DerivedData/
53+
**/ios/**/Icon?
54+
**/ios/**/Pods/
55+
**/ios/**/.symlinks/
56+
**/ios/**/profile
57+
**/ios/**/xcuserdata
58+
**/ios/.generated/
59+
**/ios/Flutter/App.framework
60+
**/ios/Flutter/Flutter.framework
61+
**/ios/Flutter/Flutter.podspec
62+
**/ios/Flutter/Generated.xcconfig
63+
**/ios/Flutter/app.flx
64+
**/ios/Flutter/app.zip
65+
**/ios/Flutter/flutter_assets/
66+
**/ios/Flutter/flutter_export_environment.sh
67+
**/ios/ServiceDefinitions.json
68+
**/ios/Runner/GeneratedPluginRegistrant.*
69+
70+
# Exceptions to above rules.
71+
!**/ios/**/default.mode1v3
72+
!**/ios/**/default.mode2v3
73+
!**/ios/**/default.pbxuser
74+
!**/ios/**/default.perspectivev3
75+
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

.metadata

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: 27321ebbad34b0a3fafe99fac037102196d655ff
8+
channel: stable
9+
10+
project_type: package

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## [0.0.1] - TODO: Add release date.
2+
3+
* TODO: Describe initial release.

LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO: Add your license here.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# chips_choice
2+
3+
A new Flutter package project.
4+
5+
## Getting Started
6+
7+
This project is a starting point for a Dart
8+
[package](https://flutter.dev/developing-packages/),
9+
a library module containing code that can be shared easily across
10+
multiple Flutter or Dart projects.
11+
12+
For help getting started with Flutter, view our
13+
[online documentation](https://flutter.dev/docs), which offers tutorials,
14+
samples, guidance on mobile development, and a full API reference.

lib/chips_choice.dart

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
library chips_choice;
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter/rendering.dart';
5+
6+
/// Easy way to provide a single or multiple choice chips.
7+
class ChipsChoice<T> extends StatelessWidget {
8+
9+
/// List of option item
10+
final List<ChipsChoiceOption<T>> options;
11+
12+
/// Choice item config
13+
final ChipsChoiceItemConfig itemConfig;
14+
15+
/// Builder for custom choice item
16+
final ChipsChoiceBuilder<T> itemBuilder;
17+
18+
/// List padding
19+
final EdgeInsetsGeometry padding;
20+
21+
final T _value;
22+
final List<T> _values;
23+
final ChipsChoiceChanged<T> _onChangedSingle;
24+
final ChipsChoiceChanged<List<T>> _onChangedMultiple;
25+
final bool _isMultiChoice;
26+
27+
ChipsChoice.single({
28+
Key key,
29+
@required T value,
30+
@required this.options,
31+
@required ChipsChoiceChanged<T> onChanged,
32+
this.itemConfig = const ChipsChoiceItemConfig(),
33+
this.itemBuilder,
34+
this.padding,
35+
}) : assert(onChanged != null),
36+
_isMultiChoice = false,
37+
_value = value,
38+
_values = null,
39+
_onChangedMultiple = null,
40+
_onChangedSingle = onChanged,
41+
super(key: key);
42+
43+
ChipsChoice.multiple({
44+
Key key,
45+
@required List<T> value,
46+
@required this.options,
47+
@required ChipsChoiceChanged<List<T>> onChanged,
48+
this.itemConfig = const ChipsChoiceItemConfig(),
49+
this.itemBuilder,
50+
this.padding,
51+
}) : assert(onChanged != null && value != null),
52+
_isMultiChoice = true,
53+
_value = null,
54+
_values = value,
55+
_onChangedSingle = null,
56+
_onChangedMultiple = onChanged,
57+
super(key: key);
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
return itemConfig.isWrapped == true
62+
? _listWrapped()
63+
: _listScrollable();
64+
}
65+
66+
Widget _listScrollable() {
67+
return SingleChildScrollView(
68+
scrollDirection: Axis.horizontal,
69+
padding: padding ?? EdgeInsets.symmetric(horizontal: 10),
70+
child: Row(
71+
children: _choiceItems,
72+
),
73+
);
74+
}
75+
76+
Widget _listWrapped() {
77+
return Padding(
78+
padding: padding ?? EdgeInsets.symmetric(
79+
vertical: 10.0,
80+
horizontal: 15.0,
81+
),
82+
child: Wrap(
83+
spacing: itemConfig.wrappedSpacing, // gap between adjacent chips
84+
runSpacing: itemConfig.wrappedRunSpacing, // gap between lines
85+
children: _choiceItems,
86+
),
87+
);
88+
}
89+
90+
List<Widget> get _choiceItems {
91+
return List<Widget>.generate(
92+
options.length,
93+
(i) => _choiceItemBuilder(i),
94+
).where((item) => item != null).toList();
95+
}
96+
97+
Widget _choiceItemBuilder(int i) {
98+
ChipsChoiceOption<T> item = options[i];
99+
bool selected = _isMultiChoice
100+
? _values.contains(item.value)
101+
: _value == item.value;
102+
return item.hidden == false
103+
? itemBuilder?.call(item, itemConfig, selected) ?? _ChipsChoiceItem(
104+
item: item,
105+
config: itemConfig,
106+
selected: selected,
107+
onSelect: _onSelect,
108+
)
109+
: null;
110+
}
111+
112+
void _onSelect(T value, bool selected) {
113+
if (_isMultiChoice) {
114+
List<T> values = List.from(_values ?? []);
115+
if (selected) {
116+
values.add(value);
117+
} else {
118+
values.remove(value);
119+
}
120+
_onChangedMultiple?.call(values);
121+
} else {
122+
_onChangedSingle?.call(value);
123+
}
124+
}
125+
}
126+
127+
class _ChipsChoiceItem<T> extends StatelessWidget {
128+
129+
final ChipsChoiceOption<T> item;
130+
final ChipsChoiceItemConfig config;
131+
final Function onSelect;
132+
final bool selected;
133+
134+
_ChipsChoiceItem({
135+
Key key,
136+
@required this.item,
137+
@required this.config,
138+
@required this.onSelect,
139+
@required this.selected,
140+
}) : super(key: key);
141+
142+
@override
143+
Widget build(BuildContext context) {
144+
ThemeData _theme = Theme.of(context);
145+
Color _selectedColor = config.selectedColor ?? _theme.primaryColor;
146+
Color _unselectedColor = config.unselectedColor ?? _theme.unselectedWidgetColor;
147+
148+
return Padding(
149+
padding: config.margin,
150+
child: FilterChip(
151+
label: Text(
152+
item.label,
153+
style: TextStyle(
154+
color: selected ? _selectedColor : _unselectedColor,
155+
),
156+
),
157+
avatar: item.avatar,
158+
shape: config.shapeBuilder?.call(selected) ?? StadiumBorder(
159+
side: BorderSide(
160+
color: selected
161+
? _selectedColor.withOpacity(.2)
162+
: _unselectedColor.withOpacity(.2),
163+
),
164+
),
165+
elevation: config.elevation,
166+
pressElevation: config.pressElevation,
167+
shadowColor: _unselectedColor,
168+
selectedShadowColor: _selectedColor,
169+
backgroundColor: Colors.transparent,
170+
selectedColor: Colors.transparent,
171+
checkmarkColor: _selectedColor,
172+
showCheckmark: config.showCheckmark == true,
173+
selected: selected,
174+
onSelected: item.disabled == false
175+
? (_selected) => onSelect(item.value, _selected)
176+
: null,
177+
),
178+
);
179+
}
180+
}
181+
182+
class ChipsChoiceOption<T> {
183+
final T value;
184+
final String label;
185+
final Widget avatar;
186+
final bool disabled;
187+
final bool hidden;
188+
189+
ChipsChoiceOption({
190+
@required this.value,
191+
@required this.label,
192+
this.avatar,
193+
this.disabled = false,
194+
this.hidden = false,
195+
}) : assert(disabled != null),
196+
assert(hidden != null);
197+
198+
static List<ChipsChoiceOption<R>> listFrom<S, R>({
199+
@required List<S> source,
200+
@required ChipsChoiceOptionProp<S, R> value,
201+
@required ChipsChoiceOptionProp<S, String> label,
202+
ChipsChoiceOptionProp<S, Widget> avatar,
203+
ChipsChoiceOptionProp<S, bool> disabled,
204+
ChipsChoiceOptionProp<S, bool> hidden,
205+
}) => source
206+
.asMap()
207+
.map((index, item) => MapEntry(index, ChipsChoiceOption<R>(
208+
value: value?.call(index, item),
209+
label: label?.call(index, item),
210+
avatar: avatar?.call(index, item),
211+
disabled: disabled?.call(index, item) ?? false,
212+
hidden: hidden?.call(index, item) ?? false,
213+
)))
214+
.values
215+
.toList()
216+
.cast<ChipsChoiceOption<R>>();
217+
}
218+
219+
class ChipsChoiceItemConfig {
220+
final bool isWrapped;
221+
final bool showCheckmark;
222+
final Color selectedColor;
223+
final Color unselectedColor;
224+
final EdgeInsetsGeometry margin;
225+
final double wrappedSpacing;
226+
final double wrappedRunSpacing;
227+
final double elevation;
228+
final double pressElevation;
229+
final ChipsChoiceShapeBuilder shapeBuilder;
230+
231+
const ChipsChoiceItemConfig({
232+
this.isWrapped = false,
233+
this.showCheckmark = true,
234+
this.selectedColor,
235+
this.unselectedColor,
236+
this.margin = const EdgeInsets.all(5),
237+
this.wrappedSpacing = 10.0,
238+
this.wrappedRunSpacing = 0,
239+
this.elevation = 0,
240+
this.pressElevation = 0,
241+
this.shapeBuilder,
242+
});
243+
}
244+
245+
typedef ShapeBorder ChipsChoiceShapeBuilder(bool selected);
246+
typedef R ChipsChoiceOptionProp<T, R>(int index, T item);
247+
typedef void ChipsChoiceChanged<T>(T values);
248+
typedef Widget ChipsChoiceBuilder<T>(
249+
ChipsChoiceOption<T> item,
250+
ChipsChoiceItemConfig config,
251+
bool selected
252+
);

0 commit comments

Comments
 (0)