feat: Add functionality to remove items from Continue Watching
- Updated the DiscoverScreen to include a callback for removing items from Continue Watching. - Enhanced MediaCard to accept a new callback for removing items from Continue Watching. - Modified MediaContextMenu to handle the removal of items from Continue Watching without affecting their watched status or progress. - Updated dependencies in pubspec.lock to their latest versions. Co-authored-by: Doezer <11655673+Doezer@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
Doezer
co-authored by
Doezer
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent
4845d2e659
commit
9be219ce15
@@ -869,6 +869,15 @@ class PlexClient {
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove item from Continue Watching (On Deck) without affecting watch status or progress
|
||||
/// This uses the same endpoint Plex Web uses to hide items from Continue Watching
|
||||
Future<void> removeFromOnDeck(String ratingKey) async {
|
||||
await _dio.put(
|
||||
'/actions/removeFromContinueWatching',
|
||||
queryParameters: {'ratingKey': ratingKey},
|
||||
);
|
||||
}
|
||||
|
||||
/// Get server preferences
|
||||
Future<Map<String, dynamic>> getServerPreferences() async {
|
||||
final response = await _dio.get('/:/prefs');
|
||||
|
||||
+14
-2
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 3
|
||||
/// Strings: 930 (310 per locale)
|
||||
/// Strings: 936 (312 per locale)
|
||||
///
|
||||
/// Built on 2025-11-12 at 17:03 UTC
|
||||
/// Built on 2025-11-13 at 18:06 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
@@ -426,6 +426,7 @@ class _StringsMediaMenuEn {
|
||||
// Translations
|
||||
String get markAsWatched => 'Mark as Watched';
|
||||
String get markAsUnwatched => 'Mark as Unwatched';
|
||||
String get removeFromContinueWatching => 'Remove from Continue Watching';
|
||||
String get goToSeries => 'Go to series';
|
||||
String get goToSeason => 'Go to season';
|
||||
String get shufflePlay => 'Shuffle Play';
|
||||
@@ -487,6 +488,7 @@ class _StringsMessagesEn {
|
||||
// Translations
|
||||
String get markedAsWatched => 'Marked as watched';
|
||||
String get markedAsUnwatched => 'Marked as unwatched';
|
||||
String get removedFromContinueWatching => 'Removed from Continue Watching';
|
||||
String errorLoading({required Object error}) => 'Error: ${error}';
|
||||
String get fileInfoNotAvailable => 'File information not available';
|
||||
String errorLoadingFileInfo({required Object error}) => 'Error loading file info: ${error}';
|
||||
@@ -1032,6 +1034,7 @@ class _StringsMediaMenuIt implements _StringsMediaMenuEn {
|
||||
// Translations
|
||||
@override String get markAsWatched => 'Segna come visto';
|
||||
@override String get markAsUnwatched => 'Segna come non visto';
|
||||
@override String get removeFromContinueWatching => 'Rimuovi da Continua a guardare';
|
||||
@override String get goToSeries => 'Vai alle serie';
|
||||
@override String get goToSeason => 'Vai alla stagione';
|
||||
@override String get shufflePlay => 'Riproduzione casuale';
|
||||
@@ -1093,6 +1096,7 @@ class _StringsMessagesIt implements _StringsMessagesEn {
|
||||
// Translations
|
||||
@override String get markedAsWatched => 'Segna come visto';
|
||||
@override String get markedAsUnwatched => 'Segna come non visto';
|
||||
@override String get removedFromContinueWatching => 'Rimosso da Continua a guardare';
|
||||
@override String errorLoading({required Object error}) => 'Errore: ${error}';
|
||||
@override String get fileInfoNotAvailable => 'Informazioni sul file non disponibili';
|
||||
@override String errorLoadingFileInfo({required Object error}) => 'Errore caricamento informazioni sul file: ${error}';
|
||||
@@ -1638,6 +1642,7 @@ class _StringsMediaMenuSv implements _StringsMediaMenuEn {
|
||||
// Translations
|
||||
@override String get markAsWatched => 'Markera som sedd';
|
||||
@override String get markAsUnwatched => 'Markera som osedd';
|
||||
@override String get removeFromContinueWatching => 'Ta bort från Fortsätt titta';
|
||||
@override String get goToSeries => 'Gå till serie';
|
||||
@override String get goToSeason => 'Gå till säsong';
|
||||
@override String get shufflePlay => 'Blanda uppspelning';
|
||||
@@ -1699,6 +1704,7 @@ class _StringsMessagesSv implements _StringsMessagesEn {
|
||||
// Translations
|
||||
@override String get markedAsWatched => 'Markerad som sedd';
|
||||
@override String get markedAsUnwatched => 'Markerad som osedd';
|
||||
@override String get removedFromContinueWatching => 'Borttagen från Fortsätt titta';
|
||||
@override String errorLoading({required Object error}) => 'Fel: ${error}';
|
||||
@override String get fileInfoNotAvailable => 'Filinformation inte tillgänglig';
|
||||
@override String errorLoadingFileInfo({required Object error}) => 'Fel vid laddning av filinformation: ${error}';
|
||||
@@ -2100,6 +2106,7 @@ extension on Translations {
|
||||
case 'fileInfo.has64bitOffsets': return '64-bit Offsets';
|
||||
case 'mediaMenu.markAsWatched': return 'Mark as Watched';
|
||||
case 'mediaMenu.markAsUnwatched': return 'Mark as Unwatched';
|
||||
case 'mediaMenu.removeFromContinueWatching': return 'Remove from Continue Watching';
|
||||
case 'mediaMenu.goToSeries': return 'Go to series';
|
||||
case 'mediaMenu.goToSeason': return 'Go to season';
|
||||
case 'mediaMenu.shufflePlay': return 'Shuffle Play';
|
||||
@@ -2125,6 +2132,7 @@ extension on Translations {
|
||||
case 'userStatus.protected': return 'Protected';
|
||||
case 'messages.markedAsWatched': return 'Marked as watched';
|
||||
case 'messages.markedAsUnwatched': return 'Marked as unwatched';
|
||||
case 'messages.removedFromContinueWatching': return 'Removed from Continue Watching';
|
||||
case 'messages.errorLoading': return ({required Object error}) => 'Error: ${error}';
|
||||
case 'messages.fileInfoNotAvailable': return 'File information not available';
|
||||
case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Error loading file info: ${error}';
|
||||
@@ -2418,6 +2426,7 @@ extension on _StringsIt {
|
||||
case 'fileInfo.has64bitOffsets': return 'Offset a 64-bit';
|
||||
case 'mediaMenu.markAsWatched': return 'Segna come visto';
|
||||
case 'mediaMenu.markAsUnwatched': return 'Segna come non visto';
|
||||
case 'mediaMenu.removeFromContinueWatching': return 'Rimuovi da Continua a guardare';
|
||||
case 'mediaMenu.goToSeries': return 'Vai alle serie';
|
||||
case 'mediaMenu.goToSeason': return 'Vai alla stagione';
|
||||
case 'mediaMenu.shufflePlay': return 'Riproduzione casuale';
|
||||
@@ -2443,6 +2452,7 @@ extension on _StringsIt {
|
||||
case 'userStatus.protected': return 'Protetto';
|
||||
case 'messages.markedAsWatched': return 'Segna come visto';
|
||||
case 'messages.markedAsUnwatched': return 'Segna come non visto';
|
||||
case 'messages.removedFromContinueWatching': return 'Rimosso da Continua a guardare';
|
||||
case 'messages.errorLoading': return ({required Object error}) => 'Errore: ${error}';
|
||||
case 'messages.fileInfoNotAvailable': return 'Informazioni sul file non disponibili';
|
||||
case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Errore caricamento informazioni sul file: ${error}';
|
||||
@@ -2736,6 +2746,7 @@ extension on _StringsSv {
|
||||
case 'fileInfo.has64bitOffsets': return '64-bit offset';
|
||||
case 'mediaMenu.markAsWatched': return 'Markera som sedd';
|
||||
case 'mediaMenu.markAsUnwatched': return 'Markera som osedd';
|
||||
case 'mediaMenu.removeFromContinueWatching': return 'Ta bort från Fortsätt titta';
|
||||
case 'mediaMenu.goToSeries': return 'Gå till serie';
|
||||
case 'mediaMenu.goToSeason': return 'Gå till säsong';
|
||||
case 'mediaMenu.shufflePlay': return 'Blanda uppspelning';
|
||||
@@ -2761,6 +2772,7 @@ extension on _StringsSv {
|
||||
case 'userStatus.protected': return 'Skyddad';
|
||||
case 'messages.markedAsWatched': return 'Markerad som sedd';
|
||||
case 'messages.markedAsUnwatched': return 'Markerad som osedd';
|
||||
case 'messages.removedFromContinueWatching': return 'Borttagen från Fortsätt titta';
|
||||
case 'messages.errorLoading': return ({required Object error}) => 'Fel: ${error}';
|
||||
case 'messages.fileInfoNotAvailable': return 'Filinformation inte tillgänglig';
|
||||
case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Fel vid laddning av filinformation: ${error}';
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
"mediaMenu": {
|
||||
"markAsWatched": "Mark as Watched",
|
||||
"markAsUnwatched": "Mark as Unwatched",
|
||||
"removeFromContinueWatching": "Remove from Continue Watching",
|
||||
"goToSeries": "Go to series",
|
||||
"goToSeason": "Go to season",
|
||||
"shufflePlay": "Shuffle Play",
|
||||
@@ -204,6 +205,7 @@
|
||||
"messages": {
|
||||
"markedAsWatched": "Marked as watched",
|
||||
"markedAsUnwatched": "Marked as unwatched",
|
||||
"removedFromContinueWatching": "Removed from Continue Watching",
|
||||
"errorLoading": "Error: ${error}",
|
||||
"fileInfoNotAvailable": "File information not available",
|
||||
"errorLoadingFileInfo": "Error loading file info: ${error}",
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
"mediaMenu": {
|
||||
"markAsWatched": "Segna come visto",
|
||||
"markAsUnwatched": "Segna come non visto",
|
||||
"removeFromContinueWatching": "Rimuovi da Continua a guardare",
|
||||
"goToSeries": "Vai alle serie",
|
||||
"goToSeason": "Vai alla stagione",
|
||||
"shufflePlay": "Riproduzione casuale",
|
||||
@@ -204,6 +205,7 @@
|
||||
"messages": {
|
||||
"markedAsWatched": "Segna come visto",
|
||||
"markedAsUnwatched": "Segna come non visto",
|
||||
"removedFromContinueWatching": "Rimosso da Continua a guardare",
|
||||
"errorLoading": "Errore: ${error}",
|
||||
"fileInfoNotAvailable": "Informazioni sul file non disponibili",
|
||||
"errorLoadingFileInfo": "Errore caricamento informazioni sul file: ${error}",
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
"mediaMenu": {
|
||||
"markAsWatched": "Markera som sedd",
|
||||
"markAsUnwatched": "Markera som osedd",
|
||||
"removeFromContinueWatching": "Ta bort från Fortsätt titta",
|
||||
"goToSeries": "Gå till serie",
|
||||
"goToSeason": "Gå till säsong",
|
||||
"shufflePlay": "Blanda uppspelning",
|
||||
@@ -204,6 +205,7 @@
|
||||
"messages": {
|
||||
"markedAsWatched": "Markerad som sedd",
|
||||
"markedAsUnwatched": "Markerad som osedd",
|
||||
"removedFromContinueWatching": "Borttagen från Fortsätt titta",
|
||||
"errorLoading": "Fel: ${error}",
|
||||
"fileInfoNotAvailable": "Filinformation inte tillgänglig",
|
||||
"errorLoadingFileInfo": "Fel vid laddning av filinformation: ${error}",
|
||||
|
||||
@@ -627,7 +627,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildHorizontalList(_onDeck, isLarge: false),
|
||||
_buildHorizontalList(_onDeck, isLarge: false, isInContinueWatching: true),
|
||||
],
|
||||
|
||||
|
||||
@@ -1236,6 +1236,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
Widget _buildHorizontalList(
|
||||
List<PlexMetadata> items, {
|
||||
bool isLarge = false,
|
||||
bool isInContinueWatching = false,
|
||||
}) {
|
||||
return SliverToBoxAdapter(
|
||||
child: LayoutBuilder(
|
||||
@@ -1278,7 +1279,11 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
width: cardWidth,
|
||||
height: posterHeight,
|
||||
onRefresh: updateItem,
|
||||
onRemoveFromContinueWatching: isInContinueWatching
|
||||
? _refreshContinueWatching
|
||||
: null,
|
||||
forceGridMode: true,
|
||||
isInContinueWatching: isInContinueWatching,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -19,7 +19,9 @@ class MediaCard extends StatefulWidget {
|
||||
final double? width;
|
||||
final double? height;
|
||||
final void Function(String ratingKey)? onRefresh;
|
||||
final VoidCallback? onRemoveFromContinueWatching;
|
||||
final bool forceGridMode;
|
||||
final bool isInContinueWatching;
|
||||
|
||||
const MediaCard({
|
||||
super.key,
|
||||
@@ -27,7 +29,9 @@ class MediaCard extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.onRefresh,
|
||||
this.onRemoveFromContinueWatching,
|
||||
this.forceGridMode = false,
|
||||
this.isInContinueWatching = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -99,7 +103,9 @@ class _MediaCardState extends State<MediaCard> {
|
||||
return MediaContextMenu(
|
||||
metadata: widget.item,
|
||||
onRefresh: widget.onRefresh,
|
||||
onRemoveFromContinueWatching: widget.onRemoveFromContinueWatching,
|
||||
onTap: () => _handleTap(context),
|
||||
isInContinueWatching: widget.isInContinueWatching,
|
||||
child: viewMode == ViewMode.grid
|
||||
? _MediaCardGrid(
|
||||
item: widget.item,
|
||||
|
||||
@@ -22,15 +22,19 @@ class _MenuAction {
|
||||
class MediaContextMenu extends StatefulWidget {
|
||||
final PlexMetadata metadata;
|
||||
final void Function(String ratingKey)? onRefresh;
|
||||
final VoidCallback? onRemoveFromContinueWatching;
|
||||
final VoidCallback? onTap;
|
||||
final Widget child;
|
||||
final bool isInContinueWatching;
|
||||
|
||||
const MediaContextMenu({
|
||||
super.key,
|
||||
required this.metadata,
|
||||
this.onRefresh,
|
||||
this.onRemoveFromContinueWatching,
|
||||
this.onTap,
|
||||
required this.child,
|
||||
this.isInContinueWatching = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -83,6 +87,17 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
);
|
||||
}
|
||||
|
||||
// Remove from Continue Watching (only in continue watching section)
|
||||
if (widget.isInContinueWatching) {
|
||||
menuActions.add(
|
||||
_MenuAction(
|
||||
value: 'remove_from_continue_watching',
|
||||
icon: Icons.close,
|
||||
label: t.mediaMenu.removeFromContinueWatching,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Go to Series (for episodes and seasons)
|
||||
if ((itemType == 'episode' || itemType == 'season') &&
|
||||
widget.metadata.grandparentTitle != null) {
|
||||
@@ -227,6 +242,32 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
);
|
||||
break;
|
||||
|
||||
case 'remove_from_continue_watching':
|
||||
// Remove from Continue Watching without affecting watch status or progress
|
||||
// This preserves the progression for partially watched items
|
||||
// and doesn't mark unwatched next episodes as watched
|
||||
try {
|
||||
await client.removeFromOnDeck(widget.metadata.ratingKey);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.messages.removedFromContinueWatching)),
|
||||
);
|
||||
// Use specific callback if provided, otherwise fallback to onRefresh
|
||||
if (widget.onRemoveFromContinueWatching != null) {
|
||||
widget.onRemoveFromContinueWatching!();
|
||||
} else {
|
||||
widget.onRefresh?.call(widget.metadata.ratingKey);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'series':
|
||||
await _navigateToRelated(
|
||||
context,
|
||||
|
||||
+10
-10
@@ -69,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d"
|
||||
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
version: "4.1.1"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -396,10 +396,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -611,10 +611,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -812,10 +812,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
version: "0.28.0"
|
||||
safe_local_storage:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1097,10 +1097,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
version: "0.7.7"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user