From 715e707bb6d2ed82896b522f5421d20bdc0dd0b5 Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Sat, 9 Aug 2025 09:23:47 +0200 Subject: [PATCH] feat: Add on/off/blurred options to the background image (#442) Co-authored-by: PartyDonut --- lib/l10n/app_en.arb | 3 +- .../settings/client_settings_model.dart | 24 +++++++- .../client_settings_model.freezed.dart | 38 ++++++------ .../settings/client_settings_model.g.dart | 12 +++- .../client_settings_visual.dart | 20 ++++--- lib/screens/shared/detail_scaffold.dart | 59 ++++++++++--------- lib/screens/shared/nested_scaffold.dart | 7 ++- .../components/background_image.dart | 9 ++- 8 files changed, 109 insertions(+), 63 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 033e3a5..ec84166 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1316,5 +1316,6 @@ "type": "String" } } - } + }, + "blurred": "Blurred" } \ No newline at end of file diff --git a/lib/models/settings/client_settings_model.dart b/lib/models/settings/client_settings_model.dart index 404d85d..509c04d 100644 --- a/lib/models/settings/client_settings_model.dart +++ b/lib/models/settings/client_settings_model.dart @@ -28,6 +28,28 @@ enum GlobalHotKeys { } } +enum BackgroundType { + disabled, + enabled, + blurred; + + const BackgroundType(); + + double get opacityValues => switch (this) { + BackgroundType.disabled => 1.0, + BackgroundType.enabled => 0.7, + BackgroundType.blurred => 0.5, + }; + + String label(BuildContext context) { + return switch (this) { + BackgroundType.disabled => context.localized.off, + BackgroundType.enabled => context.localized.enabled, + BackgroundType.blurred => context.localized.blurred, + }; + } +} + @Freezed(copyWith: true) class ClientSettingsModel with _$ClientSettingsModel { const ClientSettingsModel._(); @@ -52,7 +74,7 @@ class ClientSettingsModel with _$ClientSettingsModel { @Default(false) bool showAllCollectionTypes, @Default(2) int maxConcurrentDownloads, @Default(DynamicSchemeVariant.rainbow) DynamicSchemeVariant schemeVariant, - @Default(true) bool backgroundPosters, + @Default(BackgroundType.blurred) BackgroundType backgroundImage, @Default(true) bool checkForUpdates, @Default(false) bool usePosterForLibrary, String? lastViewedUpdate, diff --git a/lib/models/settings/client_settings_model.freezed.dart b/lib/models/settings/client_settings_model.freezed.dart index e1dfe6f..bc0f182 100644 --- a/lib/models/settings/client_settings_model.freezed.dart +++ b/lib/models/settings/client_settings_model.freezed.dart @@ -40,7 +40,7 @@ mixin _$ClientSettingsModel { bool get showAllCollectionTypes => throw _privateConstructorUsedError; int get maxConcurrentDownloads => throw _privateConstructorUsedError; DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError; - bool get backgroundPosters => throw _privateConstructorUsedError; + BackgroundType get backgroundImage => throw _privateConstructorUsedError; bool get checkForUpdates => throw _privateConstructorUsedError; bool get usePosterForLibrary => throw _privateConstructorUsedError; String? get lastViewedUpdate => throw _privateConstructorUsedError; @@ -84,7 +84,7 @@ abstract class $ClientSettingsModelCopyWith<$Res> { bool showAllCollectionTypes, int maxConcurrentDownloads, DynamicSchemeVariant schemeVariant, - bool backgroundPosters, + BackgroundType backgroundImage, bool checkForUpdates, bool usePosterForLibrary, String? lastViewedUpdate, @@ -126,7 +126,7 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel> Object? showAllCollectionTypes = null, Object? maxConcurrentDownloads = null, Object? schemeVariant = null, - Object? backgroundPosters = null, + Object? backgroundImage = null, Object? checkForUpdates = null, Object? usePosterForLibrary = null, Object? lastViewedUpdate = freezed, @@ -210,10 +210,10 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel> ? _value.schemeVariant : schemeVariant // ignore: cast_nullable_to_non_nullable as DynamicSchemeVariant, - backgroundPosters: null == backgroundPosters - ? _value.backgroundPosters - : backgroundPosters // ignore: cast_nullable_to_non_nullable - as bool, + backgroundImage: null == backgroundImage + ? _value.backgroundImage + : backgroundImage // ignore: cast_nullable_to_non_nullable + as BackgroundType, checkForUpdates: null == checkForUpdates ? _value.checkForUpdates : checkForUpdates // ignore: cast_nullable_to_non_nullable @@ -266,7 +266,7 @@ abstract class _$$ClientSettingsModelImplCopyWith<$Res> bool showAllCollectionTypes, int maxConcurrentDownloads, DynamicSchemeVariant schemeVariant, - bool backgroundPosters, + BackgroundType backgroundImage, bool checkForUpdates, bool usePosterForLibrary, String? lastViewedUpdate, @@ -306,7 +306,7 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res> Object? showAllCollectionTypes = null, Object? maxConcurrentDownloads = null, Object? schemeVariant = null, - Object? backgroundPosters = null, + Object? backgroundImage = null, Object? checkForUpdates = null, Object? usePosterForLibrary = null, Object? lastViewedUpdate = freezed, @@ -390,10 +390,10 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res> ? _value.schemeVariant : schemeVariant // ignore: cast_nullable_to_non_nullable as DynamicSchemeVariant, - backgroundPosters: null == backgroundPosters - ? _value.backgroundPosters - : backgroundPosters // ignore: cast_nullable_to_non_nullable - as bool, + backgroundImage: null == backgroundImage + ? _value.backgroundImage + : backgroundImage // ignore: cast_nullable_to_non_nullable + as BackgroundType, checkForUpdates: null == checkForUpdates ? _value.checkForUpdates : checkForUpdates // ignore: cast_nullable_to_non_nullable @@ -442,7 +442,7 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel this.showAllCollectionTypes = false, this.maxConcurrentDownloads = 2, this.schemeVariant = DynamicSchemeVariant.rainbow, - this.backgroundPosters = true, + this.backgroundImage = BackgroundType.blurred, this.checkForUpdates = true, this.usePosterForLibrary = false, this.lastViewedUpdate, @@ -510,7 +510,7 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel final DynamicSchemeVariant schemeVariant; @override @JsonKey() - final bool backgroundPosters; + final BackgroundType backgroundImage; @override @JsonKey() final bool checkForUpdates; @@ -532,7 +532,7 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundPosters: $backgroundPosters, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; + return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundImage: $backgroundImage, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; } @override @@ -561,7 +561,7 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel ..add( DiagnosticsProperty('maxConcurrentDownloads', maxConcurrentDownloads)) ..add(DiagnosticsProperty('schemeVariant', schemeVariant)) - ..add(DiagnosticsProperty('backgroundPosters', backgroundPosters)) + ..add(DiagnosticsProperty('backgroundImage', backgroundImage)) ..add(DiagnosticsProperty('checkForUpdates', checkForUpdates)) ..add(DiagnosticsProperty('usePosterForLibrary', usePosterForLibrary)) ..add(DiagnosticsProperty('lastViewedUpdate', lastViewedUpdate)) @@ -607,7 +607,7 @@ abstract class _ClientSettingsModel extends ClientSettingsModel { final bool showAllCollectionTypes, final int maxConcurrentDownloads, final DynamicSchemeVariant schemeVariant, - final bool backgroundPosters, + final BackgroundType backgroundImage, final bool checkForUpdates, final bool usePosterForLibrary, final String? lastViewedUpdate, @@ -659,7 +659,7 @@ abstract class _ClientSettingsModel extends ClientSettingsModel { @override DynamicSchemeVariant get schemeVariant; @override - bool get backgroundPosters; + BackgroundType get backgroundImage; @override bool get checkForUpdates; @override diff --git a/lib/models/settings/client_settings_model.g.dart b/lib/models/settings/client_settings_model.g.dart index 484963f..8166712 100644 --- a/lib/models/settings/client_settings_model.g.dart +++ b/lib/models/settings/client_settings_model.g.dart @@ -41,7 +41,9 @@ _$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson( schemeVariant: $enumDecodeNullable( _$DynamicSchemeVariantEnumMap, json['schemeVariant']) ?? DynamicSchemeVariant.rainbow, - backgroundPosters: json['backgroundPosters'] as bool? ?? true, + backgroundImage: $enumDecodeNullable( + _$BackgroundTypeEnumMap, json['backgroundImage']) ?? + BackgroundType.blurred, checkForUpdates: json['checkForUpdates'] as bool? ?? true, usePosterForLibrary: json['usePosterForLibrary'] as bool? ?? false, lastViewedUpdate: json['lastViewedUpdate'] as String?, @@ -78,7 +80,7 @@ Map _$$ClientSettingsModelImplToJson( 'showAllCollectionTypes': instance.showAllCollectionTypes, 'maxConcurrentDownloads': instance.maxConcurrentDownloads, 'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!, - 'backgroundPosters': instance.backgroundPosters, + 'backgroundImage': _$BackgroundTypeEnumMap[instance.backgroundImage]!, 'checkForUpdates': instance.checkForUpdates, 'usePosterForLibrary': instance.usePosterForLibrary, 'lastViewedUpdate': instance.lastViewedUpdate, @@ -123,6 +125,12 @@ const _$DynamicSchemeVariantEnumMap = { DynamicSchemeVariant.fruitSalad: 'fruitSalad', }; +const _$BackgroundTypeEnumMap = { + BackgroundType.disabled: 'disabled', + BackgroundType.enabled: 'enabled', + BackgroundType.blurred: 'blurred', +}; + const _$GlobalHotKeysEnumMap = { GlobalHotKeys.search: 'search', GlobalHotKeys.exit: 'exit', diff --git a/lib/screens/settings/client_sections/client_settings_visual.dart b/lib/screens/settings/client_sections/client_settings_visual.dart index 64076fe..d799152 100644 --- a/lib/screens/settings/client_sections/client_settings_visual.dart +++ b/lib/screens/settings/client_sections/client_settings_visual.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/l10n/generated/app_localizations.dart'; +import 'package:fladder/models/settings/client_settings_model.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart'; import 'package:fladder/screens/settings/widgets/settings_label_divider.dart'; @@ -90,13 +91,18 @@ List buildClientSettingsVisual( SettingsListTile( label: Text(context.localized.enableBackgroundPostersTitle), subLabel: Text(context.localized.enableBackgroundPostersDesc), - onTap: () => ref - .read(clientSettingsProvider.notifier) - .update((cb) => cb.copyWith(backgroundPosters: !clientSettings.backgroundPosters)), - trailing: Switch( - value: clientSettings.backgroundPosters, - onChanged: (value) => - ref.read(clientSettingsProvider.notifier).update((cb) => cb.copyWith(backgroundPosters: value)), + trailing: EnumBox( + current: clientSettings.backgroundImage.label(context), + itemBuilder: (context) => BackgroundType.values + .map( + (e) => PopupMenuItem( + value: e, + child: Text(e.label(context)), + onTap: () => + ref.read(clientSettingsProvider.notifier).update((cb) => cb.copyWith(backgroundImage: e)), + ), + ) + .toList(), ), ), SettingsListTile( diff --git a/lib/screens/shared/detail_scaffold.dart b/lib/screens/shared/detail_scaffold.dart index 7adcbdb..f3a1e5c 100644 --- a/lib/screens/shared/detail_scaffold.dart +++ b/lib/screens/shared/detail_scaffold.dart @@ -96,34 +96,37 @@ class _DetailScaffoldState extends ConsumerState { if (backgroundImage != null) Align( alignment: Alignment.topCenter, - child: ShaderMask( - shaderCallback: (bounds) => LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.white, - Colors.white, - Colors.white, - Colors.white, - Colors.white, - Colors.white.withValues(alpha: 0), - ], - ).createShader(bounds), - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: double.infinity, - minHeight: minHeight - 20, - maxHeight: maxHeight.clamp(minHeight, 2500) - 20, - ), - child: FadeInImage( - placeholder: backgroundImage!.imageProvider, - placeholderColor: Colors.transparent, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - placeholderFit: BoxFit.cover, - excludeFromSemantics: true, - placeholderFilterQuality: FilterQuality.low, - image: backgroundImage!.imageProvider, + child: Padding( + padding: EdgeInsets.only(left: sideBarPadding), + child: ShaderMask( + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white, + Colors.white, + Colors.white, + Colors.white, + Colors.white, + Colors.white.withValues(alpha: 0), + ], + ).createShader(bounds), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, + minHeight: minHeight - 20, + maxHeight: maxHeight.clamp(minHeight, 2500) - 20, + ), + child: FadeInImage( + placeholder: backgroundImage!.imageProvider, + placeholderColor: Colors.transparent, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + placeholderFit: BoxFit.cover, + excludeFromSemantics: true, + placeholderFilterQuality: FilterQuality.low, + image: backgroundImage!.imageProvider, + ), ), ), ), diff --git a/lib/screens/shared/nested_scaffold.dart b/lib/screens/shared/nested_scaffold.dart index 62fded7..8e242bf 100644 --- a/lib/screens/shared/nested_scaffold.dart +++ b/lib/screens/shared/nested_scaffold.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fladder/providers/settings/client_settings_provider.dart'; + class NestedScaffold extends ConsumerWidget { final Widget body; final Widget? background; @@ -13,6 +15,7 @@ class NestedScaffold extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final backgroundOpacity = ref.watch(clientSettingsProvider.select((value) => value.backgroundImage.opacityValues)); return Stack( alignment: Alignment.bottomCenter, children: [ @@ -23,8 +26,8 @@ class NestedScaffold extends ConsumerWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Theme.of(context).colorScheme.surface.withValues(alpha: 0.85), - Theme.of(context).colorScheme.surface.withValues(alpha: 0.7), + Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity), + Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity - 0.15), ], ), ), diff --git a/lib/widgets/navigation_scaffold/components/background_image.dart b/lib/widgets/navigation_scaffold/components/background_image.dart index 3defeda..fafba22 100644 --- a/lib/widgets/navigation_scaffold/components/background_image.dart +++ b/lib/widgets/navigation_scaffold/components/background_image.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/images_models.dart'; +import 'package:fladder/models/settings/client_settings_model.dart'; import 'package:fladder/providers/api_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/util/fladder_image.dart'; @@ -36,7 +37,8 @@ class _BackgroundImageState extends ConsumerState { } void updateItems() { - final enabled = ref.read(clientSettingsProvider.select((value) => value.backgroundPosters)); + final enabled = + ref.read(clientSettingsProvider.select((value) => value.backgroundImage != BackgroundType.disabled)); WidgetsBinding.instance.addPostFrameCallback((value) async { if (!enabled && mounted) return; @@ -69,12 +71,13 @@ class _BackgroundImageState extends ConsumerState { @override Widget build(BuildContext context) { - final enabled = ref.watch(clientSettingsProvider.select((value) => value.backgroundPosters)); + final state = ref.watch(clientSettingsProvider.select((value) => value.backgroundImage)); + final enabled = state != BackgroundType.disabled; return enabled ? FladderImage( image: backgroundImage, fit: BoxFit.cover, - blurOnly: false, + blurOnly: state == BackgroundType.blurred, ) : const SizedBox.shrink(); }