From 1bca03c4d5a073f9ae7aac56a523d603875c7c22 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 6 Jul 2023 19:31:56 +0200 Subject: [PATCH] neon: allow apps to define custom themes --- packages/neon/neon/lib/src/app.dart | 22 ++-- .../lib/src/models/app_implementation.dart | 5 + packages/neon/neon/lib/src/utils/theme.dart | 101 ++++++++++++------ 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index 1bf5e11e..e143f6e2 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/packages/neon/neon/lib/src/app.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:neon/l10n/localizations.dart'; @@ -285,7 +286,13 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra ? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities : null, builder: (final context, final capabilitiesSnapshot) { - final nextcloudTheme = capabilitiesSnapshot.data?.capabilities.theming; + final appTheme = AppTheme( + capabilitiesSnapshot.data?.capabilities.theming, + keepOriginalAccentColor: themeKeepOriginalAccentColor, + oledAsDark: themeOLEDAsDark, + appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(), + ); + return MaterialApp.router( localizationsDelegates: [ ..._appImplementations.map((final app) => app.localizationsDelegate), @@ -298,17 +305,8 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra ...AppLocalizations.supportedLocales, }, themeMode: themeMode, - theme: getThemeFromNextcloudTheme( - nextcloudTheme, - Brightness.light, - keepOriginalAccentColor: nextcloudTheme == null || themeKeepOriginalAccentColor, - ), - darkTheme: getThemeFromNextcloudTheme( - nextcloudTheme, - Brightness.dark, - keepOriginalAccentColor: nextcloudTheme == null || themeKeepOriginalAccentColor, - oledAsDark: themeOLEDAsDark, - ), + theme: appTheme.lightTheme, + darkTheme: appTheme.darkTheme, routerConfig: _routerDelegate, ); }, diff --git a/packages/neon/neon/lib/src/models/app_implementation.dart b/packages/neon/neon/lib/src/models/app_implementation.dart index 71ae1bae..750c7b7f 100644 --- a/packages/neon/neon/lib/src/models/app_implementation.dart +++ b/packages/neon/neon/lib/src/models/app_implementation.dart @@ -86,6 +86,11 @@ abstract class AppImplementation void dispose() { options.dispose(); } + + /// A custom theme that will be injected into the widget tree. + /// + /// You can later acess it through `Theme.of(context).extension()`. + ThemeExtension? theme; } extension AppImplementationFind on Iterable { diff --git a/packages/neon/neon/lib/src/utils/theme.dart b/packages/neon/neon/lib/src/utils/theme.dart index 242f0a36..28e87a0d 100644 --- a/packages/neon/neon/lib/src/utils/theme.dart +++ b/packages/neon/neon/lib/src/utils/theme.dart @@ -7,40 +7,79 @@ import 'package:nextcloud/nextcloud.dart'; const themePrimaryColor = Color(0xFFF37736); @internal -ThemeData getThemeFromNextcloudTheme( - final CoreServerCapabilities_Ocs_Data_Capabilities_Theming? nextcloudTheme, - final Brightness brightness, { - required final bool keepOriginalAccentColor, - final bool oledAsDark = false, -}) { - if (oledAsDark) { - assert(brightness == Brightness.dark, 'Brightness.dark is required for oledAsDark.'); +@immutable +class AppTheme { + AppTheme( + this.nextcloudTheme, { + final bool keepOriginalAccentColor = false, + this.oledAsDark = false, + this.appThemes, + }) : keepOriginalAccentColor = nextcloudTheme == null || keepOriginalAccentColor; + + final CoreServerCapabilities_Ocs_Data_Capabilities_Theming? nextcloudTheme; + final bool keepOriginalAccentColor; + final bool oledAsDark; + final Iterable? appThemes; + + late final _primaryColor = nextcloudTheme?.color != null ? HexColor(nextcloudTheme!.color!) : themePrimaryColor; + late final _keepOriginalAccentColorOverride = keepOriginalAccentColor ? _primaryColor : null; + + ColorScheme _buildColorScheme(final Brightness brightness) { + final oledBackgroundOverride = oledAsDark && brightness == Brightness.dark ? Colors.black : null; + + return ColorScheme.fromSeed( + seedColor: _primaryColor, + brightness: brightness, + ).copyWith( + background: oledBackgroundOverride, + primary: _keepOriginalAccentColorOverride, + secondary: _keepOriginalAccentColorOverride, + ); + } + + ThemeData _getTheme(final Brightness brightness) { + final colorScheme = _buildColorScheme(brightness); + + return ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + scaffoldBackgroundColor: colorScheme.background, + cardColor: colorScheme.background, // For LicensePage + snackBarTheme: _snackBarTheme, + dividerTheme: _dividerTheme, + extensions: [ + const NeonTheme(), + ...?appThemes, + ], + ); } - final primaryColor = nextcloudTheme?.color != null ? HexColor(nextcloudTheme!.color!) : themePrimaryColor; - - final oledBackgroundOverride = oledAsDark ? Colors.black : null; - final keepOriginalAccentColorOverride = keepOriginalAccentColor ? primaryColor : null; - final colorScheme = ColorScheme.fromSeed( - seedColor: primaryColor, - brightness: brightness, - ).copyWith( - background: oledBackgroundOverride, - primary: keepOriginalAccentColorOverride, - secondary: keepOriginalAccentColorOverride, + ThemeData get lightTheme => _getTheme(Brightness.light); + ThemeData get darkTheme => _getTheme(Brightness.dark); + + static const _snackBarTheme = SnackBarThemeData( + behavior: SnackBarBehavior.floating, ); - return ThemeData( - useMaterial3: true, - colorScheme: colorScheme, - scaffoldBackgroundColor: colorScheme.background, - cardColor: colorScheme.background, // For LicensePage - snackBarTheme: const SnackBarThemeData( - behavior: SnackBarBehavior.floating, - ), - dividerTheme: const DividerThemeData( - thickness: 1.5, - space: 30, - ), + static const _dividerTheme = DividerThemeData( + thickness: 1.5, + space: 30, ); } + +@internal +@immutable +class NeonTheme extends ThemeExtension { + const NeonTheme(); + + @override + NeonTheme copyWith() => const NeonTheme(); + + @override + NeonTheme lerp(final NeonTheme? other, final double t) { + if (other is! NeonTheme) { + return this; + } + return const NeonTheme(); + } +}