From 6700edc653bcd933fe2fa944a302c6862bcdfdf6 Mon Sep 17 00:00:00 2001 From: jld3103 Date: Sat, 7 Oct 2023 14:53:18 +0200 Subject: [PATCH] feat(neon): Support custom backgrounds Signed-off-by: jld3103 --- packages/neon/neon/lib/l10n/en.arb | 1 + .../neon/neon/lib/l10n/localizations.dart | 6 ++ .../neon/neon/lib/l10n/localizations_en.dart | 3 + packages/neon/neon/lib/src/app.dart | 82 +++++++++++-------- packages/neon/neon/lib/src/pages/home.dart | 5 +- .../neon/neon/lib/src/pages/settings.dart | 3 + packages/neon/neon/lib/src/theme/theme.dart | 23 +++++- .../neon/lib/src/utils/global_options.dart | 9 ++ .../lib/src/widgets/custom_background.dart | 56 +++++++++++++ 9 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 packages/neon/neon/lib/src/widgets/custom_background.dart diff --git a/packages/neon/neon/lib/l10n/en.arb b/packages/neon/neon/lib/l10n/en.arb index d7aa46cdbee..a7cc0dec52f 100644 --- a/packages/neon/neon/lib/l10n/en.arb +++ b/packages/neon/neon/lib/l10n/en.arb @@ -132,6 +132,7 @@ "globalOptionsThemeModeAutomatic": "Automatic", "globalOptionsThemeOLEDAsDark": "OLED theme as dark theme", "globalOptionsThemeKeepOriginalAccentColor": "Keep the original accent color", + "globalOptionsThemeCustomBackground": "Custom background", "globalOptionsPushNotificationsEnabled": "Enabled", "globalOptionsPushNotificationsEnabledDisabledNotice": "No UnifiedPush distributor could be found or you denied the permission for showing notifications. Please go to the app settings and allow notifications and go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications", "globalOptionsPushNotificationsDistributor": "UnifiedPush Distributor", diff --git a/packages/neon/neon/lib/l10n/localizations.dart b/packages/neon/neon/lib/l10n/localizations.dart index d7f12d815a8..95eadd8212e 100644 --- a/packages/neon/neon/lib/l10n/localizations.dart +++ b/packages/neon/neon/lib/l10n/localizations.dart @@ -515,6 +515,12 @@ abstract class NeonLocalizations { /// **'Keep the original accent color'** String get globalOptionsThemeKeepOriginalAccentColor; + /// No description provided for @globalOptionsThemeCustomBackground. + /// + /// In en, this message translates to: + /// **'Custom background'** + String get globalOptionsThemeCustomBackground; + /// No description provided for @globalOptionsPushNotificationsEnabled. /// /// In en, this message translates to: diff --git a/packages/neon/neon/lib/l10n/localizations_en.dart b/packages/neon/neon/lib/l10n/localizations_en.dart index c26c2ddf8da..2415ba71120 100644 --- a/packages/neon/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/neon/lib/l10n/localizations_en.dart @@ -253,6 +253,9 @@ class NeonLocalizationsEn extends NeonLocalizations { @override String get globalOptionsThemeKeepOriginalAccentColor => 'Keep the original accent color'; + @override + String get globalOptionsThemeCustomBackground => 'Custom background'; + @override String get globalOptionsPushNotificationsEnabled => 'Enabled'; diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index 2281e150d4a..d11b26dc48e 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/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/custom_background.dart'; import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/nextcloud.dart'; import 'package:quick_actions/quick_actions.dart'; @@ -281,42 +282,51 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra valueListenable: _globalOptions.themeOLEDAsDark, builder: (final context, final themeOLEDAsDark, final _) => ValueListenableBuilder( valueListenable: _globalOptions.themeKeepOriginalAccentColor, - builder: (final context, final themeKeepOriginalAccentColor, final _) => StreamBuilder( - stream: _accountsBloc.activeAccount, - builder: (final context, final activeAccountSnapshot) { - FlutterNativeSplash.remove(); - return ResultBuilder.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, - ); - }, - ); - }, + builder: (final context, final themeKeepOriginalAccentColor, final _) => ValueListenableBuilder( + valueListenable: _globalOptions.themeCustomBackground, + builder: (final context, final themeCustomBackground, final _) => StreamBuilder( + stream: _accountsBloc.activeAccount, + builder: (final context, final activeAccountSnapshot) { + FlutterNativeSplash.remove(); + return ResultBuilder.behaviorSubject( + stream: activeAccountSnapshot.hasData + ? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities + : null, + builder: (final context, final capabilitiesSnapshot) { + final nextcloudTheme = capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming; + final appTheme = AppTheme( + nextcloudTheme, + keepOriginalAccentColor: themeKeepOriginalAccentColor, + oledAsDark: themeOLEDAsDark, + customBackground: themeCustomBackground, + 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, + builder: (final context, final child) => NeonCustomBackground( + theme: themeCustomBackground ? nextcloudTheme : null, + child: child, + ), + ); + }, + ); + }, + ), ), ), ), diff --git a/packages/neon/neon/lib/src/pages/home.dart b/packages/neon/neon/lib/src/pages/home.dart index 7826c8f616f..c58d6526e52 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -179,10 +179,7 @@ class _HomePageState extends State { if (drawerAlwaysVisible) { return Row( children: [ - ColoredBox( - color: Theme.of(context).colorScheme.background, - child: drawer, - ), + drawer, Expanded( child: body, ), diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index 7dc7185240d..4db97e3bdef 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -128,6 +128,9 @@ class _SettingsPageState extends State { ToggleSettingsTile( option: globalOptions.themeKeepOriginalAccentColor, ), + ToggleSettingsTile( + option: globalOptions.themeCustomBackground, + ), ], ), SettingsCategory( diff --git a/packages/neon/neon/lib/src/theme/theme.dart b/packages/neon/neon/lib/src/theme/theme.dart index a2692d61e75..4e89fa0ca89 100644 --- a/packages/neon/neon/lib/src/theme/theme.dart +++ b/packages/neon/neon/lib/src/theme/theme.dart @@ -13,12 +13,14 @@ class AppTheme { required this.neonTheme, final bool keepOriginalAccentColor = false, this.oledAsDark = false, + this.customBackground = false, this.appThemes, }) : keepOriginalAccentColor = nextcloudTheme == null || keepOriginalAccentColor; final core.ThemingPublicCapabilities_Theming? nextcloudTheme; final bool keepOriginalAccentColor; final bool oledAsDark; + final bool customBackground; final Iterable? appThemes; final NeonTheme neonTheme; @@ -40,7 +42,7 @@ class AppTheme { ThemeData _getTheme(final Brightness brightness) { final colorScheme = _buildColorScheme(brightness); - return ThemeData( + final theme = ThemeData( useMaterial3: true, colorScheme: colorScheme, scaffoldBackgroundColor: colorScheme.background, @@ -54,6 +56,25 @@ class AppTheme { ...?appThemes, ], ); + + if (customBackground) { + return theme.copyWith( + scaffoldBackgroundColor: Colors.transparent, + cardColor: Colors.transparent, + drawerTheme: const DrawerThemeData( + backgroundColor: Colors.transparent, + ), + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Colors.transparent, + elevation: 0, + ), + ); + } + + return theme; } ThemeData get lightTheme => _getTheme(Brightness.light); diff --git a/packages/neon/neon/lib/src/utils/global_options.dart b/packages/neon/neon/lib/src/utils/global_options.dart index c2d936991a7..cf8bbc29511 100644 --- a/packages/neon/neon/lib/src/utils/global_options.dart +++ b/packages/neon/neon/lib/src/utils/global_options.dart @@ -71,6 +71,7 @@ class GlobalOptions extends OptionsCollection { themeMode, themeOLEDAsDark, themeKeepOriginalAccentColor, + themeCustomBackground, pushNotificationsEnabled, pushNotificationsDistributor, startupMinimized, @@ -143,6 +144,13 @@ class GlobalOptions extends OptionsCollection { defaultValue: false, ); + late final themeCustomBackground = ToggleOption( + storage: storage, + key: GlobalOptionKeys.themeCustomBackground, + label: (final context) => NeonLocalizations.of(context).globalOptionsThemeCustomBackground, + defaultValue: false, + ); + late final pushNotificationsEnabled = ToggleOption( storage: storage, key: GlobalOptionKeys.pushNotificationsEnabled, @@ -224,6 +232,7 @@ enum GlobalOptionKeys implements Storable { themeMode._('theme-mode'), themeOLEDAsDark._('theme-oled-as-dark'), themeKeepOriginalAccentColor._('theme-keep-original-accent-color'), + themeCustomBackground._('theme-custom-background'), pushNotificationsEnabled._('push-notifications-enabled'), pushNotificationsDistributor._('push-notifications-distributor'), startupMinimized._('startup-minimized'), diff --git a/packages/neon/neon/lib/src/widgets/custom_background.dart b/packages/neon/neon/lib/src/widgets/custom_background.dart new file mode 100644 index 00000000000..179138f16d4 --- /dev/null +++ b/packages/neon/neon/lib/src/widgets/custom_background.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; +import 'package:neon/src/utils/hex_color.dart'; +import 'package:neon/src/widgets/image.dart'; +import 'package:nextcloud/core.dart' as core; + +@internal +class NeonCustomBackground extends StatelessWidget { + const NeonCustomBackground({ + required this.theme, + required this.child, + super.key, + }); + + final core.ThemingPublicCapabilities_Theming? theme; + final Widget? child; + + double _opacity(final BuildContext context) => Theme.of(context).brightness == Brightness.light ? 0.2 : 0.1; + + @override + Widget build(final BuildContext context) { + if (theme == null) { + return ColoredBox( + color: Theme.of(context).colorScheme.background, + child: child, + ); + } + + if (theme?.backgroundPlain ?? true) { + return ColoredBox( + color: + Color.lerp(HexColor(theme!.background), Theme.of(context).colorScheme.background, 1 - _opacity(context))!, + child: child, + ); + } + + final image = NeonUrlImage( + url: theme!.background, + fit: BoxFit.fill, + ); + return ColoredBox( + color: Theme.of(context).colorScheme.background, + child: Stack( + children: [ + Positioned.fill( + child: Opacity( + opacity: _opacity(context), + child: image, + ), + ), + if (child != null) child!, + ], + ), + ); + } +}