From b7b951ddbf85cb90083ed9a2c41c67ad347598dc Mon Sep 17 00:00:00 2001 From: Julian Dice <19397727+windoze95@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:01:46 -0500 Subject: [PATCH] feat(library): make server-side caching invisible; offline is the only save Reframes the UI to match the model: there's no user-facing "download/ collection". Episodes just play (cached or not); the only deliberate per-video save is "save offline to device". - video_list_tile: drop the cataloged "Download to server" button, the failed "retry", and the server downloading/pending progress+cancel badge. The only trailing affordance is the offline save/saving/saved state, and only for a cached (COMPLETE) video. Un-cached episodes show nothing (invisible caching). Removed onDownload/onCancel/downloadProgress params. - channel_detail: every episode is tap-to-play now (un-cached starts via instant-stream); removed the download handler, "Re-download" menu item, the optimistic-pending set and the 5s download-status polling. - downloads_screen -> "Offline": dropped the server-downloads section, activeDownloadsProvider and its polling; only on-device saves remain. Bottom nav tab relabeled "Offline". - websocket_provider: download_progress is now a no-op (caching is invisible); download_complete still refreshes feeds + drives auto-offline. - Removed now-unused api_service.downloadVideo/getActiveDownloads/cancelDownload and the download_progress_provider. Tests updated (a11y: offline label + un-cached shows no caching status). 156 passed; analyze + format clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/config/routes.dart | 6 +- lib/providers/download_progress_provider.dart | 23 -- lib/providers/websocket_provider.dart | 11 +- lib/screens/channel_detail_screen.dart | 132 +---------- lib/screens/downloads_screen.dart | 219 +----------------- lib/services/api_service.dart | 20 -- lib/widgets/video_list_tile.dart | 192 +++++---------- test/widgets/accessibility_test.dart | 18 +- 8 files changed, 93 insertions(+), 528 deletions(-) delete mode 100644 lib/providers/download_progress_provider.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d9d60f0..690d62a 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -177,9 +177,9 @@ class _ScaffoldWithNav extends ConsumerWidget { label: 'Discover', ), BottomNavigationBarItem( - icon: Icon(Icons.download_outlined), - activeIcon: Icon(Icons.download), - label: 'Downloads', + icon: Icon(Icons.offline_pin_outlined), + activeIcon: Icon(Icons.offline_pin), + label: 'Offline', ), BottomNavigationBarItem( icon: Icon(Icons.settings_outlined), diff --git a/lib/providers/download_progress_provider.dart b/lib/providers/download_progress_provider.dart deleted file mode 100644 index 45990c2..0000000 --- a/lib/providers/download_progress_provider.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -/// Ephemeral map of video_id → download percentage (0.0–100.0). -/// Updated via WebSocket events, not persisted. -class DownloadProgressNotifier extends Notifier> { - @override - Map build() => {}; - - void updateProgress(String videoId, double pct) { - state = {...state, videoId: pct}; - } - - void removeProgress(String videoId) { - final current = Map.from(state); - current.remove(videoId); - state = current; - } -} - -final downloadProgressProvider = - NotifierProvider>( - DownloadProgressNotifier.new, - ); diff --git a/lib/providers/websocket_provider.dart b/lib/providers/websocket_provider.dart index 0f8a3aa..054c53e 100644 --- a/lib/providers/websocket_provider.dart +++ b/lib/providers/websocket_provider.dart @@ -6,7 +6,6 @@ import '../services/offline_service.dart'; import '../services/api_service.dart'; import 'auth_provider.dart'; import 'channel_provider.dart'; -import 'download_progress_provider.dart'; import 'feed_provider.dart'; import 'discover_provider.dart'; import 'offline_provider.dart'; @@ -43,18 +42,12 @@ final webSocketConnectionProvider = Provider((ref) { final subscription = wsService.events.listen((event) { switch (event.type) { case WebSocketEventType.downloadProgress: - final videoId = event.data['video_id'] as String?; - final pct = (event.data['percentage'] as num?)?.toDouble(); - if (videoId != null && pct != null) { - ref - .read(downloadProgressProvider.notifier) - .updateProgress(videoId, pct); - } + // Caching is invisible — download progress is no longer surfaced. + break; case WebSocketEventType.downloadComplete: final videoId = event.data['video_id'] as String?; final channelId = event.data['channel_id'] as String?; if (videoId != null) { - ref.read(downloadProgressProvider.notifier).removeProgress(videoId); ref.invalidate(videoDetailProvider(videoId)); } if (channelId != null) { diff --git a/lib/screens/channel_detail_screen.dart b/lib/screens/channel_detail_screen.dart index 156bed3..4a2ca33 100644 --- a/lib/screens/channel_detail_screen.dart +++ b/lib/screens/channel_detail_screen.dart @@ -1,14 +1,10 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../models/video.dart'; import '../providers/channel_provider.dart'; -import '../providers/download_progress_provider.dart'; import '../providers/feed_provider.dart'; -import '../providers/settings_provider.dart'; import '../services/api_service.dart'; import '../services/storage_service.dart'; import '../widgets/queue_action.dart'; @@ -26,9 +22,6 @@ class ChannelDetailScreen extends ConsumerStatefulWidget { } class _ChannelDetailScreenState extends ConsumerState { - Timer? _pollTimer; - final Set _pendingVideoIds = {}; - @override void initState() { super.initState(); @@ -62,52 +55,6 @@ class _ChannelDetailScreenState extends ConsumerState { } } - @override - void dispose() { - _pollTimer?.cancel(); - super.dispose(); - } - - void _startPolling() { - if (_pollTimer?.isActive ?? false) return; - _pollTimer = Timer.periodic(const Duration(seconds: 5), (_) { - ref.invalidate(channelVideosProvider(widget.channelId)); - }); - } - - void _stopPolling() { - _pollTimer?.cancel(); - _pollTimer = null; - } - - void _checkPollingNeeded(List