diff --git a/README.md b/README.md index 503210fa..3aaae8c2 100644 --- a/README.md +++ b/README.md @@ -13,31 +13,36 @@ To be able to use this package follow this steps: ```dart import 'package:yaru/yaru.dart'; ``` -3. Inside your `MaterialApp` set `theme` to `yarulight` which is the standard light theme and `darkTheme` to `yaruDark` to get the yaru dark theme. The `darkTheme` property is used when you toggle your system theme to dark: +3. Create `YaruTheme` ```dart MaterialApp( - theme: yaruLight, - darkTheme: yaruDark, - home: Scaffold( - appBar: AppBar( - title: Text('Yaru Theme'), - ), - body: Container(), + home: YaruTheme( + child: Scaffold( + appBar: AppBar( + title: Text('Yaru Theme'), ), - ); + body: Container(), + ), + ), + ); ``` -## yaru.dart flavors +## Flavors and accent colors -Yaru comes in 7x2 different versions: -- `yaruLight` & `yaruDark` -- `yaruXubuntuLight` & `yaruXubuntuDark` -- `yaruKubuntuLight` & `yaruKubuntuDark` -- `yaruLubuntuLight` & `yaruLubuntuDark` -- `yaruUbuntuStudioLight` & `yaruUbuntuStudioDark` -- `yaruMateLight` & `yaruMateDark` -- `yaruUbuntuBudgieLight` & `yaruUbuntuBudgieDark` +Yaru comes in several flavors and accent colors. The `YaruTheme` widget detects +the appropriate flavor and accent color from the system on Linux, and defaults +to `YaruFlavor.ubuntu` and `YaruAccent.orange` on other platforms. Applications +may choose a specific flavor and accent color by manually setting the `accent` +and `flavor` properties, respectively. + + ```dart + YaruTheme( + accent: YaruAccent.red, + flavor: YaruFlavor.ubuntu, + child: ... + ) + ``` ## Contributing to yaru.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index b82d1bbd..0d924d17 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,40 +1,47 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:yaru/yaru.dart'; import 'package:yaru_example/view/home_page.dart'; void main() { - runApp(MultiProvider( - providers: [ - ChangeNotifierProvider(create: (_) => LightTheme(yaruLight)), - ChangeNotifierProvider(create: (_) => DarkTheme(yaruDark)), - ChangeNotifierProvider(create: (_) => AppTheme(ThemeMode.light)), - ], - child: MyApp(), - )); + runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: HomePage(), + home: Builder(builder: (context) { + return YaruTheme( + data: AppTheme.of(context), + child: HomePage(), + ); + }), debugShowCheckedModeBanner: false, - themeMode: context.watch().value, - theme: context.watch().value, - darkTheme: context.watch().value, ); } } -class LightTheme extends ValueNotifier { - LightTheme(ThemeData value) : super(value); -} - -class DarkTheme extends ValueNotifier { - DarkTheme(ThemeData value) : super(value); -} +class AppTheme { + static YaruThemeData of(BuildContext context) { + return SharedAppData.getValue(context, 'theme', () => YaruThemeData()); + } -class AppTheme extends ValueNotifier { - AppTheme(ThemeMode value) : super(value); + static void apply( + BuildContext context, { + YaruAccent? accent, + YaruFlavor? flavor, + bool? highContrast, + ThemeMode? themeMode, + }) { + SharedAppData.setValue( + context, + 'theme', + AppTheme.of(context).copyWith( + themeMode: themeMode, + accent: accent, + flavor: flavor, + highContrast: highContrast, + ), + ); + } } diff --git a/example/lib/view/home_page.dart b/example/lib/view/home_page.dart index 375bd1cc..0e7eb3d3 100644 --- a/example/lib/view/home_page.dart +++ b/example/lib/view/home_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:yaru/yaru.dart'; import 'package:yaru_example/main.dart'; import 'package:yaru_example/view/color_disk.dart'; @@ -41,9 +40,7 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { - final themeMode = context.watch(); - final lightTheme = context.read(); - final darkTheme = context.read(); + final theme = YaruTheme.of(context); return Scaffold( appBar: AppBar( leading: SizedBox( @@ -54,34 +51,29 @@ class _HomePageState extends State { style: TextButton.styleFrom( padding: const EdgeInsets.all(0), shape: const CircleBorder()), - onPressed: () => themeMode.value == ThemeMode.light - ? themeMode.value = ThemeMode.dark - : themeMode.value = ThemeMode.light, - child: Icon(themeMode.value == ThemeMode.light + onPressed: () => AppTheme.apply(context, + themeMode: theme.themeMode == ThemeMode.light + ? ThemeMode.dark + : ThemeMode.light), + child: Icon(theme.themeMode == ThemeMode.light ? Icons.dark_mode : Icons.light_mode)), ), ), actions: [ ColorDisk( - color: themeMode.value == ThemeMode.light + color: theme.themeMode == ThemeMode.light ? Colors.black : Colors.white, - selected: (lightTheme.value == yaruHighContrastLight || - darkTheme.value == yaruHighContrastDark), - onPressed: () { - lightTheme.value = yaruHighContrastLight; - darkTheme.value = yaruHighContrastDark; - }), + selected: theme.highContrast == true, + onPressed: () => AppTheme.apply(context, highContrast: true)), for (final accent in YaruAccent.values) ColorDisk( - color: getYaruLightTheme(accent).primaryColor, - selected: Theme.of(context).primaryColor == - getYaruLightTheme(accent).primaryColor, - onPressed: () { - lightTheme.value = getYaruLightTheme(accent); - darkTheme.value = getYaruDarkTheme(accent); - }), + color: getYaruLightTheme(accent).primaryColor, + selected: accent == theme.accent && theme.highContrast != true, + onPressed: () => + AppTheme.apply(context, accent: accent, highContrast: false), + ), SizedBox( width: 20, ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index cec83872..66b4239f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: flutter: sdk: flutter - provider: ^6.0.2 yaru: path: ../ diff --git a/lib/src/widgets/inherited_theme.dart b/lib/src/widgets/inherited_theme.dart new file mode 100644 index 00000000..1deef48a --- /dev/null +++ b/lib/src/widgets/inherited_theme.dart @@ -0,0 +1,290 @@ +import 'dart:async'; + +import 'package:dbus/dbus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gsettings/gsettings.dart'; +import 'package:platform/platform.dart'; + +import 'package:yaru/yaru.dart'; + +/// Available Yaru flavors. +enum YaruFlavor { + /// Ubuntu Budgie + budgie, + + /// Kubuntu + kubuntu, + + /// Lubuntu + lubuntu, + + /// Ubuntu MATE + mate, + + /// Ubuntu + ubuntu, + + /// Xubuntu + xubuntu, +} + +YaruFlavor? _detectYaruFlavor(Platform platform) { + final desktop = !kIsWeb + ? platform.environment['XDG_CURRENT_DESKTOP']?.toUpperCase() + : null; + if (desktop != null) { + if (desktop.contains('BUDGIE')) return YaruFlavor.budgie; + if (desktop.contains('GNOME')) return YaruFlavor.ubuntu; + if (desktop.contains('KDE')) return YaruFlavor.kubuntu; + if (desktop.contains('LXQT')) return YaruFlavor.lubuntu; + if (desktop.contains('MATE')) return YaruFlavor.mate; + if (desktop.contains('XFCE')) return YaruFlavor.xubuntu; + } + return null; +} + +const _yaruAccents = { + 'Yaru': YaruAccent.orange, + 'Yaru-dark': YaruAccent.orange, + 'Yaru-bark': YaruAccent.bark, + 'Yaru-bark-dark': YaruAccent.bark, + 'Yaru-sage': YaruAccent.sage, + 'Yaru-sage-dark': YaruAccent.sage, + 'Yaru-olive': YaruAccent.olive, + 'Yaru-olive-dark': YaruAccent.olive, + 'Yaru-viridian': YaruAccent.viridian, + 'Yaru-viridian-dark': YaruAccent.viridian, + 'Yaru-prussiangreen': YaruAccent.prussianGreen, + 'Yaru-prussiangreen-dark': YaruAccent.prussianGreen, + 'Yaru-blue': YaruAccent.blue, + 'Yaru-blue-dark': YaruAccent.blue, + 'Yaru-purple': YaruAccent.purple, + 'Yaru-purple-dark': YaruAccent.purple, + 'Yaru-magenta': YaruAccent.magenta, + 'Yaru-magenta-dark': YaruAccent.magenta, + 'Yaru-red': YaruAccent.red, + 'Yaru-red-dark': YaruAccent.red, +}; + +/// 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. +/// +/// 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({ + Key? key, + required this.child, + this.data = const YaruThemeData(), + @visibleForTesting Platform? platform, + @visibleForTesting GSettings? settings, + }) : _platform = platform ?? const LocalPlatform(), + _settings = settings, + super(key: key); + + /// The widget below this widget in the tree. + final Widget child; + + /// Specifies the theme for descendant widgets. + final YaruThemeData data; + + final Platform _platform; + final GSettings? _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 createState() => _YaruThemeState(); +} + +class _YaruThemeState extends State { + YaruAccent? _accent; + YaruFlavor? _flavor; + GSettings? _settings; + StreamSubscription>? _subscription; + + @override + void initState() { + super.initState(); + if (widget.data.accent == null && !kIsWeb && widget._platform.isLinux) { + _settings = widget._settings ?? GSettings('org.gnome.desktop.interface'); + _subscription = _settings!.keysChanged.listen((keys) { + if (keys.contains('gtk-theme')) { + updateAccent(); + } + }); + updateAccent(); + } + if (widget.data.flavor == null) { + _flavor = _detectYaruFlavor(widget._platform); + } + } + + @override + void dispose() { + super.dispose(); + _subscription?.cancel(); + if (widget._settings == null) { + _settings?.close(); + } + } + + Future updateAccent() async { + assert(!kIsWeb && widget._platform.isLinux); + final name = await _settings?.get('gtk-theme') as DBusString; + setState(() => _accent = _yaruAccents[name.value]); + } + + 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( + accent: widget.data.accent ?? _accent, + flavor: widget.data.flavor ?? _flavor, + 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; + } + + switch (data.flavor) { + case YaruFlavor.budgie: + return dark ? yaruUbuntuBudgieDark : yaruUbuntuBudgieLight; + case YaruFlavor.kubuntu: + return dark ? yaruKubuntuDark : yaruKubuntuLight; + case YaruFlavor.lubuntu: + return dark ? yaruLubuntuDark : yaruLubuntuLight; + case YaruFlavor.mate: + return dark ? yaruMateDark : yaruMateLight; + case YaruFlavor.xubuntu: + return dark ? yaruXubuntuDark : yaruXubuntuLight; + default: + final accent = data.accent ?? YaruAccent.orange; + return dark ? getYaruDarkTheme(accent) : getYaruLightTheme(accent); + } + } + + @override + Widget build(BuildContext context) { + final data = resolveData(); + return _YaruInheritedTheme( + data: data, + child: AnimatedTheme( + data: resolveTheme(data), + child: widget.child, + ), + ); + } +} + +@immutable +class YaruThemeData with Diagnosticable { + const YaruThemeData({ + this.accent, + this.flavor, + this.highContrast, + this.themeMode, + }); + + /// Specifies the accent color. Only applicaple if [flavor] is + /// `YaruFlavor.ubuntu`. + final YaruAccent? accent; + + /// Specifies the theme flavor. + final YaruFlavor? flavor; + + /// 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({ + YaruAccent? accent, + YaruFlavor? flavor, + bool? highContrast, + ThemeMode? themeMode, + }) { + return YaruThemeData( + accent: accent ?? this.accent, + flavor: flavor ?? this.flavor, + highContrast: highContrast ?? this.highContrast, + themeMode: themeMode ?? this.themeMode, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('accent', accent)); + properties.add(DiagnosticsProperty('flavor', flavor)); + properties.add(DiagnosticsProperty('highContrast', highContrast)); + properties.add(DiagnosticsProperty('themeMode', themeMode)); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is YaruThemeData && + other.accent == accent && + other.flavor == flavor && + other.highContrast == highContrast && + other.themeMode == themeMode; + } + + @override + int get hashCode => Object.hash(accent, flavor, highContrast, themeMode); +} + +class _YaruInheritedTheme extends InheritedTheme { + const _YaruInheritedTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: 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); + } +} diff --git a/lib/yaru.dart b/lib/yaru.dart index 8dc0d59a..dda4b24d 100644 --- a/lib/yaru.dart +++ b/lib/yaru.dart @@ -19,3 +19,4 @@ export 'package:yaru/src/themes/yaru_ubuntu_studio_dark.dart'; export 'package:yaru/src/themes/yaru_ubuntu_studio_light.dart'; export 'package:yaru/src/themes/yaru_xubuntu_dark.dart'; export 'package:yaru/src/themes/yaru_xubuntu_light.dart'; +export 'package:yaru/src/widgets/inherited_theme.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index a1885593..c723bc99 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,8 +10,17 @@ environment: flutter: ">=1.17.0" dependencies: + dbus: ^0.7.3 flutter: sdk: flutter + gsettings: ^0.2.5 + platform: ^3.1.0 + +dev_dependencies: + build_runner: ^2.1.10 + flutter_test: + sdk: flutter + mockito: ^5.1.0 flutter: fonts: diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 00000000..24013966 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,169 @@ +import 'dart:async'; + +import 'package:dbus/dbus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:gsettings/gsettings.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; +import 'package:yaru/yaru.dart'; + +import 'widget_test.mocks.dart'; + +@GenerateMocks([GSettings]) +void main() { + group('accent', () { + testWidgets('explicit', (tester) async { + await tester.pumpTheme(accent: YaruAccent.blue); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).accent, YaruAccent.blue); + }); + + testWidgets('unknown', (tester) async { + final settings = MockGSettings(); + when(settings.keysChanged).thenAnswer((_) => Stream.empty()); + when(settings.get('gtk-theme')).thenAnswer((_) async => DBusString('')); + + await tester.pumpTheme(settings: settings); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).accent, null); + }); + + testWidgets('yaru', (tester) async { + final settings = MockGSettings(); + when(settings.keysChanged).thenAnswer((_) => Stream.empty()); + when(settings.get('gtk-theme')) + .thenAnswer((_) async => DBusString('Yaru-blue')); + + await tester.pumpTheme(settings: settings); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).accent, YaruAccent.blue); + }); + + testWidgets('change', (tester) async { + final settings = MockGSettings(); + final keysChanged = StreamController>(sync: true); + addTearDown(keysChanged.close); + when(settings.keysChanged).thenAnswer((_) => keysChanged.stream); + when(settings.get('gtk-theme')) + .thenAnswer((_) async => DBusString('Yaru-blue')); + + await tester.pumpTheme(settings: settings); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).accent, YaruAccent.blue); + + when(settings.get('gtk-theme')) + .thenAnswer((_) async => DBusString('Yaru-red')); + keysChanged.add(['gtk-theme']); + await tester.pump(); + expect(YaruTheme.of(context).accent, YaruAccent.red); + }); + }); + + group('flavor', () { + testWidgets('explicit', (tester) async { + await tester.pumpTheme(flavor: YaruFlavor.ubuntu); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.ubuntu); + }); + + testWidgets('unknown', (tester) async { + await tester.pumpTheme(desktop: 'unknown'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, isNull); + }); + + testWidgets('budgie', (tester) async { + await tester.pumpTheme(desktop: 'budgie-desktop'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.budgie); + }); + + testWidgets('ubuntu', (tester) async { + await tester.pumpTheme(desktop: 'GNOME:ubuntu'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.ubuntu); + }); + + testWidgets('kde', (tester) async { + await tester.pumpTheme(desktop: 'KDE'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.kubuntu); + }); + + testWidgets('lubuntu', (tester) async { + await tester.pumpTheme(desktop: 'LXQt'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.lubuntu); + }); + + testWidgets('mate', (tester) async { + await tester.pumpTheme(desktop: 'MATE'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.mate); + }); + + testWidgets('xubuntu', (tester) async { + await tester.pumpTheme(desktop: 'XFCE'); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).flavor, YaruFlavor.xubuntu); + }); + }); + + group('high contrast', () { + testWidgets('on', (tester) async { + await tester.pumpTheme(highContrast: true); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).highContrast, isTrue); + }); + + testWidgets('off', (tester) async { + await tester.pumpTheme(highContrast: false); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).highContrast, isFalse); + }); + }); + + group('theme mode', () { + testWidgets('light', (tester) async { + await tester.pumpTheme(themeMode: ThemeMode.light); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).themeMode, ThemeMode.light); + }); + + testWidgets('dark', (tester) async { + await tester.pumpTheme(themeMode: ThemeMode.dark); + final context = tester.element(find.byType(Container)); + expect(YaruTheme.of(context).themeMode, ThemeMode.dark); + }); + }); +} + +extension ThemeTester on WidgetTester { + Future pumpTheme({ + YaruAccent? accent, + YaruFlavor? flavor, + bool? highContrast, + ThemeMode? themeMode, + String desktop = '', + GSettings? settings, + }) async { + final data = YaruThemeData( + accent: accent, + flavor: flavor, + highContrast: highContrast, + themeMode: themeMode, + ); + await pumpWidget(MaterialApp( + key: ValueKey(data), + home: YaruTheme( + child: Container(), + data: data, + platform: FakePlatform(environment: {'XDG_CURRENT_DESKTOP': desktop}), + settings: settings, + ), + )); + return pump(); + } +} diff --git a/test/widget_test.mocks.dart b/test/widget_test.mocks.dart new file mode 100644 index 00000000..c07fc0b1 --- /dev/null +++ b/test/widget_test.mocks.dart @@ -0,0 +1,77 @@ +// Mocks generated by Mockito 5.1.0 from annotations +// in yaru/test/widget_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i4; + +import 'package:dbus/dbus.dart' as _i2; +import 'package:gsettings/src/gsettings.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeDBusValue_0 extends _i1.Fake implements _i2.DBusValue {} + +/// A class which mocks [GSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGSettings extends _i1.Mock implements _i3.GSettings { + MockGSettings() { + _i1.throwOnMissingStub(this); + } + + @override + String get schemaName => + (super.noSuchMethod(Invocation.getter(#schemaName), returnValue: '') + as String); + @override + _i4.Stream> get keysChanged => (super.noSuchMethod( + Invocation.getter(#keysChanged), + returnValue: Stream>.empty()) as _i4.Stream>); + @override + _i4.Future> list() => + (super.noSuchMethod(Invocation.method(#list, []), + returnValue: Future>.value([])) + as _i4.Future>); + @override + _i4.Future<_i2.DBusValue> get(String? name) => + (super.noSuchMethod(Invocation.method(#get, [name]), + returnValue: Future<_i2.DBusValue>.value(_FakeDBusValue_0())) + as _i4.Future<_i2.DBusValue>); + @override + _i4.Future<_i2.DBusValue> getDefault(String? name) => + (super.noSuchMethod(Invocation.method(#getDefault, [name]), + returnValue: Future<_i2.DBusValue>.value(_FakeDBusValue_0())) + as _i4.Future<_i2.DBusValue>); + @override + _i4.Future isSet(String? name) => + (super.noSuchMethod(Invocation.method(#isSet, [name]), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future set(String? name, _i2.DBusValue? value) => + (super.noSuchMethod(Invocation.method(#set, [name, value]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future unset(String? name) => + (super.noSuchMethod(Invocation.method(#unset, [name]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setAll(Map? values) => + (super.noSuchMethod(Invocation.method(#setAll, [values]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future close() => (super.noSuchMethod(Invocation.method(#close, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); +}