Browse Source

Merge pull request #1003 from nextcloud/feat/neon/options_collection_listenable

feat(neon): add optons collection builder
pull/1011/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
118a434704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 75
      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

75
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/provider.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/nextcloud.dart';
import 'package:quick_actions/quick_actions.dart';
@ -275,50 +276,42 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
}
@override
Widget build(final BuildContext context) => ValueListenableBuilder(
valueListenable: _globalOptions.themeMode,
builder: (final context, final themeMode, final _) => ValueListenableBuilder(
valueListenable: _globalOptions.themeOLEDAsDark,
builder: (final context, final themeOLEDAsDark, final _) => ValueListenableBuilder(
valueListenable: _globalOptions.themeKeepOriginalAccentColor,
builder: (final context, final themeKeepOriginalAccentColor, final _) => StreamBuilder<Account?>(
stream: _accountsBloc.activeAccount,
builder: (final context, final activeAccountSnapshot) {
FlutterNativeSplash.remove();
return ResultBuilder<core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data?>.behaviorSubject(
stream: activeAccountSnapshot.hasData
? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities
: null,
builder: (final context, final capabilitiesSnapshot) {
final appTheme = AppTheme(
capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming,
keepOriginalAccentColor: themeKeepOriginalAccentColor,
oledAsDark: themeOLEDAsDark,
appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(),
neonTheme: widget.neonTheme,
);
return MaterialApp.router(
localizationsDelegates: [
..._appImplementations.map((final app) => app.localizationsDelegate),
...NeonLocalizations.localizationsDelegates,
],
supportedLocales: {
..._appImplementations
.map((final app) => app.supportedLocales)
.expand((final element) => element),
...NeonLocalizations.supportedLocales,
},
themeMode: themeMode,
theme: appTheme.lightTheme,
darkTheme: appTheme.darkTheme,
routerConfig: _routerDelegate,
);
Widget build(final BuildContext context) => OptionsCollectionBuilder(
valueListenable: _globalOptions,
builder: (final context, final options, final _) => StreamBuilder<Account?>(
stream: _accountsBloc.activeAccount,
builder: (final context, final activeAccountSnapshot) {
FlutterNativeSplash.remove();
return ResultBuilder<core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data?>.behaviorSubject(
stream: activeAccountSnapshot.hasData
? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities
: null,
builder: (final context, final capabilitiesSnapshot) {
final appTheme = AppTheme(
capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming,
keepOriginalAccentColor: options.themeKeepOriginalAccentColor.value,
oledAsDark: options.themeOLEDAsDark.value,
appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(),
neonTheme: widget.neonTheme,
);
return MaterialApp.router(
localizationsDelegates: [
..._appImplementations.map((final app) => app.localizationsDelegate),
...NeonLocalizations.localizationsDelegates,
],
supportedLocales: {
..._appImplementations.map((final app) => app.supportedLocales).expand((final element) => element),
...NeonLocalizations.supportedLocales,
},
themeMode: options.themeMode.value,
theme: appTheme.lightTheme,
darkTheme: appTheme.darkTheme,
routerConfig: _routerDelegate,
);
},
),
),
);
},
),
);
}

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/settings/models/exportable.dart';
import 'package:neon/src/settings/models/option.dart';
@ -17,6 +17,9 @@ abstract class OptionsCollection implements Exportable, Disposable {
@protected
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].
///
/// 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