-
Notifications
You must be signed in to change notification settings - Fork 32
/
inherited_theme.dart
281 lines (248 loc) · 8.04 KB
/
inherited_theme.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:platform/platform.dart';
import 'package:yaru/src/settings.dart';
import 'package:yaru/yaru.dart';
YaruVariant? _detectYaruVariant(Platform platform) {
final desktop = !kIsWeb
? platform.environment['XDG_CURRENT_DESKTOP']?.toUpperCase()
: null;
if (desktop != null) {
if (desktop.contains('BUDGIE')) return YaruVariant.ubuntuBudgieBlue;
if (desktop.contains('GNOME')) return YaruVariant.orange;
if (desktop.contains('KDE')) return YaruVariant.kubuntuBlue;
if (desktop.contains('LXQT')) return YaruVariant.lubuntuBlue;
if (desktop.contains('MATE')) return YaruVariant.ubuntuMateGreen;
if (desktop.contains('XFCE')) return YaruVariant.xubuntuBlue;
}
return null;
}
/// Applies Yaru theme to descendant widgets.
///
/// Descendant widgets obtain the current theme's [YaruThemeData] object using
/// [YaruTheme.of]. When a widget uses [YaruTheme.of], it is automatically
/// rebuilt if the theme later changes, so that the changes can be applied.
///
/// There are two ways to use [YaruTheme] - with a child widget or as a builder.
///
/// ### Child widget
///
/// The simplest way to use [YaruTheme] is to wrap a child widget in it.
///
/// ```dart
/// MaterialApp(
/// home: YaruTheme(
/// child: ...
/// ),
/// )
/// ```
///
/// **Note**: [YaruTheme] must be a _descendant_ of [MaterialApp] to avoid that
/// [MaterialApp] overrides [YaruTheme].
///
/// When used like this, [YaruTheme] internally creates an [AnimatedTheme]
/// widget populated with the appropriate Yaru theme data. Moreover, The child
/// widget gets automatically rebuilt whenever the system theme changes.
///
/// ### Builder
///
/// An alternative way to use [YaruTheme] is to use it as a builder.
///
/// ```dart
/// YaruTheme(
/// builder: (context, yaru, child) {
/// return MaterialApp(
/// theme: yaru.variant?.theme,
/// darkTheme: yaru.variant?.darkTheme,
/// home: ...
/// );
/// },
/// )
/// ```
///
/// When used like this, [YaruTheme] does not create an [AnimatedTheme] widget.
/// Instead, it passes a [YaruThemeData] object to the [builder] function to
/// allow passing the desired values to [MaterialApp]. This has the advantage
/// that any widget created by [MaterialApp], such as the built-in [Navigator],
/// gains Yaru-theme as well.
///
/// See also:
/// * [YaruThemeData]
class YaruTheme extends StatefulWidget {
/// Applies the given theme [data] to [child].
///
/// The [data] and [child] arguments must not be null.
const YaruTheme({
super.key,
this.builder,
this.child,
this.data = const YaruThemeData(),
@visibleForTesting Platform? platform,
@visibleForTesting YaruSettings? settings,
}) : assert(
builder != null || child != null,
'Either builder or child must be provided',
),
_platform = platform ?? const LocalPlatform(),
_settings = settings;
/// Builds the widget below this widget in the tree.
final ValueWidgetBuilder<YaruThemeData>? builder;
/// The widget below this widget in the tree.
final Widget? child;
/// Specifies the theme for descendant widgets.
final YaruThemeData data;
final Platform _platform;
final YaruSettings? _settings;
/// The data from the closest [YaruTheme] instance that encloses the given
/// context.
static YaruThemeData of(BuildContext context) => maybeOf(context)!;
/// An optional data from the closest [YaruTheme] instance that encloses the
/// given context or `null` if there is no such ancestor.
static YaruThemeData? maybeOf(BuildContext context) {
final theme =
context.dependOnInheritedWidgetOfExactType<_YaruInheritedTheme>();
return theme?.data;
}
@override
State<YaruTheme> createState() => _YaruThemeState();
}
class _YaruThemeState extends State<YaruTheme> {
YaruVariant? _variant;
YaruSettings? _settings;
StreamSubscription<String?>? _subscription;
@override
void initState() {
super.initState();
if (widget.data.variant == null && !kIsWeb && widget._platform.isLinux) {
_settings = widget._settings ?? const YaruSettings();
_subscription = _settings!.themeNameChanged.listen((name) {
updateVariant(name);
});
updateVariant();
}
}
@override
void dispose() {
super.dispose();
_subscription?.cancel();
}
// "Yaru-prussiangreen-dark" => YaruAccent.prussianGreen
YaruVariant? resolveVariant(String? name) {
if (name?.endsWith('-dark') == true) {
name = name!.substring(0, name.length - 5);
}
if (name?.startsWith('Yaru-') == true) {
name = name!.substring(5);
}
if (name == 'Yaru') {
return YaruVariant.orange;
}
for (final value in YaruVariant.values) {
if (value.name.toLowerCase() == name?.toLowerCase()) {
return value;
}
}
return _detectYaruVariant(widget._platform);
}
Future<void> updateVariant([String? value]) async {
assert(!kIsWeb && widget._platform.isLinux);
final name = value ?? await _settings?.getThemeName();
setState(() => _variant = resolveVariant(name));
}
ThemeMode resolveMode() {
final mode = widget.data.themeMode ?? ThemeMode.system;
if (mode == ThemeMode.system) {
return MediaQuery.platformBrightnessOf(context) == Brightness.dark
? ThemeMode.dark
: ThemeMode.light;
}
return mode;
}
YaruThemeData resolveData() {
return YaruThemeData(
variant: widget.data.variant ?? _variant,
highContrast:
widget.data.highContrast ?? MediaQuery.highContrastOf(context),
themeMode: resolveMode(),
);
}
ThemeData resolveTheme(YaruThemeData data) {
final dark = data.themeMode == ThemeMode.dark;
if (data.highContrast!) {
return dark ? yaruHighContrastDark : yaruHighContrastLight;
}
final variant = data.variant ?? YaruVariant.orange;
return dark ? variant.darkTheme : variant.theme;
}
@override
Widget build(BuildContext context) {
final data = resolveData();
return _YaruInheritedTheme(
data: data,
child: widget.builder?.call(context, data, widget.child) ??
AnimatedTheme(
data: resolveTheme(data),
child: widget.child!,
),
);
}
}
@immutable
class YaruThemeData with Diagnosticable {
const YaruThemeData({
this.variant,
this.highContrast,
this.themeMode,
});
/// Specifies the theme variant.
final YaruVariant? variant;
/// Whether to use high contrast colors.
final bool? highContrast;
/// Whether a light or dark theme is used.
final ThemeMode? themeMode;
/// Creates a copy of this [YaruThemeData] with the provided values.
YaruThemeData copyWith({
YaruVariant? variant,
bool? highContrast,
ThemeMode? themeMode,
}) {
return YaruThemeData(
variant: variant ?? this.variant,
highContrast: highContrast ?? this.highContrast,
themeMode: themeMode ?? this.themeMode,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<YaruVariant>('variant', variant));
properties.add(DiagnosticsProperty<bool>('highContrast', highContrast));
properties.add(DiagnosticsProperty<ThemeMode>('themeMode', themeMode));
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is YaruThemeData &&
other.variant == variant &&
other.highContrast == highContrast &&
other.themeMode == themeMode;
}
@override
int get hashCode => Object.hash(variant, highContrast, themeMode);
}
class _YaruInheritedTheme extends InheritedTheme {
const _YaruInheritedTheme({
required this.data,
required super.child,
});
final YaruThemeData? data;
@override
bool updateShouldNotify(covariant _YaruInheritedTheme oldWidget) {
return data != oldWidget.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
return _YaruInheritedTheme(data: data, child: child);
}
}