From 47771a072846d057e14b45319b119472686811ff Mon Sep 17 00:00:00 2001 From: PartyDonut Date: Fri, 11 Oct 2024 15:40:04 +0200 Subject: [PATCH] [Bugfix] Video player options [Translation] Add translations for ends at --- lib/l10n/app_en.arb | 12 +- lib/l10n/app_es.arb | 3 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_jp.arb | 3 +- lib/l10n/app_nl.arb | 11 +- lib/l10n/app_zh.arb | 3 +- lib/models/item_base_model.dart | 27 +-- lib/models/items/movie_model.dart | 5 +- .../video_player_options_sheet.dart | 202 +++++++++--------- .../video_player/video_player_controls.dart | 66 +++--- 10 files changed, 175 insertions(+), 160 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1466f27..da65805 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -665,5 +665,15 @@ "example": "1" } } - } + }, + "endsAt": "ends at {date}", + "@endsAt": { + "description": "endsAt", + "placeholders": { + "date": { + "type": "DateTime", + "format": "jm" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 38cb55c..2e3c4b0 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -665,5 +665,6 @@ "example": "1" } } - } + }, + "endsAt": "termina el {date}" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index aa427b5..1cd777e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -665,5 +665,6 @@ "example": "1" } } - } + }, + "endsAt": "se termine à {date}" } \ No newline at end of file diff --git a/lib/l10n/app_jp.arb b/lib/l10n/app_jp.arb index e1731f6..7589dbb 100644 --- a/lib/l10n/app_jp.arb +++ b/lib/l10n/app_jp.arb @@ -665,5 +665,6 @@ "example": "1" } } - } + }, + "endsAt": "{date}に終了" } \ No newline at end of file diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 2f4eba5..1ab7fce 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -364,7 +364,7 @@ } }, "playLabel": "Afspelen", - "playVideos": "Video's afspelen", + "playVideos": "Video''s afspelen", "played": "Gespeeld", "quickConnectAction": "Voer snelverbind code in voor", "quickConnectInputACode": "Voer een code in", @@ -543,14 +543,14 @@ "showDetails": "Toon details", "showEmpty": "Toon leeg", "shuffleGallery": "Galerij shuffle", - "shuffleVideos": "Video's shuffle", + "shuffleVideos": "Video''s shuffle", "somethingWentWrong": "Er is iets misgegaan", "somethingWentWrongPasswordCheck": "Er is iets misgegaan, controleer uw wachtwoord", "sortBy": "Sorteer op", "sortName": "Naam", "sortOrder": "Sorteervolgorde", "start": "Start", - "studio": "{count, plural, other{Studio's} one{Studio}}", + "studio": "{count, plural, other{Studio''s} one{Studio}}", "@studio": { "description": "studio", "placeholders": { @@ -644,7 +644,7 @@ "videoScalingFitHeight": "Pas hoogte aan", "videoScalingFitWidth": "Pas breedte aan", "videoScalingScaleDown": "Schaal omlaag", - "viewPhotos": "Foto's bekijken", + "viewPhotos": "Foto''s bekijken", "watchOn": "Kijk op", "writer": "{count, plural, other{Schrijvers} one{Schrijver}}", "@writer": { @@ -665,5 +665,6 @@ "example": "1" } } - } + }, + "endsAt": "eindigt om {date}" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index a720275..86a9849 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -665,5 +665,6 @@ "example": "1" } } - } + }, + "endsAt": "结束于 {date}" } \ No newline at end of file diff --git a/lib/models/item_base_model.dart b/lib/models/item_base_model.dart index 79d83d2..40665c9 100644 --- a/lib/models/item_base_model.dart +++ b/lib/models/item_base_model.dart @@ -1,34 +1,35 @@ +import 'package:flutter/material.dart'; + import 'package:auto_route/auto_route.dart'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:ficonsax/ficonsax.dart'; -import 'package:fladder/models/book_model.dart'; -import 'package:fladder/models/boxset_model.dart'; -import 'package:fladder/models/items/media_streams_model.dart'; -import 'package:fladder/models/library_search/library_search_options.dart'; -import 'package:fladder/models/playlist_model.dart'; -import 'package:fladder/routes/auto_router.gr.dart'; -import 'package:fladder/screens/details_screens/book_detail_screen.dart'; -import 'package:fladder/util/localization_helper.dart'; -import 'package:fladder/util/string_extensions.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; +import 'package:fladder/models/book_model.dart'; +import 'package:fladder/models/boxset_model.dart'; import 'package:fladder/models/items/episode_model.dart'; import 'package:fladder/models/items/folder_model.dart'; import 'package:fladder/models/items/images_models.dart'; import 'package:fladder/models/items/item_shared_models.dart'; +import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/movie_model.dart'; import 'package:fladder/models/items/overview_model.dart'; import 'package:fladder/models/items/person_model.dart'; import 'package:fladder/models/items/photos_model.dart'; import 'package:fladder/models/items/season_model.dart'; import 'package:fladder/models/items/series_model.dart'; +import 'package:fladder/models/library_search/library_search_options.dart'; +import 'package:fladder/models/playlist_model.dart'; +import 'package:fladder/routes/auto_router.gr.dart'; +import 'package:fladder/screens/details_screens/book_detail_screen.dart'; import 'package:fladder/screens/details_screens/details_screens.dart'; import 'package:fladder/screens/details_screens/episode_detail_screen.dart'; import 'package:fladder/screens/details_screens/season_detail_screen.dart'; import 'package:fladder/screens/library_search/library_search_screen.dart'; +import 'package:fladder/util/localization_helper.dart'; +import 'package:fladder/util/string_extensions.dart'; part 'item_base_model.mapper.dart'; @@ -62,8 +63,6 @@ class ItemBaseModel with ItemBaseModelMappable { required this.jellyType, }); - String get title => name; - ItemBaseModel? setProgress(double progress) { return copyWith(userData: userData.copyWith(progress: progress)); } @@ -98,6 +97,8 @@ class ItemBaseModel with ItemBaseModelMappable { _ => null, }; + String get title => name; + ///Used for retrieving the correct id when fetching queue String get streamId => id; @@ -111,7 +112,7 @@ class ItemBaseModel with ItemBaseModelMappable { bool get unWatched => !userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0; - String? detailedName(BuildContext context) => null; + String? detailedName(BuildContext context) => "$name${overview.yearAired != null ? " (${overview.yearAired})" : ""}"; String? get subText => null; String? subTextShort(BuildContext context) => null; diff --git a/lib/models/items/movie_model.dart b/lib/models/items/movie_model.dart index a8278fd..b8ad3df 100644 --- a/lib/models/items/movie_model.dart +++ b/lib/models/items/movie_model.dart @@ -13,6 +13,7 @@ import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_stream_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/overview_model.dart'; +import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/screens/details_screens/movie_detail_screen.dart'; import 'package:fladder/util/humanize_duration.dart'; @@ -68,10 +69,6 @@ class MovieModel extends ItemStreamModel with MovieModelMappable { @override bool get identifiable => true; - @override - String? label(BuildContext context) => - overview.yearAired == null ? overview.runTime.humanize : "$name (${overview.yearAired})"; - @override ImageData? get bannerImage => images?.backDrop?.firstOrNull ?? images?.primary ?? getPosters?.primary; diff --git a/lib/screens/video_player/components/video_player_options_sheet.dart b/lib/screens/video_player/components/video_player_options_sheet.dart index 6881987..fcb0cf0 100644 --- a/lib/screens/video_player/components/video_player_options_sheet.dart +++ b/lib/screens/video_player/components/video_player_options_sheet.dart @@ -1,5 +1,9 @@ +import 'package:flutter/material.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/episode_model.dart'; import 'package:fladder/models/playback/direct_playback_model.dart'; @@ -20,15 +24,14 @@ import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/widgets/shared/enum_selection.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:fladder/widgets/shared/spaced_list_tile.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -Future showVideoPlayerOptions(BuildContext context) { +Future showVideoPlayerOptions(BuildContext context, Function() minimizePlayer) { return showBottomSheetPill( context: context, content: (context, scrollController) { return VideoOptions( controller: scrollController, + minimizePlayer: minimizePlayer, ); }, ); @@ -36,7 +39,8 @@ Future showVideoPlayerOptions(BuildContext context) { class VideoOptions extends ConsumerStatefulWidget { final ScrollController controller; - const VideoOptions({required this.controller, super.key}); + final Function() minimizePlayer; + const VideoOptions({required this.controller, required this.minimizePlayer, super.key}); @override ConsumerState createState() => _VideoOptionsMobileState(); @@ -67,16 +71,10 @@ class _VideoOptionsMobileState extends ConsumerState { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (currentItem?.title.isNotEmpty == true) - Text( - currentItem?.title ?? "", - style: Theme.of(context).textTheme.titleLarge, - ), - if (currentItem?.detailedName(context)?.isNotEmpty == true) - Text( - currentItem?.detailedName(context) ?? "", - style: Theme.of(context).textTheme.titleSmall, - ) + Text( + currentItem?.title ?? "", + style: Theme.of(context).textTheme.titleLarge, + ), ], ), const Spacer(), @@ -173,86 +171,6 @@ class _VideoOptionsMobileState extends ConsumerState { ], ), ), - // ListTile( - // title: const Text("Playback settings"), - // onTap: () => setState(() => page = 1), - // ), - ], - ); - } - - Widget itemOptions() { - final currentItem = ref.watch(playBackModel.select((value) => value?.item)); - return ListView( - shrinkWrap: true, - children: [ - navTitle("${currentItem?.title} \n${currentItem?.detailedName}"), - if (currentItem != null) ...{ - if (currentItem.type == FladderItemType.episode) - ListTile( - onTap: () { - //Pop twice once for sheet once for player - Navigator.of(context).pop(); - Navigator.of(context).pop(); - (this as EpisodeModel).parentBaseModel.navigateTo(context); - }, - title: const Text("Open show"), - ), - ListTile( - onTap: () async { - //Pop twice once for sheet once for player - Navigator.of(context).pop(); - Navigator.of(context).pop(); - await currentItem.navigateTo(context); - }, - title: const Text("Show details"), - ), - if (currentItem.type != FladderItemType.boxset) - ListTile( - onTap: () async { - await addItemToCollection(context, [currentItem]); - if (context.mounted) { - context.refreshData(); - } - }, - title: const Text("Add to collection"), - ), - if (currentItem.type != FladderItemType.playlist) - ListTile( - onTap: () async { - await addItemToPlaylist(context, [currentItem]); - if (context.mounted) { - context.refreshData(); - } - }, - title: const Text("Add to playlist"), - ), - ListTile( - onTap: () { - final favourite = !(currentItem.userData.isFavourite == true); - ref.read(userProvider.notifier).setAsFavorite(favourite, currentItem.id); - final newUserData = currentItem.userData; - final playbackModel = switch (ref.read(playBackModel)) { - DirectPlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)), - TranscodePlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)), - OfflinePlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)), - _ => null - }; - if (playbackModel != null) { - ref.read(playBackModel.notifier).update((state) => playbackModel); - } - Navigator.of(context).pop(); - }, - title: Text(currentItem.userData.isFavourite == true ? "Remove from favorites" : "Add to favourites"), - ), - ListTile( - onTap: () { - Navigator.of(context).pop(); - showInfoScreen(context, currentItem); - }, - title: const Text('Media info'), - ), - } ], ); } @@ -264,7 +182,7 @@ class _VideoOptionsMobileState extends ConsumerState { shrinkWrap: true, controller: widget.controller, children: [ - navTitle("Playback Settings"), + navTitle("Playback Settings", null), if (playbackState?.queue.isNotEmpty == true) ListTile( leading: const Icon(Icons.video_collection_rounded), @@ -294,7 +212,7 @@ class _VideoOptionsMobileState extends ConsumerState { duration: const Duration(milliseconds: 250), child: switch (page) { 1 => playbackSettings(), - 2 => itemOptions(), + 2 => itemInfo(currentItem, context), _ => mainPage(), }, ), @@ -304,7 +222,79 @@ class _VideoOptionsMobileState extends ConsumerState { ); } - Widget navTitle(String title) { + ListView itemInfo(ItemBaseModel? currentItem, BuildContext context) { + return ListView( + shrinkWrap: true, + children: [ + navTitle(currentItem?.title, currentItem?.subTextShort(context)), + if (currentItem != null) ...{ + if (currentItem.type == FladderItemType.episode) + ListTile( + onTap: () { + Navigator.of(context).pop(); + widget.minimizePlayer(); + (this as EpisodeModel).parentBaseModel.navigateTo(context); + }, + title: const Text("Open show"), + ), + ListTile( + onTap: () async { + Navigator.of(context).pop(); + widget.minimizePlayer(); + await currentItem.navigateTo(context); + }, + title: const Text("Show details"), + ), + if (currentItem.type != FladderItemType.boxset) + ListTile( + onTap: () async { + await addItemToCollection(context, [currentItem]); + if (context.mounted) { + context.refreshData(); + } + }, + title: const Text("Add to collection"), + ), + if (currentItem.type != FladderItemType.playlist) + ListTile( + onTap: () async { + await addItemToPlaylist(context, [currentItem]); + if (context.mounted) { + context.refreshData(); + } + }, + title: const Text("Add to playlist"), + ), + ListTile( + onTap: () async { + final response = await ref + .read(userProvider.notifier) + .setAsFavorite(!(currentItem.userData.isFavourite == true), currentItem.id); + final newItem = currentItem.copyWith(userData: response?.body); + final playbackModel = switch (ref.read(playBackModel)) { + DirectPlaybackModel value => value.copyWith(item: newItem), + TranscodePlaybackModel value => value.copyWith(item: newItem), + OfflinePlaybackModel value => value.copyWith(item: newItem), + _ => null + }; + ref.read(playBackModel.notifier).update((state) => playbackModel); + Navigator.of(context).pop(); + }, + title: Text(currentItem.userData.isFavourite == true ? "Remove from favorites" : "Add to favourites"), + ), + ListTile( + onTap: () { + Navigator.of(context).pop(); + showInfoScreen(context, currentItem); + }, + title: const Text('Media info'), + ), + } + ], + ); + } + + Widget navTitle(String? title, String? subText) { return Column( children: [ Row( @@ -314,10 +304,20 @@ class _VideoOptionsMobileState extends ConsumerState { onPressed: () => setState(() => page = 0), ), const SizedBox(width: 16), - Text( - title, - style: Theme.of(context).textTheme.titleLarge, - ) + Column( + children: [ + if (title != null) + Text( + title, + style: Theme.of(context).textTheme.titleLarge, + ), + if (subText != null) + Text( + subText, + style: Theme.of(context).textTheme.titleMedium, + ) + ], + ), ], ), const SizedBox(height: 12), diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index 79063fc..a9b2ee8 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -30,6 +30,7 @@ import 'package:fladder/screens/video_player/components/video_volume_slider.dart import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/duration_extensions.dart'; import 'package:fladder/util/list_padding.dart'; +import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/string_extensions.dart'; class DesktopControls extends ConsumerStatefulWidget { @@ -48,29 +49,6 @@ class _DesktopControlsState extends ConsumerState { late final double topPadding = MediaQuery.of(context).viewPadding.top; late final double bottomPadding = MediaQuery.of(context).viewPadding.bottom; - Future clear() async { - toggleOverlay(value: true); - if (!AdaptiveLayout.of(context).isDesktop) { - ScreenBrightness().resetScreenBrightness(); - } else { - disableFullscreen(); - } - - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarIconBrightness: ref.read(clientSettingsProvider.select((value) => value.statusBarBrightness(context))), - )); - - timer.cancel(); - } - - void resetTimer() => timer.reset(); - - Future closePlayer() async { - clear(); - ref.read(videoPlayerProvider).stop(); - Navigator.of(context).pop(); - } - @override Widget build(BuildContext context) { final mediaPlayback = ref.watch(mediaPlaybackProvider); @@ -252,13 +230,7 @@ class _DesktopControlsState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( - onPressed: () { - clear(); - ref - .read(mediaPlaybackProvider.notifier) - .update((state) => state.copyWith(state: VideoPlayerState.minimized)); - Navigator.of(context).pop(); - }, + onPressed: () => minimizePlayer(context), icon: const Icon( IconsaxOutline.arrow_down_1, size: 24, @@ -312,7 +284,8 @@ class _DesktopControlsState extends ConsumerState { child: Row( children: [ IconButton( - onPressed: () => showVideoPlayerOptions(context), icon: const Icon(IconsaxOutline.more)), + onPressed: () => showVideoPlayerOptions(context, () => minimizePlayer(context)), + icon: const Icon(IconsaxOutline.more)), if (AdaptiveLayout.layoutOf(context) == LayoutState.tablet) ...[ IconButton( onPressed: () => showSubSelection(context), @@ -432,7 +405,7 @@ class _DesktopControlsState extends ConsumerState { final List details = [ if (AdaptiveLayout.of(context).isDesktop) item?.label(context), mediaPlayback.duration.inMinutes > 1 - ? 'ends at ${DateFormat('HH:mm').format(DateTime.now().add(mediaPlayback.duration - mediaPlayback.position))}' + ? context.localized.endsAt(DateTime.now().add(mediaPlayback.duration - mediaPlayback.position)) : null ]; return Column( @@ -627,6 +600,35 @@ class _DesktopControlsState extends ConsumerState { )); } + void minimizePlayer(BuildContext context) { + clearOverlaySettings(); + ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.minimized)); + Navigator.of(context).pop(); + } + + Future clearOverlaySettings() async { + toggleOverlay(value: true); + if (!AdaptiveLayout.of(context).isDesktop) { + ScreenBrightness().resetScreenBrightness(); + } else { + disableFullscreen(); + } + + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarIconBrightness: ref.read(clientSettingsProvider.select((value) => value.statusBarBrightness(context))), + )); + + timer.cancel(); + } + + void resetTimer() => timer.reset(); + + Future closePlayer() async { + clearOverlaySettings(); + ref.read(videoPlayerProvider).stop(); + Navigator.of(context).pop(); + } + Future disableFullscreen() async { resetTimer(); final isFullScreen = await windowManager.isFullScreen();