From 48e762cd760148e96408f91b24300fcb8776bc3c 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 | 9 ++- 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 | 25 ++++++++- .../neon/lib/src/utils/global_options.dart | 12 ++++ .../lib/src/widgets/custom_background.dart | 56 +++++++++++++++++++ 9 files changed, 114 insertions(+), 6 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 cfc0f28e..ecb46939 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", "globalOptionsThemeUseNextcloudTheme": "Use Nextcloud theme", + "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 b588aaf2..baf26033 100644 --- a/packages/neon/neon/lib/l10n/localizations.dart +++ b/packages/neon/neon/lib/l10n/localizations.dart @@ -515,6 +515,12 @@ abstract class NeonLocalizations { /// **'Use Nextcloud theme'** String get globalOptionsThemeUseNextcloudTheme; + /// 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 e8df967a..4daaa983 100644 --- a/packages/neon/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/neon/lib/l10n/localizations_en.dart @@ -254,6 +254,9 @@ class NeonLocalizationsEn extends NeonLocalizations { @override String get globalOptionsThemeUseNextcloudTheme => 'Use Nextcloud theme'; + @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 856eb204..c51d927a 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:neon/src/widgets/options_collection_builder.dart'; import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/nextcloud.dart'; @@ -297,12 +298,14 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra ? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities : null, builder: (final context, final capabilitiesSnapshot) { + final nextcloudTheme = capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming; final appTheme = AppTheme( - nextcloudTheme: capabilitiesSnapshot.data?.capabilities.themingPublicCapabilities?.theming, + nextcloudTheme: nextcloudTheme, useNextcloudTheme: options.themeUseNextcloudTheme.value, deviceThemeLight: deviceThemeLight, deviceThemeDark: deviceThemeDark, oledAsDark: options.themeOLEDAsDark.value, + customBackground: options.themeCustomBackground.value, appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(), neonTheme: widget.neonTheme, ); @@ -322,6 +325,10 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra theme: appTheme.lightTheme, darkTheme: appTheme.darkTheme, routerConfig: _routerDelegate, + builder: (final context, final child) => NeonCustomBackground( + theme: options.themeCustomBackground.value ? 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 c82cd0eb..d0f29928 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -183,10 +183,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 14e99757..f1986218 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -159,6 +159,9 @@ class _SettingsPageState extends State { ToggleSettingsTile( option: globalOptions.themeUseNextcloudTheme, ), + 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 5c7cee8e..39b2acf1 100644 --- a/packages/neon/neon/lib/src/theme/theme.dart +++ b/packages/neon/neon/lib/src/theme/theme.dart @@ -17,6 +17,7 @@ class AppTheme { required this.neonTheme, final bool useNextcloudTheme = false, this.oledAsDark = false, + this.customBackground = false, this.appThemes, }) : useNextcloudTheme = nextcloudTheme == null || useNextcloudTheme; @@ -35,6 +36,9 @@ class AppTheme { /// Whether to use [NcColors.oledBackground] in the dark theme. final bool oledAsDark; + /// Whether to use a custom background image provided by the Nextcloud server. + final bool customBackground; + /// The theme extensions provided by the `AppImplementation`s. final Iterable? appThemes; @@ -80,7 +84,7 @@ class AppTheme { ThemeData _getTheme(final Brightness brightness) { final colorScheme = _buildColorScheme(brightness); - return ThemeData( + final theme = ThemeData( useMaterial3: true, colorScheme: colorScheme, scaffoldBackgroundColor: colorScheme.background, @@ -95,6 +99,25 @@ class AppTheme { ...?appThemes, ], ); + + if (useNextcloudTheme && 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; } /// Returns a new theme for [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 e5d57616..19506a6b 100644 --- a/packages/neon/neon/lib/src/utils/global_options.dart +++ b/packages/neon/neon/lib/src/utils/global_options.dart @@ -73,6 +73,7 @@ class GlobalOptions extends OptionsCollection { themeMode, themeOLEDAsDark, themeUseNextcloudTheme, + themeCustomBackground, pushNotificationsEnabled, pushNotificationsDistributor, startupMinimized, @@ -160,6 +161,14 @@ class GlobalOptions extends OptionsCollection { defaultValue: true, ); + late final themeCustomBackground = ToggleOption.depend( + storage: storage, + key: GlobalOptionKeys.themeCustomBackground, + label: (final context) => NeonLocalizations.of(context).globalOptionsThemeCustomBackground, + defaultValue: false, + enabled: themeUseNextcloudTheme, + ); + /// Whether to enable the push notifications plugin. /// /// Setting this option to true will request the permission to show notifications. @@ -289,6 +298,9 @@ enum GlobalOptionKeys implements Storable { /// The storage key for [GlobalOptions.themeUseNextcloudTheme] themeUseNextcloudTheme._('theme-use-nextcloud-theme'), + /// The storage key for [GlobalOptions.themeCustomBackground] + themeCustomBackground._('theme-custom-background'), + /// The storage key for [GlobalOptions.pushNotificationsEnabled] pushNotificationsEnabled._('push-notifications-enabled'), 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 00000000..179138f1 --- /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!, + ], + ), + ); + } +}