/
color_scheme_popup_menu.dart
146 lines (138 loc) · 5.04 KB
/
color_scheme_popup_menu.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
/// Widget used to select a ColorScheme based colors in example 5.
///
/// Uses index out out of range of [SchemeColor] to represent and select
/// no selection of [SchemeColor] which sets its value to null in parent,
/// so we can use a selectable item as null input, to represent default value
/// via no value definition. A bit ugly/pragmatic approach.
class ColorSchemePopupMenu extends StatelessWidget {
const ColorSchemePopupMenu({
super.key,
required this.index,
this.onChanged,
this.title,
this.subtitle,
this.contentPadding,
this.labelForDefault = 'default (primary)',
this.popupLabelDefault,
});
final int index;
final ValueChanged<int>? onChanged;
final Widget? title;
final Widget? subtitle;
final EdgeInsetsGeometry? contentPadding; // Defaults to 16.
final String labelForDefault;
final String? popupLabelDefault;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final TextStyle txtStyle = theme.textTheme.labelLarge!;
final bool enabled = onChanged != null;
// Negative value, or index covering the the last two in th enum,
// the deprecated primaryVariant and secondaryVariant are considered as
// null and default value.
final bool useDefault = index < 0 || index >= SchemeColor.values.length - 2;
final String colorName = enabled && !useDefault
? SchemeColor.values[index].name
: labelForDefault;
return PopupMenuButton<int>(
tooltip: '',
padding: EdgeInsets.zero,
onSelected: (int index) {
// We return -1 for index that reached first deprecated color.
// -1, or any negative value will cause controller for a
// SchemeColor to be set to "null", we need to be able to do that
// to input "null" property value to SchemeColor configs.
onChanged?.call(index >= SchemeColor.values.length - 2 ? -1 : index);
},
enabled: enabled,
itemBuilder: (BuildContext context) => <PopupMenuItem<int>>[
// Exclude the last two enums, deprecated primaryVariant and
// secondaryVariant.
for (int i = 0; i < SchemeColor.values.length - 1; i++)
PopupMenuItem<int>(
value: i,
child: ListTile(
dense: true,
leading: ColorSchemeBox(
color: i >= SchemeColor.values.length - 2
? colorScheme.surface
: FlexSubThemes.schemeColor(
SchemeColor.values[i],
colorScheme,
),
defaultColor: i >= SchemeColor.values.length - 2,
),
title: i >= SchemeColor.values.length - 2
// If we reached first deprecated color, make default label.
? Text(popupLabelDefault ?? labelForDefault, style: txtStyle)
: Text(SchemeColor.values[i].name, style: txtStyle),
),
)
],
child: ListTile(
enabled: enabled,
contentPadding:
contentPadding ?? const EdgeInsets.symmetric(horizontal: 16),
title: title,
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (subtitle != null) subtitle!,
Text('ColorScheme color $colorName'),
],
),
trailing: ColorSchemeBox(
color: enabled && !useDefault
? FlexSubThemes.schemeColor(
SchemeColor.values[index],
colorScheme,
)
: colorScheme.surface,
defaultColor: useDefault,
),
),
);
}
}
class ColorSchemeBox extends StatelessWidget {
const ColorSchemeBox({
super.key,
this.color = Colors.white,
this.size = const Size(45, 35),
this.defaultColor = false,
});
final Color color;
final Size size;
final bool defaultColor;
// Return true if the color is light, meaning it needs dark text for contrast.
static bool _isLight(final Color color) =>
FlexSchemeOnColors.estimateErrorBrightness(color) == Brightness.light;
// On color used when a theme color property does not have a theme onColor.
static Color _onColor(final Color color) => _isLight(color)
? Colors.black.withOpacity(0.4)
: Colors.white.withOpacity(0.4);
@override
Widget build(BuildContext context) {
return SizedBox(
width: size.width,
height: size.height,
child: Material(
color: color,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(12)),
side: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
),
child: defaultColor
? Icon(Icons.texture_outlined, color: _onColor(color))
: Icon(Icons.palette_outlined, color: _onColor(color)),
),
);
}
}