From 3b38bcfaa5993d78e819255e7f6d4814db09be98 Mon Sep 17 00:00:00 2001 From: Julian Dice <19397727+windoze95@users.noreply.github.com> Date: Sat, 27 Jun 2026 22:35:15 -0500 Subject: [PATCH] feat: watch-later queue with optimistic add/remove and player auto-advance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the iOS app up to the new queue API (POST/DELETE /api/videos/{id}/queue, GET /api/queue). - ApiService: addToQueue, removeFromQueue, getQueue — getQueue reuses the VideoPage cursor envelope, like search. - queueProvider (Notifier): loads the queue, paginates, and applies add/remove optimistically with rollback + rethrow on failure. nextAfter() picks the auto-advance target without mutating state. - Add/Remove-from-Queue actions on home video cards (long-press), search results, channel detail, and the player controls. A Queue screen reached from the Library app bar lists the queue with tap-to-play and a reorder-free remove; pull-to-refresh reloads it. - Player auto-advance: when a video plays to the end (never on manual back-out) it consumes the finished item if queued and plays the next queued one. The incoming player re-asserts immersive/landscape so the replaced route's teardown can't reset the system UI. Tests: queue provider — getQueue page parsing, optimistic add/remove with revert, idempotent add, toggle, pagination, and nextAfter. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RXMKM1rDWn8wNh93MMUtxY --- lib/config/constants.dart | 2 + lib/config/routes.dart | 6 + lib/providers/queue_provider.dart | 204 +++++++++++++++++ lib/screens/channel_detail_screen.dart | 5 + lib/screens/library_screen.dart | 5 + lib/screens/queue_screen.dart | 217 +++++++++++++++++++ lib/screens/search_screen.dart | 14 ++ lib/screens/video_player_screen.dart | 113 +++++++++- lib/services/api_service.dart | 27 +++ lib/widgets/queue_action.dart | 130 +++++++++++ lib/widgets/video_card.dart | 15 ++ test/unit/queue_notifier_test.dart | 289 +++++++++++++++++++++++++ 12 files changed, 1025 insertions(+), 2 deletions(-) create mode 100644 lib/providers/queue_provider.dart create mode 100644 lib/screens/queue_screen.dart create mode 100644 lib/widgets/queue_action.dart create mode 100644 test/unit/queue_notifier_test.dart diff --git a/lib/config/constants.dart b/lib/config/constants.dart index d92ca42..753790f 100644 --- a/lib/config/constants.dart +++ b/lib/config/constants.dart @@ -36,6 +36,7 @@ class AppConstants { static const String channelPollAll = '$apiBase/channels/poll'; static const String videos = '$apiBase/videos'; static const String activeDownloads = '$apiBase/videos/downloads'; + static const String queue = '$apiBase/queue'; static const String feedHome = '$apiBase/feed/home'; static const String feedContinueWatching = '$apiBase/feed/continue-watching'; static const String feedNewEpisodes = '$apiBase/feed/new-episodes'; @@ -60,6 +61,7 @@ class AppConstants { static String videoPreview(String id) => '$apiBase/videos/$id/preview'; static String videoPreviewStream(String id) => '$apiBase/videos/$id/preview-stream'; + static String videoQueue(String id) => '$apiBase/videos/$id/queue'; static String discoverDismiss(String id) => '$apiBase/discover/$id/dismiss'; static String websocket(String userId) => '/ws/$userId'; diff --git a/lib/config/routes.dart b/lib/config/routes.dart index f2fc299..d9d60f0 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -12,6 +12,7 @@ import '../screens/settings_screen.dart'; import '../screens/channel_detail_screen.dart'; import '../screens/video_player_screen.dart'; import '../screens/search_screen.dart'; +import '../screens/queue_screen.dart'; final _rootNavigatorKey = GlobalKey(); final _shellNavigatorKey = GlobalKey(); @@ -105,6 +106,11 @@ final routerProvider = Provider((ref) { parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const SearchScreen(), ), + GoRoute( + path: '/queue', + parentNavigatorKey: _rootNavigatorKey, + builder: (context, state) => const QueueScreen(), + ), ], ); ref.onDispose(router.dispose); diff --git a/lib/providers/queue_provider.dart b/lib/providers/queue_provider.dart new file mode 100644 index 0000000..0fe8ac0 --- /dev/null +++ b/lib/providers/queue_provider.dart @@ -0,0 +1,204 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/video.dart'; +import '../services/api_service.dart'; + +/// Immutable state for the watch-later queue: the loaded [videos] in play +/// order (front first), the pagination cursor and total, plus the loading and +/// error bookkeeping shared by the Queue screen and the per-video toggles. +class QueueState { + final List