From c88fe55e06621b1b20468baf4dee853013485f7c Mon Sep 17 00:00:00 2001 From: Frederik Feichtmeier Date: Wed, 18 Sep 2024 19:23:21 +0200 Subject: [PATCH] feat: follow accent-color gsettings key for 24.10 (#922) * feat: follow accent-color gsettings key for 24.10 * fix: make theme sync work * fix: use switch expression --- lib/src/settings.dart | 24 ------ lib/src/theme_widgets/gtk_constants.dart | 2 + lib/src/theme_widgets/inherited_theme.dart | 49 +++++++++-- lib/src/theme_widgets/settings_service.dart | 93 +++++++++++++++++++++ lib/src/theme_widgets/yaru_settings.dart | 59 +++++++++++++ pubspec.yaml | 2 + 6 files changed, 196 insertions(+), 33 deletions(-) delete mode 100644 lib/src/settings.dart create mode 100644 lib/src/theme_widgets/gtk_constants.dart create mode 100644 lib/src/theme_widgets/settings_service.dart create mode 100644 lib/src/theme_widgets/yaru_settings.dart diff --git a/lib/src/settings.dart b/lib/src/settings.dart deleted file mode 100644 index c731b1afb..000000000 --- a/lib/src/settings.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:gtk/gtk.dart'; - -abstract class YaruSettings { - factory YaruSettings() = YaruGtkSettings; - const YaruSettings._(); - - String? getThemeName(); - Stream get themeNameChanged; -} - -class YaruGtkSettings extends YaruSettings { - YaruGtkSettings([@visibleForTesting GtkSettings? settings]) - : _settings = settings ?? GtkSettings(), - super._(); - - final GtkSettings _settings; - @override - String? getThemeName() => _settings.getProperty(kGtkThemeName) as String?; - - @override - Stream get themeNameChanged => - _settings.notifyProperty(kGtkThemeName).cast(); -} diff --git a/lib/src/theme_widgets/gtk_constants.dart b/lib/src/theme_widgets/gtk_constants.dart new file mode 100644 index 000000000..1f48216c6 --- /dev/null +++ b/lib/src/theme_widgets/gtk_constants.dart @@ -0,0 +1,2 @@ +const kSchemaInterface = 'org.gnome.desktop.interface'; +const kAccentColorKey = 'accent-color'; diff --git a/lib/src/theme_widgets/inherited_theme.dart b/lib/src/theme_widgets/inherited_theme.dart index 35167c8b9..8ac73f5f9 100644 --- a/lib/src/theme_widgets/inherited_theme.dart +++ b/lib/src/theme_widgets/inherited_theme.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:platform_linux/platform.dart'; -import 'package:yaru/src/settings.dart'; +import 'package:yaru/src/theme_widgets/yaru_settings.dart'; import '../../theme.dart'; @@ -142,21 +142,29 @@ class YaruTheme extends StatefulWidget { class _YaruThemeState extends State { YaruVariant? _variant; YaruSettings? _settings; - StreamSubscription? _subscription; + StreamSubscription? _themeNameSubscription; + StreamSubscription? _accentColorSubScription; @override void initState() { super.initState(); if (widget.data.variant == null && canDetectVariant()) { _settings = widget._settings ?? YaruSettings(); - _variant = resolveVariant(_settings?.getThemeName()); - _subscription = _settings!.themeNameChanged.listen(updateVariant); + _settings?.init(); + _variant = resolveAccentColorVariant(_settings?.getAccentColor()) ?? + resolveGtkThemeVariant(_settings?.getThemeName()); + _accentColorSubScription ??= + _settings!.accentColorChanged.listen(updateVariant); + _themeNameSubscription ??= + _settings!.themeNameChanged.listen(updateVariant); } } @override void dispose() { - _subscription?.cancel(); + _themeNameSubscription?.cancel(); + _accentColorSubScription?.cancel(); + _settings?.dispose(); super.dispose(); } @@ -168,7 +176,7 @@ class _YaruThemeState extends State { // This very simple but manual solution is the safest approach for now // New theme mappings can be added here easily, after adding them in variant.dart - YaruVariant? resolveVariant(String? name) => + YaruVariant? resolveGtkThemeVariant(String? name) => switch (name?.replaceAll('-dark', '')) { 'Adwaita' => YaruVariant.adwaitaBlue, 'Adwaita-green' || 'Yaru-green' => YaruVariant.adwaitaGreen, @@ -193,10 +201,33 @@ class _YaruThemeState extends State { _ => _defaultFallBackVariant(widget._platform), }; + // This is the gnome accent-color feature for Ubuntu 24.10+ + // it is null on older systems. + // Previous similar Yaru versions replace their gnome counterpart. + // At some point we probably want to check which distribution of gnome is run and use the + // upstream colors instead. + YaruVariant? resolveAccentColorVariant(String? name) => switch (name) { + 'blue' => YaruVariant.blue, + 'teal' || 'Yaru-teal' => YaruVariant.adwaitaTeal, + 'green' || 'Yaru-green' => YaruVariant.adwaitaGreen, + 'yellow' || 'Yaru-yellow' => YaruVariant.adwaitaYellow, + 'orange' => YaruVariant.orange, + 'red' => YaruVariant.red, + 'pink' || 'Yaru-pink' => YaruVariant.magenta, + 'purple' => YaruVariant.purple, + 'slate' || 'Yaru-slate' => YaruVariant.adwaitaSlate, + 'brown' => YaruVariant.wartyBrown, + _ => null, + }; + void updateVariant([String? value]) { assert(canDetectVariant()); - final name = value ?? _settings?.getThemeName(); - setState(() => _variant = resolveVariant(name)); + final gtkThemeName = value ?? _settings?.getThemeName(); + final accentColor = value ?? _settings?.getAccentColor(); + setState( + () => _variant = resolveAccentColorVariant(accentColor) ?? + resolveGtkThemeVariant(gtkThemeName), + ); } ThemeMode resolveMode() { @@ -231,7 +262,7 @@ class _YaruThemeState extends State { @override Widget build(BuildContext context) { - if (_settings != null && _subscription == null) { + if (_settings != null && _themeNameSubscription == null) { return const SizedBox.shrink(); // #231 } final data = resolveData(); diff --git a/lib/src/theme_widgets/settings_service.dart b/lib/src/theme_widgets/settings_service.dart new file mode 100644 index 000000000..af88659af --- /dev/null +++ b/lib/src/theme_widgets/settings_service.dart @@ -0,0 +1,93 @@ +import 'package:dbus/dbus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:gsettings/gsettings.dart'; + +class GSettingsService { + final _settings = {}; + + GnomeSettings? lookup(String schemaId, {String? path}) { + try { + return _settings[schemaId] ??= GnomeSettings(schemaId, path: path); + } on GSettingsSchemaNotInstalledException catch (_) { + return null; + } + } + + void dispose() { + for (final settings in _settings.values) { + settings?.dispose(); + } + } +} + +class GnomeSettings { + GnomeSettings(String schemaId, {String? path}) + : _settings = GSettings(schemaId, path: path) { + _settings.keysChanged.listen((keys) { + for (final key in keys) { + _updateValue(key); + } + }); + } + + final GSettings _settings; + final _values = {}; + final _listeners = {}; + + void addListener(VoidCallback listener) => _listeners.add(listener); + void removeListener(VoidCallback listener) => _listeners.remove(listener); + void notifyListeners() { + for (final listener in _listeners) { + listener(); + } + } + + void dispose() => _settings.close(); + + bool? boolValue(String key) => getValue(key); + int? intValue(String key) => getValue(key); + double? doubleValue(String key) => getValue(key); + String? stringValue(String key) => getValue(key); + Iterable? stringArrayValue(String key) => + getValue(key)?.cast(); + + T? getValue(String key) => _values[key] ?? _updateValue(key); + + T? _updateValue(String key) { + T? value; + try { + _settings.get(key).then((v) { + value = v.toNative() as T?; + if (_values[key] != value) { + _values[key] = value; + notifyListeners(); + } + }); + } on GSettingsUnknownKeyException catch (_) {} + return value; + } + + Future setValue(String key, T value) async { + if (_values[key] == value) return; + _values[key] = value; + + return switch (T) { + const (bool) => _settings.set(key, DBusBoolean(value as bool)), + const (int) => _settings.set(key, DBusInt32(value as int)), + const (double) => _settings.set(key, DBusDouble(value as double)), + const (String) => _settings.set(key, DBusString(value as String)), + const (List) => + _settings.set(key, DBusArray.string(value as List)), + _ => throw UnsupportedError('Unsupported type: $T'), + }; + } + + Future setUint32Value(String key, int value) async { + if (_values[key] == value) return; + _values[key] = value; + await _settings.set(key, DBusUint32(value)); + } + + Future resetValue(String key) => + _settings.setAll({key: null}); +} diff --git a/lib/src/theme_widgets/yaru_settings.dart b/lib/src/theme_widgets/yaru_settings.dart new file mode 100644 index 000000000..e323d174a --- /dev/null +++ b/lib/src/theme_widgets/yaru_settings.dart @@ -0,0 +1,59 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:gtk/gtk.dart'; +import 'package:yaru/src/theme_widgets/gtk_constants.dart'; +import 'package:yaru/src/theme_widgets/settings_service.dart'; + +abstract class YaruSettings { + factory YaruSettings() = YaruGtkSettings; + const YaruSettings._(); + + String? getThemeName(); + String? getAccentColor(); + Stream get themeNameChanged; + Stream get accentColorChanged; + void init(); + Future dispose(); +} + +class YaruGtkSettings extends YaruSettings { + YaruGtkSettings([ + @visibleForTesting GtkSettings? settings, + @visibleForTesting GSettingsService? settingsService, + ]) : _gtkSettings = settings ?? GtkSettings(), + _gSettingsService = settingsService ?? GSettingsService(), + super._(); + + final GtkSettings _gtkSettings; + final GSettingsService _gSettingsService; + GnomeSettings? _gSettings; + + @override + String? getThemeName() => _gtkSettings.getProperty(kGtkThemeName) as String?; + + @override + Stream get themeNameChanged => + _gtkSettings.notifyProperty(kGtkThemeName).cast(); + + final _accentColorController = StreamController.broadcast(); + @override + Stream get accentColorChanged => _accentColorController.stream; + + @override + void init() { + _gSettings ??= _gSettingsService.lookup(kSchemaInterface); + _gSettings?.addListener( + () => _accentColorController.add(getAccentColor()), + ); + } + + @override + Future dispose() async { + await _accentColorController.close(); + _gSettingsService.dispose(); + } + + @override + String? getAccentColor() => _gSettings?.stringValue(kAccentColorKey); +} diff --git a/pubspec.yaml b/pubspec.yaml index 289d54cdc..d1db05709 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +12,10 @@ dependencies: animated_vector: ^0.2.0 animated_vector_annotations: ^0.2.0 collection: ^1.17.0 + dbus: ^0.7.10 flutter: sdk: flutter + gsettings: ^0.2.8 gtk: ^2.1.0 platform: ^3.1.5 platform_linux: ^0.1.2