Skip to content

Commit 84e5159

Browse files
feat: add custom checkbox and radio button widgets (#18)
* feat: add custom checkbox and radio button widgets * fix(checkbox): Update indeterminate checkbox colors --------- Co-authored-by: Mohamed Ebrahim <111383089+mohamedebrahem13@users.noreply.github.com>
1 parent a8b2e66 commit 84e5159

5 files changed

Lines changed: 260 additions & 0 deletions

File tree

assets/svg/check.svg

Lines changed: 7 additions & 0 deletions
Loading

assets/svg/indeterminate.svg

Lines changed: 3 additions & 0 deletions
Loading

lib/core/design_system/constants/assets.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class Assets {
99
static const String alertDiamond = '$iconsPath/alert-diamond.svg';
1010
static const String checkmarkBadge = '$iconsPath/checkmark-badge-02.svg';
1111
static const String cancelCircle = '$iconsPath/cancel-circle.svg';
12+
static const String check = '$iconsPath/check.svg';
13+
static const String indeterminate = '$iconsPath/indeterminate.svg';
1214
// app bar icons
1315
static const String bell = '$iconsPath/bell.svg';
1416
static const String location = '$iconsPath/location.svg';
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_svg/flutter_svg.dart';
3+
import 'package:sellio_mobile/core/design_system/constants/assets.dart';
4+
import '../../themes/sellio_colors.dart';
5+
6+
enum CheckboxState {
7+
unchecked,
8+
checked,
9+
indeterminate,
10+
disabledChecked,
11+
}
12+
13+
class CustomCheckbox extends StatefulWidget {
14+
final CheckboxState state;
15+
final ValueChanged<CheckboxState>? onChanged;
16+
final double size;
17+
18+
const CustomCheckbox({
19+
Key? key,
20+
required this.state,
21+
this.onChanged,
22+
this.size = 20.0,
23+
}) : super(key: key);
24+
25+
@override
26+
State<CustomCheckbox> createState() => _CustomCheckboxState();
27+
}
28+
29+
class _CustomCheckboxState extends State<CustomCheckbox> {
30+
@override
31+
Widget build(BuildContext context) {
32+
final bool isEnabled = widget.state != CheckboxState.disabledChecked;
33+
final colorScheme = SellioColors.light;
34+
35+
return GestureDetector(
36+
onTap: isEnabled && widget.onChanged != null
37+
? () {
38+
CheckboxState newState;
39+
switch (widget.state) {
40+
case CheckboxState.unchecked:
41+
newState = CheckboxState.checked;
42+
break;
43+
case CheckboxState.checked:
44+
newState = CheckboxState.unchecked;
45+
break;
46+
case CheckboxState.indeterminate:
47+
newState = CheckboxState.checked;
48+
break;
49+
case CheckboxState.disabledChecked:
50+
return;
51+
}
52+
widget.onChanged!(newState);
53+
}
54+
: null,
55+
child: Container(
56+
width: widget.size,
57+
height: widget.size,
58+
decoration: BoxDecoration(
59+
color: _getBackgroundColor(colorScheme),
60+
border: Border.all(
61+
color: _getBorderColor(colorScheme),
62+
width: 1.5,
63+
),
64+
borderRadius: BorderRadius.circular(4.0),
65+
boxShadow: widget.state == CheckboxState.checked
66+
? [
67+
BoxShadow(
68+
color: colorScheme.primary.withOpacity(0.1),
69+
blurRadius: 4,
70+
offset: const Offset(0, 2),
71+
)
72+
]
73+
: null,
74+
),
75+
child: _buildIcon(colorScheme),
76+
),
77+
);
78+
}
79+
80+
Color _getBackgroundColor(SellioColorScheme colorScheme) {
81+
switch (widget.state) {
82+
case CheckboxState.unchecked:
83+
return colorScheme.surfaceLow;
84+
case CheckboxState.checked:
85+
return colorScheme.primary;
86+
case CheckboxState.indeterminate:
87+
return colorScheme.primary;
88+
case CheckboxState.disabledChecked:
89+
return colorScheme.disabled;
90+
}
91+
}
92+
93+
Color _getBorderColor(SellioColorScheme colorScheme) {
94+
switch (widget.state) {
95+
case CheckboxState.unchecked:
96+
return colorScheme.stroke;
97+
case CheckboxState.checked:
98+
return colorScheme.primary;
99+
case CheckboxState.indeterminate:
100+
return colorScheme.primary;
101+
case CheckboxState.disabledChecked:
102+
return colorScheme.stroke;
103+
}
104+
}
105+
106+
Widget? _buildIcon(SellioColorScheme colorScheme) {
107+
switch (widget.state) {
108+
case CheckboxState.unchecked:
109+
return null;
110+
case CheckboxState.checked:
111+
return SvgPicture.asset(
112+
Assets.check,
113+
colorFilter: ColorFilter.mode(
114+
colorScheme.onPrimary,
115+
BlendMode.srcIn,
116+
),
117+
width: 10.0,
118+
height: 10.0,
119+
fit: BoxFit.scaleDown,
120+
);
121+
case CheckboxState.indeterminate:
122+
return SvgPicture.asset(
123+
Assets.indeterminate,
124+
colorFilter: ColorFilter.mode(
125+
colorScheme.onPrimary,
126+
BlendMode.srcIn,
127+
),
128+
width: 10.0,
129+
height: 10.0,
130+
fit: BoxFit.scaleDown,
131+
);
132+
case CheckboxState.disabledChecked:
133+
return SvgPicture.asset(
134+
Assets.check,
135+
colorFilter: ColorFilter.mode(
136+
colorScheme.hint,
137+
BlendMode.srcIn,
138+
),
139+
width: 10.0,
140+
height: 10.0,
141+
fit: BoxFit.scaleDown,
142+
);
143+
}
144+
}
145+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import 'package:flutter/material.dart';
2+
import '../../themes/sellio_colors.dart';
3+
4+
enum RadioState {
5+
unchecked,
6+
checked,
7+
disabled,
8+
}
9+
10+
class CustomRadioButton extends StatefulWidget {
11+
final RadioState state;
12+
final ValueChanged<RadioState>? onChanged;
13+
final double size;
14+
15+
const CustomRadioButton({
16+
Key? key,
17+
required this.state,
18+
this.onChanged,
19+
this.size = 20.0,
20+
}) : super(key: key);
21+
22+
@override
23+
State<CustomRadioButton> createState() => _CustomRadioButtonState();
24+
}
25+
26+
class _CustomRadioButtonState extends State<CustomRadioButton> {
27+
@override
28+
Widget build(BuildContext context) {
29+
final bool isEnabled = widget.state != RadioState.disabled;
30+
final colorScheme = SellioColors.light;
31+
32+
return GestureDetector(
33+
onTap: isEnabled && widget.onChanged != null
34+
? () {
35+
if (widget.state == RadioState.unchecked) {
36+
widget.onChanged!(RadioState.checked);
37+
}
38+
}
39+
: null,
40+
child: Container(
41+
width: widget.size,
42+
height: widget.size,
43+
decoration: BoxDecoration(
44+
color: _getBackgroundColor(colorScheme),
45+
border: Border.all(
46+
color: _getBorderColor(colorScheme),
47+
width: 1.5,
48+
),
49+
shape: BoxShape.circle,
50+
boxShadow: widget.state == RadioState.checked
51+
? [
52+
BoxShadow(
53+
color: colorScheme.primary.withOpacity(0.1),
54+
blurRadius: 4,
55+
offset: const Offset(0, 2),
56+
)
57+
]
58+
: null,
59+
),
60+
child: _buildInnerCircle(colorScheme),
61+
),
62+
);
63+
}
64+
65+
Color _getBackgroundColor(SellioColorScheme colorScheme) {
66+
switch (widget.state) {
67+
case RadioState.unchecked:
68+
return colorScheme.surfaceLow;
69+
case RadioState.checked:
70+
return colorScheme.primary;
71+
case RadioState.disabled:
72+
return colorScheme.disabled;
73+
}
74+
}
75+
76+
Color _getBorderColor(SellioColorScheme colorScheme) {
77+
switch (widget.state) {
78+
case RadioState.unchecked:
79+
return colorScheme.stroke;
80+
case RadioState.checked:
81+
return colorScheme.primary;
82+
case RadioState.disabled:
83+
return colorScheme.disabled;
84+
}
85+
}
86+
87+
Widget? _buildInnerCircle(SellioColorScheme colorScheme) {
88+
if (widget.state == RadioState.unchecked) {
89+
return null;
90+
}
91+
92+
return Center(
93+
child: Container(
94+
width: widget.size * 0.4,
95+
height: widget.size * 0.4,
96+
decoration: BoxDecoration(
97+
color: colorScheme.surface,
98+
shape: BoxShape.circle,
99+
),
100+
),
101+
);
102+
}
103+
}

0 commit comments

Comments
 (0)