From 476bdc130e27e34ccdc4c79b2061499dd26802b0 Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Thu, 24 Oct 2024 23:02:10 +0200 Subject: [PATCH] feature: Improved banners, made banner settings easier to understand. (#71) Co-authored-by: PartyDonut --- l10n.yaml | 1 - lib/l10n/app_en.arb | 16 +- lib/l10n/l10n_errors.txt | 93 ---- lib/models/account_model.freezed.dart | 21 +- .../items/intro_skip_model.freezed.dart | 46 +- .../items/item_properties_model.freezed.dart | 17 +- .../items/trick_play_model.freezed.dart | 21 +- .../settings/client_settings_model.dart | 14 - .../client_settings_model.freezed.dart | 26 +- .../settings/client_settings_model.g.dart | 9 - lib/models/settings/home_settings_model.dart | 105 ++-- .../settings/home_settings_model.freezed.dart | 216 +++++++++ .../settings/home_settings_model.g.dart | 49 ++ lib/models/syncing/sync_item.freezed.dart | 21 +- .../syncing/sync_settings_model.freezed.dart | 17 +- .../session_info_provider.freezed.dart | 21 +- .../settings/home_settings_provider.dart | 3 +- lib/providers/shared_provider.dart | 10 +- lib/routes/auto_router.gr.dart | 15 +- lib/screens/dashboard/dashboard_screen.dart | 10 +- ...sters_row.dart => home_banner_widget.dart} | 17 +- .../settings/client_settings_page.dart | 48 +- .../shared/media/banner_play_button.dart | 51 ++ lib/screens/shared/media/carousel_banner.dart | 192 ++++---- lib/screens/shared/media/media_banner.dart | 449 ++++++++---------- lib/util/application_info.freezed.dart | 17 +- lib/util/fladder_image.dart | 8 +- lib/widgets/shared/fladder_carousel.dart | 60 ++- lib/widgets/shared/fladder_slider.dart | 9 +- 29 files changed, 916 insertions(+), 666 deletions(-) delete mode 100644 lib/l10n/l10n_errors.txt create mode 100644 lib/models/settings/home_settings_model.freezed.dart create mode 100644 lib/models/settings/home_settings_model.g.dart rename lib/screens/dashboard/{top_posters_row.dart => home_banner_widget.dart} (64%) create mode 100644 lib/screens/shared/media/banner_play_button.dart diff --git a/l10n.yaml b/l10n.yaml index 1d24c57..b1cc141 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -2,4 +2,3 @@ arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart nullable-getter: false -untranslated-messages-file: lib/l10n/l10n_errors.txt diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b0fa82f..022abcc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -116,8 +116,8 @@ "@dashboardContinueReading": {}, "dashboardContinueWatching": "Continue Watching", "@dashboardContinueWatching": {}, - "dashboardNextUp": "Next-up", - "@dashboardNextUp": {}, + "nextUp": "Next-up", + "@nextUp": {}, "dashboardRecentlyAdded": "Recently added in {name}", "@dashboardRecentlyAdded": { "description": "Recently added on home screen", @@ -437,8 +437,6 @@ "@navigationSync": {}, "never": "Never", "@never": {}, - "nextUp": "Next Up", - "@nextUp": {}, "noItemsSynced": "No items synced", "@noItemsSynced": {}, "noItemsToShow": "No items to show", @@ -723,14 +721,14 @@ "@settingsContinue": {}, "settingsEnableOsMediaControls": "Enable OS media controls", "@settingsEnableOsMediaControls": {}, - "settingsHomeBannerDescription": "Switch between a banner or scrollable carousel", + "settingsHomeBannerDescription": "Display as a slideshow, carousel, or hide the banner", "@settingsHomeBannerDescription": {}, "settingsHomeBannerTitle": "Home banner", "@settingsHomeBannerTitle": {}, - "settingsHomeCarouselDesc": "Shows a banner on the dashboard screen", - "@settingsHomeCarouselDesc": {}, - "settingsHomeCarouselTitle": "Dashboard banner", - "@settingsHomeCarouselTitle": {}, + "settingsHomeBannerInformationDesc": "Information to show in home banner", + "@settingsHomeBannerInformationDesc": {}, + "settingsHomeBannerInformationTitle": "Banner information", + "@settingsHomeBannerInformationTitle": {}, "settingsHomeNextUpDesc": "Type of posters shown in the dashboard screen", "@settingsHomeNextUpDesc": {}, "settingsHomeNextUpTitle": "Next-up posters", diff --git a/lib/l10n/l10n_errors.txt b/lib/l10n/l10n_errors.txt deleted file mode 100644 index 7d77aaa..0000000 --- a/lib/l10n/l10n_errors.txt +++ /dev/null @@ -1,93 +0,0 @@ -{ - "de": [ - "homeBannerBanner", - "homeBannerCarousel", - "masonry", - "mediaTypeBoxset", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle", - "settingsHomeNextUpDesc", - "settingsNextUpCutoffDays", - "settingsPlayerNativeLibassAccelDesc", - "settingsPlayerNativeLibassAccelTitle", - "settingsPosterPinch", - "settingsPosterSize", - "settingsShowScaleSlider", - "syncDeleteItemDesc", - "syncRemoveDataDesc", - "timeAndAnnotation", - "totalSize", - "unknown", - "useDefaults", - "videoScalingFillScreenNotif", - "videoScalingFillScreenTitle", - "videoScalingFitHeight", - "videoScalingFitWidth", - "videoScalingScaleDown", - "viewPhotos", - "writer" - ], - - "es": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "fr": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "ja": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "nb": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "nl": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "pt": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "pt_BR": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "uk": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ], - - "zh": [ - "homeBannerBanner", - "homeBannerCarousel", - "settingsHomeBannerDescription", - "settingsHomeBannerTitle" - ] -} diff --git a/lib/models/account_model.freezed.dart b/lib/models/account_model.freezed.dart index 2ec93fd..4ed2918 100644 --- a/lib/models/account_model.freezed.dart +++ b/lib/models/account_model.freezed.dart @@ -36,8 +36,12 @@ mixin _$AccountModel { ServerConfiguration? get serverConfiguration => throw _privateConstructorUsedError; + /// Serializes this AccountModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of AccountModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $AccountModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -74,6 +78,8 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of AccountModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -175,6 +181,8 @@ class __$$AccountModelImplCopyWithImpl<$Res> _$AccountModelImpl _value, $Res Function(_$AccountModelImpl) _then) : super(_value, _then); + /// Create a copy of AccountModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -365,7 +373,7 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { other.serverConfiguration == serverConfiguration)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -382,7 +390,9 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { policy, serverConfiguration); - @JsonKey(ignore: true) + /// Create a copy of AccountModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith => @@ -443,8 +453,11 @@ abstract class _AccountModel extends AccountModel { @override @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? get serverConfiguration; + + /// Create a copy of AccountModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/items/intro_skip_model.freezed.dart b/lib/models/items/intro_skip_model.freezed.dart index 41245bf..ed53eeb 100644 --- a/lib/models/items/intro_skip_model.freezed.dart +++ b/lib/models/items/intro_skip_model.freezed.dart @@ -23,8 +23,12 @@ mixin _$IntroOutSkipModel { IntroSkipModel? get intro => throw _privateConstructorUsedError; IntroSkipModel? get credits => throw _privateConstructorUsedError; + /// Serializes this IntroOutSkipModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $IntroOutSkipModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -51,6 +55,8 @@ class _$IntroOutSkipModelCopyWithImpl<$Res, $Val extends IntroOutSkipModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -69,6 +75,8 @@ class _$IntroOutSkipModelCopyWithImpl<$Res, $Val extends IntroOutSkipModel> ) as $Val); } + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $IntroSkipModelCopyWith<$Res>? get intro { @@ -81,6 +89,8 @@ class _$IntroOutSkipModelCopyWithImpl<$Res, $Val extends IntroOutSkipModel> }); } + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $IntroSkipModelCopyWith<$Res>? get credits { @@ -118,6 +128,8 @@ class __$$IntroOutSkipModelImplCopyWithImpl<$Res> $Res Function(_$IntroOutSkipModelImpl) _then) : super(_value, _then); + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -164,11 +176,13 @@ class _$IntroOutSkipModelImpl extends _IntroOutSkipModel { (identical(other.credits, credits) || other.credits == credits)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, intro, credits); - @JsonKey(ignore: true) + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith => @@ -196,8 +210,11 @@ abstract class _IntroOutSkipModel extends IntroOutSkipModel { IntroSkipModel? get intro; @override IntroSkipModel? get credits; + + /// Create a copy of IntroOutSkipModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith => throw _privateConstructorUsedError; } @@ -233,8 +250,12 @@ mixin _$IntroSkipModel { toJson: _durationToMilliseconds) Duration get hideTime => throw _privateConstructorUsedError; + /// Serializes this IntroSkipModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of IntroSkipModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $IntroSkipModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -280,6 +301,8 @@ class _$IntroSkipModelCopyWithImpl<$Res, $Val extends IntroSkipModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of IntroSkipModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -360,6 +383,8 @@ class __$$IntroSkipModelImplCopyWithImpl<$Res> _$IntroSkipModelImpl _value, $Res Function(_$IntroSkipModelImpl) _then) : super(_value, _then); + /// Create a copy of IntroSkipModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -480,12 +505,14 @@ class _$IntroSkipModelImpl implements _IntroSkipModel { other.hideTime == hideTime)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, valid, start, end, showTime, hideTime); - @JsonKey(ignore: true) + /// Create a copy of IntroSkipModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith => @@ -558,8 +585,11 @@ abstract class _IntroSkipModel implements IntroSkipModel { fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds) Duration get hideTime; + + /// Create a copy of IntroSkipModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/items/item_properties_model.freezed.dart b/lib/models/items/item_properties_model.freezed.dart index 016f309..6531a37 100644 --- a/lib/models/items/item_properties_model.freezed.dart +++ b/lib/models/items/item_properties_model.freezed.dart @@ -19,7 +19,9 @@ mixin _$ItemPropertiesModel { bool get canDelete => throw _privateConstructorUsedError; bool get canDownload => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of ItemPropertiesModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ItemPropertiesModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -43,6 +45,8 @@ class _$ItemPropertiesModelCopyWithImpl<$Res, $Val extends ItemPropertiesModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ItemPropertiesModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -81,6 +85,8 @@ class __$$ItemPropertiesModelImplCopyWithImpl<$Res> $Res Function(_$ItemPropertiesModelImpl) _then) : super(_value, _then); + /// Create a copy of ItemPropertiesModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -131,7 +137,9 @@ class _$ItemPropertiesModelImpl extends _ItemPropertiesModel { @override int get hashCode => Object.hash(runtimeType, canDelete, canDownload); - @JsonKey(ignore: true) + /// Create a copy of ItemPropertiesModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith => @@ -149,8 +157,11 @@ abstract class _ItemPropertiesModel extends ItemPropertiesModel { bool get canDelete; @override bool get canDownload; + + /// Create a copy of ItemPropertiesModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/items/trick_play_model.freezed.dart b/lib/models/items/trick_play_model.freezed.dart index b376eef..8e5fe57 100644 --- a/lib/models/items/trick_play_model.freezed.dart +++ b/lib/models/items/trick_play_model.freezed.dart @@ -28,8 +28,12 @@ mixin _$TrickPlayModel { Duration get interval => throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; + /// Serializes this TrickPlayModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of TrickPlayModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $TrickPlayModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -60,6 +64,8 @@ class _$TrickPlayModelCopyWithImpl<$Res, $Val extends TrickPlayModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of TrickPlayModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -130,6 +136,8 @@ class __$$TrickPlayModelImplCopyWithImpl<$Res> _$TrickPlayModelImpl _value, $Res Function(_$TrickPlayModelImpl) _then) : super(_value, _then); + /// Create a copy of TrickPlayModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -235,7 +243,7 @@ class _$TrickPlayModelImpl extends _TrickPlayModel { const DeepCollectionEquality().equals(other._images, _images)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -247,7 +255,9 @@ class _$TrickPlayModelImpl extends _TrickPlayModel { interval, const DeepCollectionEquality().hash(_images)); - @JsonKey(ignore: true) + /// Create a copy of TrickPlayModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith => @@ -290,8 +300,11 @@ abstract class _TrickPlayModel extends TrickPlayModel { Duration get interval; @override List get images; + + /// Create a copy of TrickPlayModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/settings/client_settings_model.dart b/lib/models/settings/client_settings_model.dart index 89b5242..cdde03c 100644 --- a/lib/models/settings/client_settings_model.dart +++ b/lib/models/settings/client_settings_model.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fladder/util/custom_color_themes.dart'; -import 'package:fladder/util/localization_helper.dart'; part 'client_settings_model.freezed.dart'; part 'client_settings_model.g.dart'; @@ -23,7 +22,6 @@ class ClientSettingsModel with _$ClientSettingsModel { Duration? nextUpDateCutoff, @Default(ThemeMode.system) ThemeMode themeMode, ColorThemes? themeColor, - @Default(HomeBanner.carousel) HomeBanner homeBanner, @Default(false) bool amoledBlack, @Default(false) bool blurPlaceHolders, @Default(false) bool blurUpcomingEpisodes, @@ -73,18 +71,6 @@ class LocaleConvert implements JsonConverter { } } -enum HomeBanner { - carousel, - banner; - - const HomeBanner(); - - String label(BuildContext context) => switch (this) { - HomeBanner.carousel => context.localized.homeBannerCarousel, - HomeBanner.banner => context.localized.homeBannerBanner, - }; -} - class Vector2 { final double x; final double y; diff --git a/lib/models/settings/client_settings_model.freezed.dart b/lib/models/settings/client_settings_model.freezed.dart index 4d52c01..4036e9f 100644 --- a/lib/models/settings/client_settings_model.freezed.dart +++ b/lib/models/settings/client_settings_model.freezed.dart @@ -27,7 +27,6 @@ mixin _$ClientSettingsModel { Duration? get nextUpDateCutoff => throw _privateConstructorUsedError; ThemeMode get themeMode => throw _privateConstructorUsedError; ColorThemes? get themeColor => throw _privateConstructorUsedError; - HomeBanner get homeBanner => throw _privateConstructorUsedError; bool get amoledBlack => throw _privateConstructorUsedError; bool get blurPlaceHolders => throw _privateConstructorUsedError; bool get blurUpcomingEpisodes => throw _privateConstructorUsedError; @@ -63,7 +62,6 @@ abstract class $ClientSettingsModelCopyWith<$Res> { Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, - HomeBanner homeBanner, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -97,7 +95,6 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel> Object? nextUpDateCutoff = freezed, Object? themeMode = null, Object? themeColor = freezed, - Object? homeBanner = null, Object? amoledBlack = null, Object? blurPlaceHolders = null, Object? blurUpcomingEpisodes = null, @@ -137,10 +134,6 @@ class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel> ? _value.themeColor : themeColor // ignore: cast_nullable_to_non_nullable as ColorThemes?, - homeBanner: null == homeBanner - ? _value.homeBanner - : homeBanner // ignore: cast_nullable_to_non_nullable - as HomeBanner, amoledBlack: null == amoledBlack ? _value.amoledBlack : amoledBlack // ignore: cast_nullable_to_non_nullable @@ -197,7 +190,6 @@ abstract class _$$ClientSettingsModelImplCopyWith<$Res> Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, - HomeBanner homeBanner, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -229,7 +221,6 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res> Object? nextUpDateCutoff = freezed, Object? themeMode = null, Object? themeColor = freezed, - Object? homeBanner = null, Object? amoledBlack = null, Object? blurPlaceHolders = null, Object? blurUpcomingEpisodes = null, @@ -269,10 +260,6 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res> ? _value.themeColor : themeColor // ignore: cast_nullable_to_non_nullable as ColorThemes?, - homeBanner: null == homeBanner - ? _value.homeBanner - : homeBanner // ignore: cast_nullable_to_non_nullable - as HomeBanner, amoledBlack: null == amoledBlack ? _value.amoledBlack : amoledBlack // ignore: cast_nullable_to_non_nullable @@ -325,7 +312,6 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel this.nextUpDateCutoff, this.themeMode = ThemeMode.system, this.themeColor, - this.homeBanner = HomeBanner.carousel, this.amoledBlack = false, this.blurPlaceHolders = false, this.blurUpcomingEpisodes = false, @@ -360,9 +346,6 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel final ColorThemes? themeColor; @override @JsonKey() - final HomeBanner homeBanner; - @override - @JsonKey() final bool amoledBlack; @override @JsonKey() @@ -390,7 +373,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, homeBanner: $homeBanner, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, libraryPageSize: $libraryPageSize)'; + 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, libraryPageSize: $libraryPageSize)'; } @override @@ -405,7 +388,6 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel ..add(DiagnosticsProperty('nextUpDateCutoff', nextUpDateCutoff)) ..add(DiagnosticsProperty('themeMode', themeMode)) ..add(DiagnosticsProperty('themeColor', themeColor)) - ..add(DiagnosticsProperty('homeBanner', homeBanner)) ..add(DiagnosticsProperty('amoledBlack', amoledBlack)) ..add(DiagnosticsProperty('blurPlaceHolders', blurPlaceHolders)) ..add(DiagnosticsProperty('blurUpcomingEpisodes', blurUpcomingEpisodes)) @@ -434,8 +416,6 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel other.themeMode == themeMode) && (identical(other.themeColor, themeColor) || other.themeColor == themeColor) && - (identical(other.homeBanner, homeBanner) || - other.homeBanner == homeBanner) && (identical(other.amoledBlack, amoledBlack) || other.amoledBlack == amoledBlack) && (identical(other.blurPlaceHolders, blurPlaceHolders) || @@ -467,7 +447,6 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel nextUpDateCutoff, themeMode, themeColor, - homeBanner, amoledBlack, blurPlaceHolders, blurUpcomingEpisodes, @@ -504,7 +483,6 @@ abstract class _ClientSettingsModel extends ClientSettingsModel { final Duration? nextUpDateCutoff, final ThemeMode themeMode, final ColorThemes? themeColor, - final HomeBanner homeBanner, final bool amoledBlack, final bool blurPlaceHolders, final bool blurUpcomingEpisodes, @@ -534,8 +512,6 @@ abstract class _ClientSettingsModel extends ClientSettingsModel { @override ColorThemes? get themeColor; @override - HomeBanner get homeBanner; - @override bool get amoledBlack; @override bool get blurPlaceHolders; diff --git a/lib/models/settings/client_settings_model.g.dart b/lib/models/settings/client_settings_model.g.dart index 5b8a0d2..4b71fd5 100644 --- a/lib/models/settings/client_settings_model.g.dart +++ b/lib/models/settings/client_settings_model.g.dart @@ -25,9 +25,6 @@ _$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson( themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? ThemeMode.system, themeColor: $enumDecodeNullable(_$ColorThemesEnumMap, json['themeColor']), - homeBanner: - $enumDecodeNullable(_$HomeBannerEnumMap, json['homeBanner']) ?? - HomeBanner.carousel, amoledBlack: json['amoledBlack'] as bool? ?? false, blurPlaceHolders: json['blurPlaceHolders'] as bool? ?? false, blurUpcomingEpisodes: json['blurUpcomingEpisodes'] as bool? ?? false, @@ -50,7 +47,6 @@ Map _$$ClientSettingsModelImplToJson( 'nextUpDateCutoff': instance.nextUpDateCutoff?.inMicroseconds, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeColor': _$ColorThemesEnumMap[instance.themeColor], - 'homeBanner': _$HomeBannerEnumMap[instance.homeBanner]!, 'amoledBlack': instance.amoledBlack, 'blurPlaceHolders': instance.blurPlaceHolders, 'blurUpcomingEpisodes': instance.blurUpcomingEpisodes, @@ -85,8 +81,3 @@ const _$ColorThemesEnumMap = { ColorThemes.deepPurple: 'deepPurple', ColorThemes.blueGrey: 'blueGrey', }; - -const _$HomeBannerEnumMap = { - HomeBanner.carousel: 'carousel', - HomeBanner.banner: 'banner', -}; diff --git a/lib/models/settings/home_settings_model.dart b/lib/models/settings/home_settings_model.dart index dd2b8b2..1ab5f31 100644 --- a/lib/models/settings/home_settings_model.dart +++ b/lib/models/settings/home_settings_model.dart @@ -1,11 +1,38 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:fladder/util/localization_helper.dart'; import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'package:fladder/util/localization_helper.dart'; + +part 'home_settings_model.freezed.dart'; +part 'home_settings_model.g.dart'; + +@freezed +class HomeSettingsModel with _$HomeSettingsModel { + factory HomeSettingsModel({ + @Default(HomeBanner.carousel) HomeBanner homeBanner, + @Default(HomeCarouselSettings.combined) HomeCarouselSettings carouselSettings, + @Default(HomeNextUp.separate) HomeNextUp nextUp, + }) = _HomeSettingsModel; + + factory HomeSettingsModel.fromJson(Map json) => _$HomeSettingsModelFromJson(json); +} + +enum HomeBanner { + hide, + carousel, + banner; + + const HomeBanner(); + + String label(BuildContext context) => switch (this) { + HomeBanner.hide => context.localized.hide, + HomeBanner.carousel => context.localized.homeBannerCarousel, + HomeBanner.banner => context.localized.homeBannerBanner, + }; +} + enum HomeCarouselSettings { - off, nextUp, cont, combined, @@ -14,20 +41,10 @@ enum HomeCarouselSettings { const HomeCarouselSettings(); String label(BuildContext context) => switch (this) { - HomeCarouselSettings.off => context.localized.hide, HomeCarouselSettings.nextUp => context.localized.nextUp, HomeCarouselSettings.cont => context.localized.settingsContinue, HomeCarouselSettings.combined => context.localized.combined, }; - - String toMap() { - return toString(); - } - - static HomeCarouselSettings fromMap(String value) { - return HomeCarouselSettings.values.firstWhereOrNull((element) => element.name == value) ?? - HomeCarouselSettings.combined; - } } enum HomeNextUp { @@ -47,62 +64,4 @@ enum HomeNextUp { HomeNextUp.combined => context.localized.combined, HomeNextUp.separate => context.localized.separate, }; - - String toMap() { - return toString(); - } - - static HomeNextUp fromMap(String value) { - return HomeNextUp.values.firstWhereOrNull((element) => element.name == value) ?? HomeNextUp.separate; - } -} - -class HomeSettingsModel { - final HomeCarouselSettings carouselSettings; - final HomeNextUp nextUp; - HomeSettingsModel({ - this.carouselSettings = HomeCarouselSettings.combined, - this.nextUp = HomeNextUp.separate, - }); - - HomeSettingsModel copyWith({ - HomeCarouselSettings? carouselSettings, - HomeNextUp? nextUp, - }) { - return HomeSettingsModel( - carouselSettings: carouselSettings ?? this.carouselSettings, - nextUp: nextUp ?? this.nextUp, - ); - } - - Map toMap() { - return { - 'carouselSettings': carouselSettings.toMap(), - 'nextUp': nextUp.toMap(), - }; - } - - factory HomeSettingsModel.fromMap(Map map) { - return HomeSettingsModel( - carouselSettings: HomeCarouselSettings.fromMap(map['carouselSettings']), - nextUp: HomeNextUp.fromMap(map['nextUp']), - ); - } - - String toJson() => json.encode(toMap()); - - factory HomeSettingsModel.fromJson(String source) => HomeSettingsModel.fromMap(json.decode(source)); - - @override - String toString() => 'HomeSettingsModel(carouselSettings: $carouselSettings, nextUp: $nextUp)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is HomeSettingsModel && other.carouselSettings == carouselSettings && other.nextUp == nextUp; - } - - @override - int get hashCode => carouselSettings.hashCode ^ nextUp.hashCode; } diff --git a/lib/models/settings/home_settings_model.freezed.dart b/lib/models/settings/home_settings_model.freezed.dart new file mode 100644 index 0000000..264f33d --- /dev/null +++ b/lib/models/settings/home_settings_model.freezed.dart @@ -0,0 +1,216 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'home_settings_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +HomeSettingsModel _$HomeSettingsModelFromJson(Map json) { + return _HomeSettingsModel.fromJson(json); +} + +/// @nodoc +mixin _$HomeSettingsModel { + HomeBanner get homeBanner => throw _privateConstructorUsedError; + HomeCarouselSettings get carouselSettings => + throw _privateConstructorUsedError; + HomeNextUp get nextUp => throw _privateConstructorUsedError; + + /// Serializes this HomeSettingsModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of HomeSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $HomeSettingsModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HomeSettingsModelCopyWith<$Res> { + factory $HomeSettingsModelCopyWith( + HomeSettingsModel value, $Res Function(HomeSettingsModel) then) = + _$HomeSettingsModelCopyWithImpl<$Res, HomeSettingsModel>; + @useResult + $Res call( + {HomeBanner homeBanner, + HomeCarouselSettings carouselSettings, + HomeNextUp nextUp}); +} + +/// @nodoc +class _$HomeSettingsModelCopyWithImpl<$Res, $Val extends HomeSettingsModel> + implements $HomeSettingsModelCopyWith<$Res> { + _$HomeSettingsModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of HomeSettingsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? homeBanner = null, + Object? carouselSettings = null, + Object? nextUp = null, + }) { + return _then(_value.copyWith( + homeBanner: null == homeBanner + ? _value.homeBanner + : homeBanner // ignore: cast_nullable_to_non_nullable + as HomeBanner, + carouselSettings: null == carouselSettings + ? _value.carouselSettings + : carouselSettings // ignore: cast_nullable_to_non_nullable + as HomeCarouselSettings, + nextUp: null == nextUp + ? _value.nextUp + : nextUp // ignore: cast_nullable_to_non_nullable + as HomeNextUp, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$HomeSettingsModelImplCopyWith<$Res> + implements $HomeSettingsModelCopyWith<$Res> { + factory _$$HomeSettingsModelImplCopyWith(_$HomeSettingsModelImpl value, + $Res Function(_$HomeSettingsModelImpl) then) = + __$$HomeSettingsModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {HomeBanner homeBanner, + HomeCarouselSettings carouselSettings, + HomeNextUp nextUp}); +} + +/// @nodoc +class __$$HomeSettingsModelImplCopyWithImpl<$Res> + extends _$HomeSettingsModelCopyWithImpl<$Res, _$HomeSettingsModelImpl> + implements _$$HomeSettingsModelImplCopyWith<$Res> { + __$$HomeSettingsModelImplCopyWithImpl(_$HomeSettingsModelImpl _value, + $Res Function(_$HomeSettingsModelImpl) _then) + : super(_value, _then); + + /// Create a copy of HomeSettingsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? homeBanner = null, + Object? carouselSettings = null, + Object? nextUp = null, + }) { + return _then(_$HomeSettingsModelImpl( + homeBanner: null == homeBanner + ? _value.homeBanner + : homeBanner // ignore: cast_nullable_to_non_nullable + as HomeBanner, + carouselSettings: null == carouselSettings + ? _value.carouselSettings + : carouselSettings // ignore: cast_nullable_to_non_nullable + as HomeCarouselSettings, + nextUp: null == nextUp + ? _value.nextUp + : nextUp // ignore: cast_nullable_to_non_nullable + as HomeNextUp, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$HomeSettingsModelImpl implements _HomeSettingsModel { + _$HomeSettingsModelImpl( + {this.homeBanner = HomeBanner.carousel, + this.carouselSettings = HomeCarouselSettings.combined, + this.nextUp = HomeNextUp.separate}); + + factory _$HomeSettingsModelImpl.fromJson(Map json) => + _$$HomeSettingsModelImplFromJson(json); + + @override + @JsonKey() + final HomeBanner homeBanner; + @override + @JsonKey() + final HomeCarouselSettings carouselSettings; + @override + @JsonKey() + final HomeNextUp nextUp; + + @override + String toString() { + return 'HomeSettingsModel(homeBanner: $homeBanner, carouselSettings: $carouselSettings, nextUp: $nextUp)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HomeSettingsModelImpl && + (identical(other.homeBanner, homeBanner) || + other.homeBanner == homeBanner) && + (identical(other.carouselSettings, carouselSettings) || + other.carouselSettings == carouselSettings) && + (identical(other.nextUp, nextUp) || other.nextUp == nextUp)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, homeBanner, carouselSettings, nextUp); + + /// Create a copy of HomeSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$HomeSettingsModelImplCopyWith<_$HomeSettingsModelImpl> get copyWith => + __$$HomeSettingsModelImplCopyWithImpl<_$HomeSettingsModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$HomeSettingsModelImplToJson( + this, + ); + } +} + +abstract class _HomeSettingsModel implements HomeSettingsModel { + factory _HomeSettingsModel( + {final HomeBanner homeBanner, + final HomeCarouselSettings carouselSettings, + final HomeNextUp nextUp}) = _$HomeSettingsModelImpl; + + factory _HomeSettingsModel.fromJson(Map json) = + _$HomeSettingsModelImpl.fromJson; + + @override + HomeBanner get homeBanner; + @override + HomeCarouselSettings get carouselSettings; + @override + HomeNextUp get nextUp; + + /// Create a copy of HomeSettingsModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$HomeSettingsModelImplCopyWith<_$HomeSettingsModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/settings/home_settings_model.g.dart b/lib/models/settings/home_settings_model.g.dart new file mode 100644 index 0000000..63e1f49 --- /dev/null +++ b/lib/models/settings/home_settings_model.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'home_settings_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$HomeSettingsModelImpl _$$HomeSettingsModelImplFromJson( + Map json) => + _$HomeSettingsModelImpl( + homeBanner: + $enumDecodeNullable(_$HomeBannerEnumMap, json['homeBanner']) ?? + HomeBanner.carousel, + carouselSettings: $enumDecodeNullable( + _$HomeCarouselSettingsEnumMap, json['carouselSettings']) ?? + HomeCarouselSettings.combined, + nextUp: $enumDecodeNullable(_$HomeNextUpEnumMap, json['nextUp']) ?? + HomeNextUp.separate, + ); + +Map _$$HomeSettingsModelImplToJson( + _$HomeSettingsModelImpl instance) => + { + 'homeBanner': _$HomeBannerEnumMap[instance.homeBanner]!, + 'carouselSettings': + _$HomeCarouselSettingsEnumMap[instance.carouselSettings]!, + 'nextUp': _$HomeNextUpEnumMap[instance.nextUp]!, + }; + +const _$HomeBannerEnumMap = { + HomeBanner.hide: 'hide', + HomeBanner.carousel: 'carousel', + HomeBanner.banner: 'banner', +}; + +const _$HomeCarouselSettingsEnumMap = { + HomeCarouselSettings.nextUp: 'nextUp', + HomeCarouselSettings.cont: 'cont', + HomeCarouselSettings.combined: 'combined', +}; + +const _$HomeNextUpEnumMap = { + HomeNextUp.off: 'off', + HomeNextUp.nextUp: 'nextUp', + HomeNextUp.cont: 'cont', + HomeNextUp.combined: 'combined', + HomeNextUp.separate: 'separate', +}; diff --git a/lib/models/syncing/sync_item.freezed.dart b/lib/models/syncing/sync_item.freezed.dart index 46ca151..d6e7593 100644 --- a/lib/models/syncing/sync_item.freezed.dart +++ b/lib/models/syncing/sync_item.freezed.dart @@ -33,7 +33,9 @@ mixin _$SyncedItem { @UserDataJsonSerializer() UserData? get userData => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SyncedItemCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -74,6 +76,8 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -152,6 +156,8 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem> ) as $Val); } + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel { @@ -164,6 +170,8 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem> }); } + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $TrickPlayModelCopyWith<$Res>? get fTrickPlayModel { @@ -215,6 +223,8 @@ class __$$SyncItemImplCopyWithImpl<$Res> _$SyncItemImpl _value, $Res Function(_$SyncItemImpl) _then) : super(_value, _then); + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -415,7 +425,9 @@ class _$SyncItemImpl extends _SyncItem { const DeepCollectionEquality().hash(_subtitles), userData); - @JsonKey(ignore: true) + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith => @@ -469,8 +481,11 @@ abstract class _SyncItem extends SyncedItem { @override @UserDataJsonSerializer() UserData? get userData; + + /// Create a copy of SyncedItem + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/syncing/sync_settings_model.freezed.dart b/lib/models/syncing/sync_settings_model.freezed.dart index 2806646..cf1e3af 100644 --- a/lib/models/syncing/sync_settings_model.freezed.dart +++ b/lib/models/syncing/sync_settings_model.freezed.dart @@ -18,7 +18,9 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$SyncSettingsModel { List get items => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of SyncSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SyncSettingsModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -42,6 +44,8 @@ class _$SyncSettingsModelCopyWithImpl<$Res, $Val extends SyncSettingsModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SyncSettingsModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -75,6 +79,8 @@ class __$$SyncSettignsModelImplCopyWithImpl<$Res> $Res Function(_$SyncSettignsModelImpl) _then) : super(_value, _then); + /// Create a copy of SyncSettingsModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -122,7 +128,9 @@ class _$SyncSettignsModelImpl extends _SyncSettignsModel { int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_items)); - @JsonKey(ignore: true) + /// Create a copy of SyncSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith => @@ -137,8 +145,11 @@ abstract class _SyncSettignsModel extends SyncSettingsModel { @override List get items; + + /// Create a copy of SyncSettingsModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/providers/session_info_provider.freezed.dart b/lib/providers/session_info_provider.freezed.dart index 702406e..67dada9 100644 --- a/lib/providers/session_info_provider.freezed.dart +++ b/lib/providers/session_info_provider.freezed.dart @@ -23,8 +23,12 @@ mixin _$SessionInfoModel { String? get playbackModel => throw _privateConstructorUsedError; TranscodingInfo? get transCodeInfo => throw _privateConstructorUsedError; + /// Serializes this SessionInfoModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SessionInfoModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SessionInfoModelCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -48,6 +52,8 @@ class _$SessionInfoModelCopyWithImpl<$Res, $Val extends SessionInfoModel> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SessionInfoModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -86,6 +92,8 @@ class __$$SessionInfoModelImplCopyWithImpl<$Res> $Res Function(_$SessionInfoModelImpl) _then) : super(_value, _then); + /// Create a copy of SessionInfoModel + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -134,11 +142,13 @@ class _$SessionInfoModelImpl extends _SessionInfoModel { other.transCodeInfo == transCodeInfo)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, playbackModel, transCodeInfo); - @JsonKey(ignore: true) + /// Create a copy of SessionInfoModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SessionInfoModelImplCopyWith<_$SessionInfoModelImpl> get copyWith => @@ -166,8 +176,11 @@ abstract class _SessionInfoModel extends SessionInfoModel { String? get playbackModel; @override TranscodingInfo? get transCodeInfo; + + /// Create a copy of SessionInfoModel + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SessionInfoModelImplCopyWith<_$SessionInfoModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/providers/settings/home_settings_provider.dart b/lib/providers/settings/home_settings_provider.dart index 67d5e1f..4b9bf17 100644 --- a/lib/providers/settings/home_settings_provider.dart +++ b/lib/providers/settings/home_settings_provider.dart @@ -1,6 +1,7 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + import 'package:fladder/models/settings/home_settings_model.dart'; import 'package:fladder/providers/shared_provider.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; final homeSettingsProvider = StateNotifierProvider((ref) { return HomeSettingsNotifier(ref); diff --git a/lib/providers/shared_provider.dart b/lib/providers/shared_provider.dart index f025a3f..f53998a 100644 --- a/lib/providers/shared_provider.dart +++ b/lib/providers/shared_provider.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/settings/client_settings_model.dart'; import 'package:fladder/models/settings/home_settings_model.dart'; @@ -15,8 +18,6 @@ import 'package:fladder/providers/settings/photo_view_settings_provider.dart'; import 'package:fladder/providers/settings/subtitle_settings_provider.dart'; import 'package:fladder/providers/settings/video_player_settings_provider.dart'; import 'package:fladder/providers/user_provider.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; final sharedPreferencesProvider = Provider((ref) { throw UnimplementedError(); @@ -116,14 +117,15 @@ class SharedUtility { HomeSettingsModel get homeSettings { try { - return HomeSettingsModel.fromJson(sharedPreferences.getString(_homeSettingsKey) ?? ""); + return HomeSettingsModel.fromJson(jsonDecode(sharedPreferences.getString(_homeSettingsKey) ?? "")); } catch (e) { log(e.toString()); return HomeSettingsModel(); } } - set homeSettings(HomeSettingsModel settings) => sharedPreferences.setString(_homeSettingsKey, settings.toJson()); + set homeSettings(HomeSettingsModel settings) => + sharedPreferences.setString(_homeSettingsKey, jsonEncode(settings.toJson())); BookViewerSettingsModel get bookViewSettings { try { diff --git a/lib/routes/auto_router.gr.dart b/lib/routes/auto_router.gr.dart index b8475c5..edb1ef4 100644 --- a/lib/routes/auto_router.gr.dart +++ b/lib/routes/auto_router.gr.dart @@ -27,7 +27,8 @@ import 'package:fladder/screens/settings/security_settings_page.dart' as _i10; import 'package:fladder/screens/settings/settings_screen.dart' as _i11; import 'package:fladder/screens/splash_screen.dart' as _i12; import 'package:fladder/screens/syncing/synced_screen.dart' as _i13; -import 'package:flutter/material.dart' as _i16; +import 'package:flutter/foundation.dart' as _i16; +import 'package:flutter/material.dart' as _i19; /// generated route for /// [_i1.ClientSettingsPage] @@ -355,7 +356,7 @@ class SettingsRoute extends _i14.PageRouteInfo { class SplashRoute extends _i14.PageRouteInfo { SplashRoute({ dynamic Function(bool)? loggedIn, - _i16.Key? key, + _i19.Key? key, List<_i14.PageRouteInfo>? children, }) : super( SplashRoute.name, @@ -389,7 +390,7 @@ class SplashRouteArgs { final dynamic Function(bool)? loggedIn; - final _i16.Key? key; + final _i19.Key? key; @override String toString() { @@ -401,8 +402,8 @@ class SplashRouteArgs { /// [_i13.SyncedScreen] class SyncedRoute extends _i14.PageRouteInfo { SyncedRoute({ - _i16.ScrollController? navigationScrollController, - _i16.Key? key, + _i19.ScrollController? navigationScrollController, + _i19.Key? key, List<_i14.PageRouteInfo>? children, }) : super( SyncedRoute.name, @@ -434,9 +435,9 @@ class SyncedRouteArgs { this.key, }); - final _i16.ScrollController? navigationScrollController; + final _i19.ScrollController? navigationScrollController; - final _i16.Key? key; + final _i19.Key? key; @override String toString() { diff --git a/lib/screens/dashboard/dashboard_screen.dart b/lib/screens/dashboard/dashboard_screen.dart index f190bbc..927d66e 100644 --- a/lib/screens/dashboard/dashboard_screen.dart +++ b/lib/screens/dashboard/dashboard_screen.dart @@ -16,7 +16,7 @@ import 'package:fladder/providers/settings/home_settings_provider.dart'; import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/views_provider.dart'; import 'package:fladder/routes/auto_router.gr.dart'; -import 'package:fladder/screens/dashboard/top_posters_row.dart'; +import 'package:fladder/screens/dashboard/home_banner_widget.dart'; import 'package:fladder/screens/shared/media/poster_row.dart'; import 'package:fladder/screens/shared/nested_scaffold.dart'; import 'package:fladder/screens/shared/nested_sliver_appbar.dart'; @@ -69,6 +69,7 @@ class _DashboardScreenState extends ConsumerState { final dashboardData = ref.watch(dashboardProvider); final views = ref.watch(viewsProvider); final homeSettings = ref.watch(homeSettingsProvider); + final homeBanner = ref.watch(homeSettingsProvider.select((value) => value.homeBanner)) != HomeBanner.hide; final resumeVideo = dashboardData.resumeVideo; final resumeAudio = dashboardData.resumeAudio; final resumeBooks = dashboardData.resumeBooks; @@ -79,7 +80,6 @@ class _DashboardScreenState extends ConsumerState { HomeCarouselSettings.nextUp => dashboardData.nextUp, HomeCarouselSettings.combined => [...allResume, ...dashboardData.nextUp], HomeCarouselSettings.cont => allResume, - _ => [...allResume, ...dashboardData.nextUp], }; return MediaQuery.removeViewInsets( @@ -100,11 +100,11 @@ class _DashboardScreenState extends ConsumerState { route: LibrarySearchRoute(), parent: context, ), - if (homeSettings.carouselSettings != HomeCarouselSettings.off && homeCarouselItems.isNotEmpty) ...{ + if (homeBanner && homeCarouselItems.isNotEmpty) ...{ SliverToBoxAdapter( child: Transform.translate( offset: Offset(0, AdaptiveLayout.layoutOf(context) == LayoutState.phone ? -14 : 0), - child: TopPostersRow(posters: homeCarouselItems), + child: HomeBannerWidget(posters: homeCarouselItems), ), ), } else if (AdaptiveLayout.of(context).isDesktop) @@ -147,7 +147,7 @@ class _DashboardScreenState extends ConsumerState { (homeSettings.nextUp == HomeNextUp.nextUp || homeSettings.nextUp == HomeNextUp.separate)) SliverToBoxAdapter( child: PosterRow( - label: context.localized.dashboardNextUp, + label: context.localized.nextUp, posters: dashboardData.nextUp, ), ), diff --git a/lib/screens/dashboard/top_posters_row.dart b/lib/screens/dashboard/home_banner_widget.dart similarity index 64% rename from lib/screens/dashboard/top_posters_row.dart rename to lib/screens/dashboard/home_banner_widget.dart index 0147543..c793db7 100644 --- a/lib/screens/dashboard/top_posters_row.dart +++ b/lib/screens/dashboard/home_banner_widget.dart @@ -3,19 +3,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; -import 'package:fladder/models/settings/client_settings_model.dart'; -import 'package:fladder/providers/settings/client_settings_provider.dart'; +import 'package:fladder/models/settings/home_settings_model.dart'; +import 'package:fladder/providers/settings/home_settings_provider.dart'; import 'package:fladder/screens/shared/media/carousel_banner.dart'; import 'package:fladder/screens/shared/media/media_banner.dart'; -class TopPostersRow extends ConsumerWidget { +class HomeBannerWidget extends ConsumerWidget { final List posters; - const TopPostersRow({required this.posters, super.key}); + const HomeBannerWidget({required this.posters, super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final bannerType = ref.watch(clientSettingsProvider.select((value) => value.homeBanner)); - final maxHeight = (MediaQuery.sizeOf(context).shortestSide * 0.6).clamp(125.0, 350.0); + final bannerType = ref.watch(homeSettingsProvider.select((value) => value.homeBanner)); + final maxHeight = (MediaQuery.sizeOf(context).shortestSide * 0.6).clamp(125.0, 375.0); return switch (bannerType) { HomeBanner.carousel => Column( mainAxisSize: MainAxisSize.min, @@ -24,13 +24,14 @@ class TopPostersRow extends ConsumerWidget { items: posters, maxHeight: maxHeight, ), - const SizedBox(height: 8) + const SizedBox(height: 24) ], ), HomeBanner.banner => MediaBanner( items: posters, maxHeight: maxHeight, - ) + ), + _ => const SizedBox.shrink(), }; } } diff --git a/lib/screens/settings/client_settings_page.dart b/lib/screens/settings/client_settings_page.dart index 0d28557..f8db21a 100644 --- a/lib/screens/settings/client_settings_page.dart +++ b/lib/screens/settings/client_settings_page.dart @@ -7,7 +7,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fladder/models/settings/client_settings_model.dart'; import 'package:fladder/models/settings/home_settings_model.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/home_settings_provider.dart'; @@ -164,34 +163,12 @@ class _ClientSettingsPageState extends ConsumerState { ), const Divider(), SettingsLabelDivider(label: context.localized.dashboard), - SettingsListTile( - label: Text(context.localized.settingsHomeCarouselTitle), - subLabel: Text(context.localized.settingsHomeCarouselDesc), - trailing: EnumBox( - current: ref.watch( - homeSettingsProvider.select( - (value) => value.carouselSettings.label(context), - ), - ), - itemBuilder: (context) => HomeCarouselSettings.values - .map( - (entry) => PopupMenuItem( - value: entry, - child: Text(entry.label(context)), - onTap: () => ref - .read(homeSettingsProvider.notifier) - .update((context) => context.copyWith(carouselSettings: entry)), - ), - ) - .toList(), - ), - ), SettingsListTile( label: Text(context.localized.settingsHomeBannerTitle), subLabel: Text(context.localized.settingsHomeBannerDescription), trailing: EnumBox( current: ref.watch( - clientSettingsProvider.select( + homeSettingsProvider.select( (value) => value.homeBanner.label(context), ), ), @@ -201,13 +178,34 @@ class _ClientSettingsPageState extends ConsumerState { value: entry, child: Text(entry.label(context)), onTap: () => ref - .read(clientSettingsProvider.notifier) + .read(homeSettingsProvider.notifier) .update((context) => context.copyWith(homeBanner: entry)), ), ) .toList(), ), ), + if (ref.watch(homeSettingsProvider.select((value) => value.homeBanner)) != HomeBanner.hide) + SettingsListTile( + label: Text(context.localized.settingsHomeBannerInformationTitle), + subLabel: Text(context.localized.settingsHomeBannerInformationDesc), + trailing: EnumBox( + current: ref.watch( + homeSettingsProvider.select((value) => value.carouselSettings.label(context)), + ), + itemBuilder: (context) => HomeCarouselSettings.values + .map( + (entry) => PopupMenuItem( + value: entry, + child: Text(entry.label(context)), + onTap: () => ref + .read(homeSettingsProvider.notifier) + .update((context) => context.copyWith(carouselSettings: entry)), + ), + ) + .toList(), + ), + ), SettingsListTile( label: Text(context.localized.settingsHomeNextUpTitle), subLabel: Text(context.localized.settingsHomeNextUpDesc), diff --git a/lib/screens/shared/media/banner_play_button.dart b/lib/screens/shared/media/banner_play_button.dart new file mode 100644 index 0000000..513527c --- /dev/null +++ b/lib/screens/shared/media/banner_play_button.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:ficonsax/ficonsax.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:square_progress_indicator/square_progress_indicator.dart'; + +import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/util/item_base_model/play_item_helpers.dart'; + +class BannerPlayButton extends ConsumerWidget { + final ItemBaseModel item; + const BannerPlayButton({ + super.key, + required this.item, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Align( + alignment: Alignment.bottomRight, + child: Opacity( + opacity: 0.9, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Stack( + children: [ + Positioned.fill( + child: SquareProgressIndicator( + value: item.userData.progress / 100, + borderRadius: 12, + strokeCap: StrokeCap.round, + color: Theme.of(context).colorScheme.primary, + ), + ), + IconButton( + onPressed: () => item.play(context, ref), + icon: const Icon( + IconsaxBold.play, + size: 30, + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/shared/media/carousel_banner.dart b/lib/screens/shared/media/carousel_banner.dart index 41876e0..51f4d42 100644 --- a/lib/screens/shared/media/carousel_banner.dart +++ b/lib/screens/shared/media/carousel_banner.dart @@ -4,10 +4,12 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/screens/shared/media/banner_play_button.dart'; import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/item_base_model/item_base_model_extensions.dart'; import 'package:fladder/util/list_padding.dart'; +import 'package:fladder/util/themes_data.dart'; import 'package:fladder/widgets/shared/fladder_carousel.dart'; import 'package:fladder/widgets/shared/item_actions.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; @@ -34,107 +36,119 @@ class _CarouselBannerState extends ConsumerState { constraints: BoxConstraints(maxHeight: widget.maxHeight), child: LayoutBuilder( builder: (context, constraints) { - final maxExtent = (constraints.maxHeight * 2.1).clamp(250.0, MediaQuery.sizeOf(context).shortestSide * 0.75); + final maxExtent = (constraints.maxHeight * 2.1).clamp( + 250.0, + (MediaQuery.sizeOf(context).shortestSide * 0.75).clamp(251.0, double.maxFinite), + ); final border = BorderRadius.circular(18); return FladderCarousel( + elevation: 3, + shrinkExtent: 0, shape: RoundedRectangleBorder(borderRadius: border), - onTap: (index) => widget.items[index].navigateTo(context), - onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer - ? null - : (index) { - final poster = widget.items[index]; - showBottomSheetPill( - context: context, - item: poster, - content: (scrollContext, scrollController) => ListView( - shrinkWrap: true, - controller: scrollController, - children: poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true), - ), - ); - }, - onSecondaryTap: AdaptiveLayout.of(context).inputDevice == InputDevice.touch - ? null - : (details) async { - Offset localPosition = details.$2.globalPosition; - RelativeRect position = RelativeRect.fromLTRB( - localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy); - final poster = widget.items[details.$1]; - - await showMenu( - context: context, - position: position, - items: poster.generateActions(context, ref).popupMenuItems(useIcons: true), - ); - }, - itemExtent: maxExtent, + itemPadding: + const EdgeInsets.symmetric(horizontal: 4).copyWith(top: AdaptiveLayout.of(context).isDesktop ? 6 : 10), + padding: const EdgeInsets.symmetric(horizontal: 6), + itemExtent: widget.items.length == 1 ? MediaQuery.sizeOf(context).width : maxExtent, children: [ ...widget.items.mapIndexed( - (index, e) => LayoutBuilder(builder: (context, constraints) { + (index, item) => LayoutBuilder(builder: (context, constraints) { final opacity = (constraints.maxWidth / maxExtent); - return Stack( - clipBehavior: Clip.none, - children: [ - FladderImage(image: e.bannerImage), - AnimatedOpacity( - duration: const Duration(milliseconds: 250), - opacity: opacity.clamp(0, 1), - child: Stack( - children: [ - Positioned.fill( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomLeft, - end: Alignment.topCenter, - colors: [ - Theme.of(context).colorScheme.primaryContainer.withOpacity(0.75), - Colors.transparent, - ], + return GestureDetector( + onTap: () => widget.items[index].navigateTo(context), + onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer + ? null + : () { + final poster = widget.items[index]; + showBottomSheetPill( + context: context, + item: poster, + content: (scrollContext, scrollController) => ListView( + shrinkWrap: true, + controller: scrollController, + children: + poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true), + ), + ); + }, + onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch + ? null + : (details) async { + Offset localPosition = details.globalPosition; + RelativeRect position = RelativeRect.fromLTRB( + localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy); + final poster = widget.items[index]; + + await showMenu( + context: context, + position: position, + items: poster.generateActions(context, ref).popupMenuItems(useIcons: true), + ); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + FladderImage(image: item.bannerImage), + Opacity( + opacity: opacity.clamp(0, 1), + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomLeft, + end: Alignment.topCenter, + colors: [ + ThemesData.of(context).dark.colorScheme.primaryContainer.withOpacity(0.85), + Colors.transparent, + ], + ), ), ), ), - ), - ], - ), - ), - Align( - alignment: Alignment.bottomLeft, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - e.title, - maxLines: 2, - softWrap: e.title.length > 25, - textWidthBasis: TextWidthBasis.parent, - overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white), - ), - if (e.label(context) != null) - Text( - e.label(context)!, - maxLines: 2, - softWrap: false, - overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white), - ), - ].addInBetween(const SizedBox(height: 4)), + ], ), ), - ), - Container( - decoration: BoxDecoration( - border: Border.all( - color: Colors.white.withOpacity(0.1), - width: 1.0, + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.all(16.0).copyWith(right: constraints.maxWidth * 0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + maxLines: 2, + softWrap: item.title.length > 25, + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white), + ), + if (item.label(context) != null || item.subText != null) + Text( + item.label(context) ?? item.subText ?? "", + maxLines: 2, + softWrap: false, + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white), + ), + ].addInBetween(const SizedBox(height: 4)), ), - borderRadius: border), - ), - ], + ), + ), + BannerPlayButton(item: widget.items[index]), + IgnorePointer( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 1.0, + ), + borderRadius: border), + ), + ), + ], + ), ); }), ) diff --git a/lib/screens/shared/media/media_banner.dart b/lib/screens/shared/media/media_banner.dart index 62c80dc..be12f19 100644 --- a/lib/screens/shared/media/media_banner.dart +++ b/lib/screens/shared/media/media_banner.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; import 'package:async/async.dart'; -import 'package:collection/collection.dart'; import 'package:ficonsax/ficonsax.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; -import 'package:fladder/models/items/movie_model.dart'; +import 'package:fladder/screens/shared/media/banner_play_button.dart'; import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/item_base_model/item_base_model_extensions.dart'; import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/themes_data.dart'; import 'package:fladder/widgets/shared/fladder_carousel.dart'; +import 'package:fladder/widgets/shared/fladder_slider.dart'; import 'package:fladder/widgets/shared/item_actions.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; @@ -84,269 +84,211 @@ class _MediaBannerState extends ConsumerState { @override Widget build(BuildContext context) { - final overlayColor = ThemesData.of(context).dark.colorScheme.onSecondary; + final overlayColor = ThemesData.of(context).dark.colorScheme.primaryContainer; final shadows = [ BoxShadow(blurRadius: 12, spreadRadius: 8, color: overlayColor), ]; final currentItem = widget.items[currentPage.clamp(0, widget.items.length - 1)]; - final actions = currentItem.generateActions(context, ref); final double dragOpacity = (1 - dragOffset.abs()).clamp(0, 1); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ConstrainedBox( - constraints: BoxConstraints(maxHeight: widget.maxHeight), - child: Card( - elevation: 16, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), - surfaceTintColor: overlayColor, - color: overlayColor, - child: GestureDetector( - onTap: () => currentItem.navigateTo(context), - onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.touch - ? () async { - interacting = true; - final poster = currentItem; - showBottomSheetPill( - context: context, - item: poster, - content: (scrollContext, scrollController) => ListView( - shrinkWrap: true, - controller: scrollController, - children: poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true), - ), - ); - interacting = false; - timer.reset(); - } - : null, + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxHeight: widget.maxHeight), + child: Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + surfaceTintColor: overlayColor, + color: overlayColor, child: MouseRegion( - onEnter: (event) => setState(() => showControls = true), - onHover: (event) => timer.reset(), - onExit: (event) => setState(() => showControls = false), - child: Stack( - fit: StackFit.expand, - children: [ - Dismissible( - key: const Key("Dismissable"), - direction: DismissDirection.horizontal, - onUpdate: (details) { - setState(() { - dragOffset = details.progress * 4; - }); - }, - confirmDismiss: (direction) async { - if (direction == DismissDirection.startToEnd) { - previousSlide(); - } else { - nextSlide(); + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => currentItem.navigateTo(context), + onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.touch + ? () async { + interacting = true; + final poster = currentItem; + showBottomSheetPill( + context: context, + item: poster, + content: (scrollContext, scrollController) => ListView( + shrinkWrap: true, + controller: scrollController, + children: + poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true), + ), + ); + interacting = false; + timer.reset(); } - return false; - }, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 125), - opacity: dragOpacity.abs(), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 125), - child: Container( - key: Key(currentItem.id), - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - ), - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: Colors.white.withOpacity(0.10), strokeAlign: BorderSide.strokeAlignInside), - gradient: LinearGradient( - begin: Alignment.bottomLeft, - end: Alignment.topCenter, - colors: [ - overlayColor.withOpacity(1), - overlayColor.withOpacity(0.75), - overlayColor.withOpacity(0.45), - overlayColor.withOpacity(0.15), - overlayColor.withOpacity(0), - overlayColor.withOpacity(0), - overlayColor.withOpacity(0.1), - ], - ), - ), - child: SizedBox( - width: double.infinity, - height: double.infinity, - child: Padding( - padding: const EdgeInsets.all(1), - child: FladderImage( - fit: BoxFit.cover, - image: currentItem.bannerImage, - ), - ), - ), - ), - ), - ), - ), - Row( + : null, + onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch + ? null + : (details) async { + Offset localPosition = details.globalPosition; + RelativeRect position = RelativeRect.fromLTRB( + localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy); + final poster = currentItem; + + await showMenu( + context: context, + position: position, + items: poster.generateActions(context, ref).popupMenuItems(useIcons: true), + ); + }, + child: MouseRegion( + onEnter: (event) => setState(() => showControls = true), + onHover: (event) => timer.reset(), + onExit: (event) => setState(() => showControls = false), + child: Stack( + fit: StackFit.expand, children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: IgnorePointer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - currentItem.title, - maxLines: 2, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - shadows: shadows, - color: Colors.white, - ), - ), - ), - if (currentItem.label(context) != null && currentItem is! MovieModel) - Flexible( - child: Text( - currentItem.label(context)!, - maxLines: 2, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - shadows: shadows, - color: Colors.white.withOpacity(0.75), - ), - ), - ), - if (currentItem.overview.summary.isNotEmpty && - AdaptiveLayout.layoutOf(context) != LayoutState.phone) - Flexible( - child: Text( - currentItem.overview.summary, - maxLines: 2, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - shadows: shadows, - color: Colors.white.withOpacity(0.75), - ), - ), - ), - ].addInBetween(const SizedBox(height: 6)), + Dismissible( + key: const Key("Dismissable"), + direction: DismissDirection.horizontal, + onUpdate: (details) { + setState(() { + dragOffset = details.progress * 4; + }); + }, + confirmDismiss: (direction) async { + if (direction == DismissDirection.startToEnd) { + previousSlide(); + } else { + nextSlide(); + } + return false; + }, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 125), + opacity: dragOpacity.abs(), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 125), + child: Container( + key: Key(currentItem.id), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: Colors.white.withOpacity(0.10), strokeAlign: BorderSide.strokeAlignInside), + gradient: LinearGradient( + begin: Alignment.bottomLeft, + end: Alignment.topCenter, + colors: [ + overlayColor.withOpacity(0.85), + Colors.transparent, + ], + ), + ), + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: Padding( + padding: const EdgeInsets.all(1), + child: FladderImage( + fit: BoxFit.cover, + image: currentItem.bannerImage, ), ), ), - ].addInBetween(const SizedBox(height: 16)), + ), ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: AnimatedOpacity( - opacity: showControls ? 1 : 0, - duration: const Duration(milliseconds: 250), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton.filledTonal( - onPressed: () => nextSlide(), - icon: const Icon(IconsaxOutline.arrow_right_3), - ) - ], + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: IgnorePointer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + currentItem.title, + maxLines: 2, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + shadows: shadows, + color: Colors.white, + ), + ), + ), + if (currentItem.label(context) != null || currentItem.subText != null) + Flexible( + child: Text( + currentItem.label(context) ?? currentItem.subText ?? "", + maxLines: 2, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + shadows: shadows, + color: Colors.white.withOpacity(0.75), + ), + ), + ), + ].addInBetween(const SizedBox(height: 6)), + ), + ), + ), + ].addInBetween(const SizedBox(height: 16)), + ), + ), ), - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: AnimatedOpacity( + opacity: showControls ? 1 : 0, + duration: const Duration(milliseconds: 250), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton.filledTonal( + onPressed: () => nextSlide(), + icon: const Icon(IconsaxOutline.arrow_right_3), + ) + ], + ), + ), + ), + ], ), + BannerPlayButton(item: currentItem), ], ), - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.all(16), - child: Card( - child: PopupMenuButton( - onOpened: () => interacting = true, - onCanceled: () { - interacting = false; - timer.reset(); - }, - itemBuilder: (context) => actions.popupMenuItems(useIcons: true), - ), - ), - ), - ), - ], + ), ), ), ), ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - final delta = (details.primaryDelta ?? 0) / 20; - slidePosition += delta; - if (slidePosition > 1) { - nextSlide(); - slidePosition = 0; - } else if (slidePosition < -1) { - previousSlide(); - slidePosition = 0; - } - }, - onHorizontalDragStart: (details) { - slidePosition = 0; - }, - child: Container( - color: Colors.black.withOpacity(0), - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - runAlignment: WrapAlignment.center, - children: widget.items.mapIndexed((index, e) { - return Tooltip( - message: '${e.name}\n${e.detailedName}', - child: Card( - elevation: 0, - color: Colors.transparent, - child: InkWell( - onTapUp: currentPage == index - ? null - : (details) { - animateToTarget(index); - timer.reset(); - }, - child: Container( - alignment: Alignment.center, - color: Colors.red.withOpacity(0), - width: 28, - height: 28, - child: AnimatedContainer( - duration: const Duration(milliseconds: 125), - width: currentItem == e ? 22 : 6, - height: currentItem == e ? 10 : 6, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: currentItem == e - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.primary.withOpacity(0.25), - ), - ), - ), - ), - ), - ); - }).toList(), + if (widget.items.length > 1) + FractionallySizedBox( + widthFactor: 0.35, + child: FladderSlider( + value: currentPage.toDouble(), + min: 0, + animation: const Duration(milliseconds: 250), + thumbWidth: 24, + activeTrackColor: Theme.of(context).colorScheme.surfaceDim, + inactiveTrackColor: Theme.of(context).colorScheme.surfaceDim, + max: widget.items.length.toDouble() - 1, + onChanged: (value) => setState(() => currentPage = value.toInt()), ), - ), - ), - ) - ], + ) + else + const SizedBox(height: 24) + ], + ), ); } @@ -368,3 +310,28 @@ class _MediaBannerState extends ConsumerState { updateItem(currentPage + step); } } + +class RoundedTrackShape extends RoundedRectSliderTrackShape { + @override + void paint(PaintingContext context, Offset offset, + {required RenderBox parentBox, + required SliderThemeData sliderTheme, + required Animation enableAnimation, + required TextDirection textDirection, + required Offset thumbCenter, + Offset? secondaryOffset, + bool isDiscrete = false, + bool isEnabled = false, + double additionalActiveTrackHeight = 0}) { + super.paint(context, offset, + parentBox: parentBox, + sliderTheme: sliderTheme, + enableAnimation: enableAnimation, + textDirection: textDirection, + thumbCenter: thumbCenter, + secondaryOffset: secondaryOffset, + isDiscrete: isDiscrete, + isEnabled: isEnabled, + additionalActiveTrackHeight: additionalActiveTrackHeight); + } +} diff --git a/lib/util/application_info.freezed.dart b/lib/util/application_info.freezed.dart index 6813631..92dc1c4 100644 --- a/lib/util/application_info.freezed.dart +++ b/lib/util/application_info.freezed.dart @@ -21,7 +21,9 @@ mixin _$ApplicationInfo { String get buildNumber => throw _privateConstructorUsedError; String get os => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of ApplicationInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApplicationInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -45,6 +47,8 @@ class _$ApplicationInfoCopyWithImpl<$Res, $Val extends ApplicationInfo> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApplicationInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -93,6 +97,8 @@ class __$$ApplicationInfoImplCopyWithImpl<$Res> _$ApplicationInfoImpl _value, $Res Function(_$ApplicationInfoImpl) _then) : super(_value, _then); + /// Create a copy of ApplicationInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -156,7 +162,9 @@ class _$ApplicationInfoImpl extends _ApplicationInfo { @override int get hashCode => Object.hash(runtimeType, name, version, buildNumber, os); - @JsonKey(ignore: true) + /// Create a copy of ApplicationInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApplicationInfoImplCopyWith<_$ApplicationInfoImpl> get copyWith => @@ -180,8 +188,11 @@ abstract class _ApplicationInfo extends ApplicationInfo { String get buildNumber; @override String get os; + + /// Create a copy of ApplicationInfo + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApplicationInfoImplCopyWith<_$ApplicationInfoImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/util/fladder_image.dart b/lib/util/fladder_image.dart index 55fc6a8..4cf34a5 100644 --- a/lib/util/fladder_image.dart +++ b/lib/util/fladder_image.dart @@ -1,11 +1,13 @@ -import 'package:fladder/models/items/images_models.dart'; -import 'package:fladder/providers/settings/client_settings_provider.dart'; -import 'package:fladder/util/adaptive_layout.dart'; import 'package:flutter/material.dart'; + import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:transparent_image/transparent_image.dart'; +import 'package:fladder/models/items/images_models.dart'; +import 'package:fladder/providers/settings/client_settings_provider.dart'; +import 'package:fladder/util/adaptive_layout.dart'; + class FladderImage extends ConsumerWidget { final ImageData? image; final Widget Function(BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded)? frameBuilder; diff --git a/lib/widgets/shared/fladder_carousel.dart b/lib/widgets/shared/fladder_carousel.dart index e271a28..f7408d1 100644 --- a/lib/widgets/shared/fladder_carousel.dart +++ b/lib/widgets/shared/fladder_carousel.dart @@ -48,7 +48,8 @@ class FladderCarousel extends StatefulWidget { /// Creates a Material Design carousel. const FladderCarousel({ super.key, - this.padding, + this.itemPadding, + this.padding = EdgeInsets.zero, this.backgroundColor, this.elevation, this.shape, @@ -68,7 +69,9 @@ class FladderCarousel extends StatefulWidget { /// The amount of space to surround each carousel item with. /// /// Defaults to [EdgeInsets.all] of 4 pixels. - final EdgeInsets? padding; + final EdgeInsets? itemPadding; + + final EdgeInsets padding; /// The background color for each carousel item. /// @@ -234,7 +237,7 @@ class _CarouselViewState extends State { final AxisDirection axisDirection = _getDirection(context); final ScrollPhysics physics = widget.itemSnapping ? const CarouselScrollPhysics() : ScrollConfiguration.of(context).getScrollPhysics(context); - final EdgeInsets effectivePadding = widget.padding ?? const EdgeInsets.all(4.0); + final EdgeInsets effectivePadding = widget.itemPadding ?? const EdgeInsets.all(4.0); final Color effectiveBackgroundColor = widget.backgroundColor ?? Theme.of(context).colorScheme.surface; final double effectiveElevation = widget.elevation ?? 0.0; final ShapeBorder effectiveShape = @@ -266,7 +269,10 @@ class _CarouselViewState extends State { delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return Padding( - padding: effectivePadding, + padding: effectivePadding.add(EdgeInsets.only( + left: index == 0 ? widget.padding.left : 0, + right: index == widget.children.length - 1 ? widget.padding.right : 0, + )), child: Material( clipBehavior: Clip.antiAlias, color: effectiveBackgroundColor, @@ -276,29 +282,31 @@ class _CarouselViewState extends State { fit: StackFit.expand, children: [ widget.children.elementAt(index), - Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.onTap != null ? () => widget.onTap!.call(index) : null, - onLongPress: widget.onLongPress != null ? () => widget.onLongPress!.call(index) : null, - onSecondaryTapDown: widget.onSecondaryTap != null - ? (details) => widget.onSecondaryTap!.call((index, details)) - : null, - overlayColor: widget.overlayColor ?? - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return theme.colorScheme.onSurface.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return theme.colorScheme.onSurface.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return theme.colorScheme.onSurface.withOpacity(0.1); - } - return null; - }), + if (widget.onTap != null || widget.onSecondaryTap != null || widget.onLongPress != null) + Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.onTap != null ? () => widget.onTap!.call(index) : null, + onLongPress: + widget.onLongPress != null ? () => widget.onLongPress!.call(index) : null, + onSecondaryTapDown: widget.onSecondaryTap != null + ? (details) => widget.onSecondaryTap!.call((index, details)) + : null, + overlayColor: widget.overlayColor ?? + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.pressed)) { + return theme.colorScheme.onSurface.withOpacity(0.1); + } + if (states.contains(WidgetState.hovered)) { + return theme.colorScheme.onSurface.withOpacity(0.08); + } + if (states.contains(WidgetState.focused)) { + return theme.colorScheme.onSurface.withOpacity(0.1); + } + return null; + }), + ), ), - ), ], ), ), diff --git a/lib/widgets/shared/fladder_slider.dart b/lib/widgets/shared/fladder_slider.dart index d06e899..153ddfd 100644 --- a/lib/widgets/shared/fladder_slider.dart +++ b/lib/widgets/shared/fladder_slider.dart @@ -1,6 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:fladder/util/num_extension.dart'; import 'package:fladder/widgets/gapped_container_shape.dart'; -import 'package:flutter/material.dart'; double normalize(double min, double max, double value) { return (value - min) / (max - min); @@ -12,6 +13,8 @@ class FladderSlider extends StatefulWidget { final double max; final int? divisions; final double thumbWidth; + final Color? activeTrackColor; + final Color? inactiveTrackColor; final bool showThumb; final Duration animation; final Function(double value)? onChanged; @@ -25,6 +28,8 @@ class FladderSlider extends StatefulWidget { this.divisions, this.onChanged, this.thumbWidth = 6.5, + this.activeTrackColor, + this.inactiveTrackColor, this.showThumb = true, this.animation = const Duration(milliseconds: 100), this.onChangeStart, @@ -146,6 +151,8 @@ class FladderSliderState extends State with SingleTickerProviderS height: height, width: constraints.maxWidth, child: GappedContainerShape( + activeColor: widget.activeTrackColor, + inActiveColor: widget.inactiveTrackColor, thumbPosition: relativeValue, ), ),