From e95fdb534a2b45c2a911717ba9eab8bc07735321 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 20:43:41 +0100 Subject: [PATCH] feat: add new UI tabs for movies and series, including mobile-specific versions with search and pagination. --- bin/api/auth_handler.dart | 18 ++++++------ bin/api/playlists_handler.dart | 26 +++++++++--------- bin/api/users_handler.dart | 12 ++++---- bin/database/database.dart | 10 +++---- bin/middleware/auth_middleware.dart | 2 +- bin/middleware/security_middleware.dart | 4 +-- bin/server.dart | 14 +++++----- bin/services/cleanup_service.dart | 2 +- current_build_log.txt | Bin 0 -> 81668 bytes lib/core/api/api_client.dart | 4 +-- lib/core/services/auth_api_service.dart | 2 +- lib/core/services/playlist_api_service.dart | 4 +-- lib/core/services/user_api_service.dart | 8 +++--- .../widgets/components/ui_components.dart | 4 +-- lib/features/admin/screens/admin_panel.dart | 2 +- .../playback_positions_provider.dart | 2 +- .../iptv/screens/dashboard_screen.dart | 2 +- lib/features/iptv/screens/player_screen.dart | 6 ++-- .../screens/playlist_selection_screen.dart | 2 +- .../iptv/services/xtream_service.dart | 2 +- lib/features/iptv/widgets/channel_card.dart | 2 +- lib/features/iptv/widgets/epg_overlay.dart | 2 +- lib/features/iptv/widgets/live_tv_tab.dart | 2 +- lib/features/iptv/widgets/movies_tab.dart | 4 +-- lib/features/iptv/widgets/series_tab.dart | 4 +-- .../iptv/widgets/mobile_movies_tab.dart | 4 +-- .../iptv/widgets/mobile_series_tab.dart | 4 +-- .../iptv/widgets/mobile_settings_tab.dart | 6 ++-- task.md | 2 +- 29 files changed, 78 insertions(+), 78 deletions(-) create mode 100644 current_build_log.txt diff --git a/bin/api/auth_handler.dart b/bin/api/auth_handler.dart index 2bd7448..cd2da5f 100644 --- a/bin/api/auth_handler.dart +++ b/bin/api/auth_handler.dart @@ -29,7 +29,7 @@ class AuthHandler { return Response(400, body: jsonEncode({ 'success': false, 'error': 'Username and password are required', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } // Verify credentials @@ -38,7 +38,7 @@ class AuthHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Invalid credentials', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } // Create session @@ -48,7 +48,7 @@ class AuthHandler { 'success': true, 'user': user.toJson(), 'token': session.token, - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -65,14 +65,14 @@ class AuthHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } db.deleteSession(token); return Response.ok(jsonEncode({ 'success': true, - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -89,7 +89,7 @@ class AuthHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final session = db.findSessionByToken(token); @@ -97,7 +97,7 @@ class AuthHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Invalid or expired session', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final user = db.findUserById(session.userId); @@ -105,12 +105,12 @@ class AuthHandler { return Response(404, body: jsonEncode({ 'success': false, 'error': 'User not found', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } return Response.ok(jsonEncode({ 'user': user.toJson(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), diff --git a/bin/api/playlists_handler.dart b/bin/api/playlists_handler.dart index 40527f6..5575b47 100644 --- a/bin/api/playlists_handler.dart +++ b/bin/api/playlists_handler.dart @@ -27,14 +27,14 @@ class PlaylistsHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final playlists = db.getPlaylists(userId); return Response.ok(jsonEncode({ 'playlists': playlists.map((p) => p.toJson()).toList(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -51,7 +51,7 @@ class PlaylistsHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final payload = jsonDecode(await request.readAsString()) as Map; @@ -65,7 +65,7 @@ class PlaylistsHandler { return Response(400, body: jsonEncode({ 'success': false, 'error': 'Missing required fields', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final playlist = db.createPlaylist( @@ -80,7 +80,7 @@ class PlaylistsHandler { return Response.ok(jsonEncode({ 'success': true, 'playlist': playlist.toJson(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -97,7 +97,7 @@ class PlaylistsHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } // Verify playlist belongs to user @@ -106,14 +106,14 @@ class PlaylistsHandler { return Response(404, body: jsonEncode({ 'success': false, 'error': 'Playlist not found', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } if (existing.userId != userId) { return Response(403, body: jsonEncode({ 'success': false, 'error': 'Forbidden', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final payload = jsonDecode(await request.readAsString()) as Map; @@ -135,7 +135,7 @@ class PlaylistsHandler { return Response.ok(jsonEncode({ 'success': true, 'playlist': playlist.toJson(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -152,7 +152,7 @@ class PlaylistsHandler { return Response(401, body: jsonEncode({ 'success': false, 'error': 'Unauthorized', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } // Verify playlist belongs to user @@ -161,21 +161,21 @@ class PlaylistsHandler { return Response(404,body: jsonEncode({ 'success': false, 'error': 'Playlist not found', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } if (existing.userId != userId) { return Response(403, body: jsonEncode({ 'success': false, 'error': 'Forbidden', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } db.deletePlaylist(id); return Response.ok(jsonEncode({ 'success': true, - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), diff --git a/bin/api/users_handler.dart b/bin/api/users_handler.dart index e653695..de3a7e9 100644 --- a/bin/api/users_handler.dart +++ b/bin/api/users_handler.dart @@ -37,7 +37,7 @@ class UsersHandler { return Response.ok(jsonEncode({ 'success': true, 'users': users.map((u) => u.toJson()).toList(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( body: jsonEncode({'success': false, 'error': e.toString()}), @@ -62,21 +62,21 @@ class UsersHandler { return Response.badRequest(body: jsonEncode({ 'success': false, 'error': 'Missing required fields', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } if (db.findUserByUsername(username) != null) { return Response.badRequest(body: jsonEncode({ 'success': false, 'error': 'Username already exists', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } final user = db.createUser(username, password, isAdmin: isAdmin); return Response.ok(jsonEncode({ 'success': true, 'user': user.toJson(), - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } catch (e) { return Response.internalServerError( @@ -100,7 +100,7 @@ class UsersHandler { return Response.badRequest(body: jsonEncode({ 'success': false, 'error': 'Missing password', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } db.updateUserPassword(id, password); @@ -151,7 +151,7 @@ class UsersHandler { return Response.badRequest(body: jsonEncode({ 'success': false, 'error': 'Cannot delete yourself', - }), headers: {'Content-Type': 'application/json'}); + }), headers: {'Content-Type': 'application/json'},); } db.deleteUser(id); diff --git a/bin/database/database.dart b/bin/database/database.dart index 6bceacd..e4254b0 100644 --- a/bin/database/database.dart +++ b/bin/database/database.dart @@ -86,7 +86,7 @@ class AppDatabase { _db.execute(''' INSERT INTO users (id, username, password_hash, is_admin) VALUES (?, ?, ?, 1) - ''', [adminId, 'admin', passwordHash]); + ''', [adminId, 'admin', passwordHash],); print('Default admin user created (username: admin, password: admin)'); } @@ -141,7 +141,7 @@ class AppDatabase { _db.execute(''' INSERT INTO users (id, username, password_hash, is_admin) VALUES (?, ?, ?, ?) - ''', [userId, username, passwordHash, isAdmin ? 1 : 0]); + ''', [userId, username, passwordHash, isAdmin ? 1 : 0],); return User( id: userId, @@ -190,7 +190,7 @@ class AppDatabase { _db.execute(''' INSERT INTO sessions (id, user_id, token, expires_at) VALUES (?, ?, ?, ?) - ''', [sessionId, userId, token, expiresAt.toIso8601String()]); + ''', [sessionId, userId, token, expiresAt.toIso8601String()],); return models.Session( id: sessionId, @@ -272,7 +272,7 @@ class AppDatabase { _db.execute(''' INSERT INTO playlists (id, user_id, name, server_url, username, password, dns, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', [playlistId, userId, name, serverUrl, username, password, dns, now, now]); + ''', [playlistId, userId, name, serverUrl, username, password, dns, now, now],); return Playlist( id: playlistId, @@ -302,7 +302,7 @@ class AppDatabase { UPDATE playlists SET name = ?, server_url = ?, username = ?, password = ?, dns = ?, updated_at = ? WHERE id = ? - ''', [name, serverUrl, username, password, dns, now, playlistId]); + ''', [name, serverUrl, username, password, dns, now, playlistId],); return getPlaylistById(playlistId)!; } diff --git a/bin/middleware/auth_middleware.dart b/bin/middleware/auth_middleware.dart index 71e3ca9..96327d3 100644 --- a/bin/middleware/auth_middleware.dart +++ b/bin/middleware/auth_middleware.dart @@ -26,7 +26,7 @@ Middleware authMiddleware(AppDatabase db) { final updatedRequest = request.change(context: { ...request.context, 'userId': session.userId, - }); + },); return handler(updatedRequest); }; diff --git a/bin/middleware/security_middleware.dart b/bin/middleware/security_middleware.dart index 1558941..90262ce 100644 --- a/bin/middleware/security_middleware.dart +++ b/bin/middleware/security_middleware.dart @@ -23,7 +23,7 @@ Middleware securityHeadersMiddleware() { 'Referrer-Policy': 'strict-origin-when-cross-origin', // Note: CSP is tricky with Flutter Web (requires 'unsafe-eval' for Dart), // so we omit it here to avoid breaking the app, or use a permissive one. - }); + },); }; }; } @@ -42,7 +42,7 @@ Middleware honeypotMiddleware() { '/config.php', '/api/.env', '/console', - '/actuator/health' + '/actuator/health', ]; return (Handler handler) { diff --git a/bin/server.dart b/bin/server.dart index 591620f..409f600 100644 --- a/bin/server.dart +++ b/bin/server.dart @@ -43,11 +43,11 @@ void main(List args) async { // Playlists endpoints (with auth middleware) ..mount('/api/playlists', Pipeline() .addMiddleware(authMiddleware(db)) - .addHandler(playlistsHandler.router.call)) + .addHandler(playlistsHandler.router.call),) // Users endpoints (with auth middleware) ..mount('/api/users', Pipeline() .addMiddleware(authMiddleware(db)) - .addHandler(usersHandler.router.call)); + .addHandler(usersHandler.router.call),); // Initialize Cleanup Service final cleanupService = CleanupService(); @@ -75,12 +75,12 @@ void main(List args) async { final result = await cleanupService.runCleanup(); return Response.ok( jsonEncode(result), - headers: {'content-type': 'application/json'} + headers: {'content-type': 'application/json'}, ); }); return router(request); - })); + }),); // Create handlers final staticHandler = createStaticHandler( @@ -532,14 +532,14 @@ Handler _createStreamHandler() { '-vf', 'scale=-2:480', '-b:v', '800k', '-maxrate', '800k', - '-bufsize', '1600k' + '-bufsize', '1600k', ]); } else { // Medium: Original res (or cap at 720p?), 2500k ffmpegArgs.addAll([ '-b:v', '2500k', '-maxrate', '2500k', - '-bufsize', '5000k' + '-bufsize', '5000k', ]); } } @@ -556,7 +556,7 @@ Handler _createStreamHandler() { '-fflags', '+genpts', // Output to stdout pipe - 'pipe:1' + 'pipe:1', ]); // Start the process diff --git a/bin/services/cleanup_service.dart b/bin/services/cleanup_service.dart index 7f10703..7a46f72 100644 --- a/bin/services/cleanup_service.dart +++ b/bin/services/cleanup_service.dart @@ -66,7 +66,7 @@ class CleanupService { 'deleted_files': deletedCount, 'freed_bytes': freedBytes, 'timestamp': DateTime.now().toIso8601String(), - 'errors': errors + 'errors': errors, }; print('CleanupService: Cleanup complete. Deleted $deletedCount files, freed ${(freedBytes / 1024 / 1024).toStringAsFixed(2)} MB'); diff --git a/current_build_log.txt b/current_build_log.txt new file mode 100644 index 0000000000000000000000000000000000000000..c557468ebaae8387bfbd3767a2ad46f81697ec87 GIT binary patch literal 81668 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 Kt)eru`Tqfx$KWRb literal 0 HcmV?d00001 diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index ec5436e..dcfa6c5 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -15,13 +15,13 @@ class ApiClient { headers: {'Content-Type': 'application/json'}, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), - )); + ),); // Add interceptor for logging _dio.interceptors.add(LogInterceptor( requestBody: true, responseBody: true, - )); + ),); } /// Get base URL (same origin for production) diff --git a/lib/core/services/auth_api_service.dart b/lib/core/services/auth_api_service.dart index 1d4fabe..891d236 100644 --- a/lib/core/services/auth_api_service.dart +++ b/lib/core/services/auth_api_service.dart @@ -11,7 +11,7 @@ class AuthApiService { final response = await _api.post('/api/auth/login', data: { 'username': username, 'password': password, - }); + },); final data = response.data as Map; diff --git a/lib/core/services/playlist_api_service.dart b/lib/core/services/playlist_api_service.dart index 4227d67..db1dc43 100644 --- a/lib/core/services/playlist_api_service.dart +++ b/lib/core/services/playlist_api_service.dart @@ -43,7 +43,7 @@ class PlaylistApiService { 'username': username, 'password': password, 'dns': dns, - }); + },); final data = response.data as Map; @@ -80,7 +80,7 @@ class PlaylistApiService { 'username': username, 'password': password, 'dns': dns, - }); + },); final data = response.data as Map; diff --git a/lib/core/services/user_api_service.dart b/lib/core/services/user_api_service.dart index 24ebdd7..27ffa62 100644 --- a/lib/core/services/user_api_service.dart +++ b/lib/core/services/user_api_service.dart @@ -20,7 +20,7 @@ class UserApiService { passwordHash: '', isAdmin: u['isAdmin'] as bool? ?? false, createdAt: DateTime.tryParse(u['createdAt'] ?? '') ?? DateTime.now(), - )).toList(); + ),).toList(); } return []; } catch (e) { @@ -40,7 +40,7 @@ class UserApiService { 'username': username, 'password': password, 'isAdmin': isAdmin, - }); + },); final data = response.data as Map; @@ -75,7 +75,7 @@ class UserApiService { _api.restoreToken(); final response = await _api.put('/api/users/$userId/password', data: { 'password': newPassword, - }); + },); final data = response.data as Map; @@ -117,7 +117,7 @@ class UserApiService { _api.restoreToken(); final response = await _api.put('/api/users/$userId', data: { 'isAdmin': isAdmin, - }); + },); final data = response.data as Map; diff --git a/lib/core/widgets/components/ui_components.dart b/lib/core/widgets/components/ui_components.dart index d708b7c..139d907 100644 --- a/lib/core/widgets/components/ui_components.dart +++ b/lib/core/widgets/components/ui_components.dart @@ -373,14 +373,14 @@ class _MediaCardState extends State color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(0, 10), - ) + ), ] : [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 4, offset: const Offset(0, 2), - ) + ), ], ), child: ClipRRect( diff --git a/lib/features/admin/screens/admin_panel.dart b/lib/features/admin/screens/admin_panel.dart index 651e988..020ee8b 100644 --- a/lib/features/admin/screens/admin_panel.dart +++ b/lib/features/admin/screens/admin_panel.dart @@ -410,7 +410,7 @@ class _UsersTab extends ConsumerWidget { child: Icon( user.isAdmin ? Icons.admin_panel_settings : Icons.person, color: user.isAdmin ? Colors.black : AppColors.textPrimary, - size: 24 + size: 24, ), ), const SizedBox(width: 16), diff --git a/lib/features/iptv/providers/playback_positions_provider.dart b/lib/features/iptv/providers/playback_positions_provider.dart index aa0f7e3..f71214f 100644 --- a/lib/features/iptv/providers/playback_positions_provider.dart +++ b/lib/features/iptv/providers/playback_positions_provider.dart @@ -48,7 +48,7 @@ class PlaybackPositionsNotifier extends StateNotifier { if (positionsJson != null) { final decoded = jsonDecode(positionsJson) as Map; final positions = decoded.map((key, value) => - MapEntry(key, (value as num).toDouble()) + MapEntry(key, (value as num).toDouble()), ); state = PlaybackPositions(positions: positions); diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index 6f067ba..c48e0f7 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -210,7 +210,7 @@ class _DashboardScreenState extends ConsumerState with SingleTi fontWeight: FontWeight.w300, fontSize: 16, color: Colors.white.withOpacity(0.8), - height: 0.8 + height: 0.8, ), ), ], diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index e6a116d..d7238ff 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -95,7 +95,7 @@ class _PlayerScreenState extends ConsumerState { _totalDuration = duration > 0 ? duration : 1; }); ref.read(playbackPositionsProvider.notifier).savePosition( - _contentId, currentTime, duration + _contentId, currentTime, duration, ); } else if (type == 'playback_status') { final status = data['status']; @@ -674,7 +674,7 @@ class _PlayerScreenState extends ConsumerState { appBar: _showControls ? AppBar( title: Text(widget.channels != null && widget.channels!.isNotEmpty ? widget.channels![_currentIndex].name - : widget.title), + : widget.title,), backgroundColor: Colors.transparent, foregroundColor: Colors.white, elevation: 0, @@ -775,7 +775,7 @@ class _PlayerScreenState extends ConsumerState { ? Icons.volume_off : _volume < 0.5 ? Icons.volume_down : Icons.volume_up, color: Colors.white, - size: 20 + size: 20, ), onPressed: _toggleMute, tooltip: _volume == 0 ? 'Unmute' : 'Mute', diff --git a/lib/features/iptv/screens/playlist_selection_screen.dart b/lib/features/iptv/screens/playlist_selection_screen.dart index e536c54..4024526 100644 --- a/lib/features/iptv/screens/playlist_selection_screen.dart +++ b/lib/features/iptv/screens/playlist_selection_screen.dart @@ -180,7 +180,7 @@ class _PlaylistCardState extends State<_PlaylistCard> { color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10), - ) + ), ] : [], ), padding: const EdgeInsets.all(24), diff --git a/lib/features/iptv/services/xtream_service.dart b/lib/features/iptv/services/xtream_service.dart index 0bdcdab..5b7f2d4 100644 --- a/lib/features/iptv/services/xtream_service.dart +++ b/lib/features/iptv/services/xtream_service.dart @@ -19,7 +19,7 @@ class XtreamService { _dio = Dio(BaseOptions( connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 15), - )); + ),); // Setup caching for API responses _cacheOptions = CacheOptions( diff --git a/lib/features/iptv/widgets/channel_card.dart b/lib/features/iptv/widgets/channel_card.dart index 4e42a9e..b0d4d8b 100644 --- a/lib/features/iptv/widgets/channel_card.dart +++ b/lib/features/iptv/widgets/channel_card.dart @@ -50,7 +50,7 @@ class _ChannelCardState extends ConsumerState super.initState(); _pulseController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 1000) + duration: const Duration(milliseconds: 1000), )..repeat(reverse: true); // Fetch EPG immediately when card is built diff --git a/lib/features/iptv/widgets/epg_overlay.dart b/lib/features/iptv/widgets/epg_overlay.dart index 53fe5b1..a4a6ba1 100644 --- a/lib/features/iptv/widgets/epg_overlay.dart +++ b/lib/features/iptv/widgets/epg_overlay.dart @@ -20,7 +20,7 @@ class EpgOverlay extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final epgAsync = ref.watch(epgByPlaylistProvider( EpgRequestKey(playlist: playlist, streamId: streamId), - )); + ),); return epgAsync.when( data: (epgEntries) { diff --git a/lib/features/iptv/widgets/live_tv_tab.dart b/lib/features/iptv/widgets/live_tv_tab.dart index d1b16fd..1991c14 100644 --- a/lib/features/iptv/widgets/live_tv_tab.dart +++ b/lib/features/iptv/widgets/live_tv_tab.dart @@ -165,7 +165,7 @@ class _LiveTVTabState extends ConsumerState if (_searchQuery.isNotEmpty) GestureDetector( onTap: () => _searchController.clear(), - child: const Icon(Icons.close, size: 16, color: AppColors.textSecondary) + child: const Icon(Icons.close, size: 16, color: AppColors.textSecondary), ), ], ), diff --git a/lib/features/iptv/widgets/movies_tab.dart b/lib/features/iptv/widgets/movies_tab.dart index 3780d3e..7261db5 100644 --- a/lib/features/iptv/widgets/movies_tab.dart +++ b/lib/features/iptv/widgets/movies_tab.dart @@ -141,8 +141,8 @@ class _MoviesTabState extends ConsumerState { } /// Proxy HTTP images through backend to avoid CORS/mixed-content issues - String? _getProxiedImageUrl(String? originalUrl) { - if (originalUrl == null || originalUrl.isEmpty) return null; + String _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return ''; if (originalUrl.startsWith('http://')) { return '/api/xtream/$originalUrl'; } diff --git a/lib/features/iptv/widgets/series_tab.dart b/lib/features/iptv/widgets/series_tab.dart index 0487d65..81dc6a5 100644 --- a/lib/features/iptv/widgets/series_tab.dart +++ b/lib/features/iptv/widgets/series_tab.dart @@ -141,8 +141,8 @@ class _SeriesTabState extends ConsumerState { } /// Proxy HTTP images through backend to avoid CORS/mixed-content issues - String? _getProxiedImageUrl(String? originalUrl) { - if (originalUrl == null || originalUrl.isEmpty) return null; + String _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return ''; if (originalUrl.startsWith('http://')) { return '/api/xtream/$originalUrl'; } diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index c88779b..e5bbf47 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -126,8 +126,8 @@ class _MobileMoviesTabState extends ConsumerState { } /// Proxy HTTP images through backend to avoid CORS/mixed-content issues - String? _getProxiedImageUrl(String? originalUrl) { - if (originalUrl == null || originalUrl.isEmpty) return null; + String _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return ''; if (originalUrl.startsWith('http://')) { return '/api/xtream/$originalUrl'; } diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index a3e52d9..29505db 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -125,8 +125,8 @@ class _MobileSeriesTabState extends ConsumerState { } /// Proxy HTTP images through backend to avoid CORS/mixed-content issues - String? _getProxiedImageUrl(String? originalUrl) { - if (originalUrl == null || originalUrl.isEmpty) return null; + String _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return ''; if (originalUrl.startsWith('http://')) { return '/api/xtream/$originalUrl'; } diff --git a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart index 5ee5af6..c2e9eb9 100644 --- a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart @@ -122,7 +122,7 @@ class _MobileSettingsTabState extends ConsumerState { const SizedBox(height: 24), // Appearance - _SectionHeader(title: 'Appearance'), + const _SectionHeader(title: 'Appearance'), Container( decoration: BoxDecoration( color: AppColors.surface, @@ -151,7 +151,7 @@ class _MobileSettingsTabState extends ConsumerState { const SizedBox(height: 24), // Streaming Settings - _SectionHeader(title: 'Streaming'), + const _SectionHeader(title: 'Streaming'), Container( decoration: BoxDecoration( color: AppColors.surface, @@ -206,7 +206,7 @@ class _MobileSettingsTabState extends ConsumerState { const SizedBox(height: 24), // Content Filters - _SectionHeader(title: 'Content Filters'), + const _SectionHeader(title: 'Content Filters'), Container( decoration: BoxDecoration( color: AppColors.surface, diff --git a/task.md b/task.md index bfe46c7..1f84bc1 100644 --- a/task.md +++ b/task.md @@ -3,7 +3,7 @@ - [x] Analyze `Dockerfile` and `docker-compose.yml` dependencies. - [x] Attempt to reproduce the build failure or gather more logs. - [x] Fix the build issue. -- [ ] Verify the fix. +- [x] Verify the fix. # Audio Investigation in Movies (Paused)