Browse Source

feat(neon): add optons collection builder

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/1003/head
Nikolas Rimikis 1 year ago
parent
commit
9efa2f337d
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 23
      packages/neon/neon/lib/src/app.dart
  2. 5
      packages/neon/neon/lib/src/settings/models/options_collection.dart
  3. 71
      packages/neon/neon/lib/src/widgets/options_collection_builder.dart

23
packages/neon/neon/lib/src/app.dart

@ -22,6 +22,7 @@ import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/localizations.dart'; import 'package:neon/src/utils/localizations.dart';
import 'package:neon/src/utils/provider.dart'; import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/utils/push_utils.dart'; import 'package:neon/src/utils/push_utils.dart';
import 'package:neon/src/widgets/options_collection_builder.dart';
import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:quick_actions/quick_actions.dart'; import 'package:quick_actions/quick_actions.dart';
@ -275,13 +276,9 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
@override @override
Widget build(final BuildContext context) => ValueListenableBuilder( Widget build(final BuildContext context) => OptionsCollectionBuilder(
valueListenable: _globalOptions.themeMode, valueListenable: _globalOptions,
builder: (final context, final themeMode, final _) => ValueListenableBuilder( builder: (final context, final options, final _) => StreamBuilder<Account?>(
valueListenable: _globalOptions.themeOLEDAsDark,
builder: (final context, final themeOLEDAsDark, final _) => ValueListenableBuilder(
valueListenable: _globalOptions.themeKeepOriginalAccentColor,
builder: (final context, final themeKeepOriginalAccentColor, final _) => StreamBuilder<Account?>(
stream: _accountsBloc.activeAccount, stream: _accountsBloc.activeAccount,
builder: (final context, final activeAccountSnapshot) { builder: (final context, final activeAccountSnapshot) {
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
@ -292,8 +289,8 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
builder: (final context, final capabilitiesSnapshot) { builder: (final context, final capabilitiesSnapshot) {
final appTheme = AppTheme( final appTheme = AppTheme(
capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming, capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming,
keepOriginalAccentColor: themeKeepOriginalAccentColor, keepOriginalAccentColor: options.themeKeepOriginalAccentColor.value,
oledAsDark: themeOLEDAsDark, oledAsDark: options.themeOLEDAsDark.value,
appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(), appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(),
neonTheme: widget.neonTheme, neonTheme: widget.neonTheme,
); );
@ -304,12 +301,10 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
...NeonLocalizations.localizationsDelegates, ...NeonLocalizations.localizationsDelegates,
], ],
supportedLocales: { supportedLocales: {
..._appImplementations ..._appImplementations.map((final app) => app.supportedLocales).expand((final element) => element),
.map((final app) => app.supportedLocales)
.expand((final element) => element),
...NeonLocalizations.supportedLocales, ...NeonLocalizations.supportedLocales,
}, },
themeMode: themeMode, themeMode: options.themeMode.value,
theme: appTheme.lightTheme, theme: appTheme.lightTheme,
darkTheme: appTheme.darkTheme, darkTheme: appTheme.darkTheme,
routerConfig: _routerDelegate, routerConfig: _routerDelegate,
@ -318,7 +313,5 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
); );
}, },
), ),
),
),
); );
} }

5
packages/neon/neon/lib/src/settings/models/options_collection.dart

@ -1,4 +1,4 @@
import 'package:meta/meta.dart'; import 'package:flutter/foundation.dart';
import 'package:neon/src/models/disposable.dart'; import 'package:neon/src/models/disposable.dart';
import 'package:neon/src/settings/models/exportable.dart'; import 'package:neon/src/settings/models/exportable.dart';
import 'package:neon/src/settings/models/option.dart'; import 'package:neon/src/settings/models/option.dart';
@ -17,6 +17,9 @@ abstract class OptionsCollection implements Exportable, Disposable {
@protected @protected
Iterable<Option<dynamic>> get options; Iterable<Option<dynamic>> get options;
/// Return a [Listenable] that triggers when any of the given [options] themselves trigger.
Listenable get listenable => Listenable.merge(options.toList());
/// Resets all [options]. /// Resets all [options].
/// ///
/// Implementers extending this must call super. /// Implementers extending this must call super.

71
packages/neon/neon/lib/src/widgets/options_collection_builder.dart

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:neon/src/settings/models/options_collection.dart';
/// A widget that rebuilds when one of the options in an [OptionsCollection] changes.
class OptionsCollectionBuilder<T extends OptionsCollection> extends StatefulWidget {
/// Creates a [OptionsCollectionBuilder].
///
/// The [valueListenable] and [builder] arguments must not be null.
/// The [child] is optional but is good practice to use if part of the widget
/// subtree does not depend on the value of the [valueListenable].
const OptionsCollectionBuilder({
required this.valueListenable,
required this.builder,
this.child,
super.key,
});
/// The [OptionsCollection] whose values you depend on in order to build.
final T valueListenable;
/// A [ValueWidgetBuilder] which builds a widget depending on the
/// [valueListenable]'s value.
///
/// Can incorporate a [valueListenable] value-independent widget subtree
/// from the [child] parameter into the returned widget tree.
///
/// Must not be null.
final ValueWidgetBuilder<T> builder;
/// A [valueListenable]-independent widget which is passed back to the [builder].
///
/// This argument is optional and can be null if the entire widget subtree the
/// [builder] builds depends on the value of the [valueListenable]. For
/// example, in the case where the [valueListenable] is a [String] and the
/// [builder] returns a [Text] widget with the current [String] value, there
/// would be no useful [child].
final Widget? child;
@override
State<StatefulWidget> createState() => _OptionsCollectionBuilderState<T>();
}
class _OptionsCollectionBuilderState<T extends OptionsCollection> extends State<OptionsCollectionBuilder<T>> {
@override
void initState() {
super.initState();
widget.valueListenable.listenable.addListener(_valueChanged);
}
@override
void didUpdateWidget(final OptionsCollectionBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable.listenable.removeListener(_valueChanged);
widget.valueListenable.listenable.addListener(_valueChanged);
}
}
@override
void dispose() {
widget.valueListenable.listenable.removeListener(_valueChanged);
super.dispose();
}
void _valueChanged() {
setState(() {});
}
@override
Widget build(final BuildContext context) => widget.builder(context, widget.valueListenable, widget.child);
}
Loading…
Cancel
Save