diff --git a/lib/presentation/providers/downloads_provider.dart b/lib/presentation/providers/downloads_provider.dart index ee12704..5154d0b 100644 --- a/lib/presentation/providers/downloads_provider.dart +++ b/lib/presentation/providers/downloads_provider.dart @@ -9,10 +9,12 @@ class DownloadsProvider with ChangeNotifier { Timer? _progressTimer; bool _isLoading = false; String? _error; + String? _stackTrace; List get torrents => List.unmodifiable(_torrents); bool get isLoading => _isLoading; String? get error => _error; + String? get stackTrace => _stackTrace; DownloadsProvider() { _startProgressUpdates(); @@ -168,4 +170,8 @@ class DownloadsProvider with ChangeNotifier { _error = error; notifyListeners(); } +}? error) { + _error = error; + notifyListeners(); + } } \ No newline at end of file diff --git a/lib/presentation/providers/movie_detail_provider.dart b/lib/presentation/providers/movie_detail_provider.dart index 1a9cbfb..da8ec8a 100644 --- a/lib/presentation/providers/movie_detail_provider.dart +++ b/lib/presentation/providers/movie_detail_provider.dart @@ -24,6 +24,9 @@ class MovieDetailProvider with ChangeNotifier { String? _error; String? get error => _error; + String? _stackTrace; + String? get stackTrace => _stackTrace; + Future loadMedia(int mediaId, String mediaType) async { _isLoading = true; _isImdbLoading = true; @@ -63,6 +66,7 @@ class MovieDetailProvider with ChangeNotifier { print('Error loading media: $e'); print('Stack trace: $stackTrace'); _error = e.toString(); + _stackTrace = stackTrace.toString(); _isLoading = false; notifyListeners(); } finally { diff --git a/lib/presentation/screens/downloads/downloads_screen.dart b/lib/presentation/screens/downloads/downloads_screen.dart index 58679c9..56bb597 100644 --- a/lib/presentation/screens/downloads/downloads_screen.dart +++ b/lib/presentation/screens/downloads/downloads_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/downloads_provider.dart'; +import '../../widgets/error_display.dart'; import '../../../data/models/torrent_info.dart'; import 'torrent_detail_screen.dart'; @@ -46,37 +47,13 @@ class _DownloadsScreenState extends State { } if (provider.error != null) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.error_outline, - size: 64, - color: Colors.red.shade300, - ), - const SizedBox(height: 16), - Text( - 'Ошибка загрузки', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 8), - Text( - provider.error!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.grey.shade600, - ), - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () { - provider.refreshDownloads(); - }, - child: const Text('Попробовать снова'), - ), - ], - ), + return ErrorDisplay( + title: 'Ошибка загрузки торрентов', + error: provider.error!, + stackTrace: provider.stackTrace, + onRetry: () { + provider.refreshDownloads(); + }, ); } diff --git a/lib/presentation/screens/movie_detail/movie_detail_screen.dart b/lib/presentation/screens/movie_detail/movie_detail_screen.dart index 890694d..4494e9a 100644 --- a/lib/presentation/screens/movie_detail/movie_detail_screen.dart +++ b/lib/presentation/screens/movie_detail/movie_detail_screen.dart @@ -7,6 +7,7 @@ import 'package:neomovies_mobile/presentation/providers/reactions_provider.dart' import 'package:neomovies_mobile/presentation/providers/movie_detail_provider.dart'; import 'package:neomovies_mobile/presentation/screens/player/video_player_screen.dart'; import 'package:neomovies_mobile/presentation/screens/torrent_selector/torrent_selector_screen.dart'; +import 'package:neomovies_mobile/presentation/widgets/error_display.dart'; import 'package:provider/provider.dart'; class MovieDetailScreen extends StatefulWidget { @@ -89,7 +90,15 @@ class _MovieDetailScreenState extends State { } if (provider.error != null) { - return Center(child: Text('Error: ${provider.error}')); + return ErrorDisplay( + title: 'Ошибка загрузки ${widget.mediaType == 'movie' ? 'фильма' : 'сериала'}', + error: provider.error!, + stackTrace: provider.stackTrace, + onRetry: () { + Provider.of(context, listen: false) + .loadMedia(int.parse(widget.movieId), widget.mediaType); + }, + ); } if (provider.movie == null) { diff --git a/lib/presentation/widgets/error_display.dart b/lib/presentation/widgets/error_display.dart new file mode 100644 index 0000000..0a29a41 --- /dev/null +++ b/lib/presentation/widgets/error_display.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Widget that displays detailed error information for debugging +class ErrorDisplay extends StatelessWidget { + final String title; + final String error; + final String? stackTrace; + final VoidCallback? onRetry; + + const ErrorDisplay({ + super.key, + this.title = 'Произошла ошибка', + required this.error, + this.stackTrace, + this.onRetry, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Error icon and title + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 48, + color: Colors.red.shade400, + ), + ], + ), + const SizedBox(height: 16), + + // Title + Text( + title, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.red.shade700, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + + // Error message card + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.red.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.info_outline, size: 20, color: Colors.red.shade700), + const SizedBox(width: 8), + Text( + 'Сообщение об ошибке:', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.red.shade700, + ), + ), + ], + ), + const SizedBox(height: 8), + SelectableText( + error, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 13, + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: error)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ошибка скопирована в буфер обмена'), + duration: Duration(seconds: 2), + ), + ); + }, + icon: const Icon(Icons.copy, size: 18), + label: const Text('Копировать ошибку'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red.shade700, + side: BorderSide(color: Colors.red.shade300), + ), + ), + ), + ], + ), + ], + ), + ), + + // Stack trace (if available) + if (stackTrace != null && stackTrace!.isNotEmpty) ...[ + const SizedBox(height: 16), + ExpansionTile( + title: Row( + children: [ + Icon(Icons.bug_report, size: 20, color: Colors.orange.shade700), + const SizedBox(width: 8), + Text( + 'Stack Trace (для разработчиков)', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.orange.shade700, + ), + ), + ], + ), + backgroundColor: Colors.orange.shade50, + collapsedBackgroundColor: Colors.orange.shade50, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.orange.shade200), + ), + collapsedShape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.orange.shade200), + ), + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade900, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + stackTrace!, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 11, + color: Colors.greenAccent, + ), + ), + const SizedBox(height: 12), + OutlinedButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: stackTrace!)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Stack trace скопирован в буфер обмена'), + duration: Duration(seconds: 2), + ), + ); + }, + icon: const Icon(Icons.copy, size: 18), + label: const Text('Копировать stack trace'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.greenAccent, + side: const BorderSide(color: Colors.greenAccent), + ), + ), + ], + ), + ), + ], + ), + ], + + // Retry button + if (onRetry != null) ...[ + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: onRetry, + icon: const Icon(Icons.refresh), + label: const Text('Попробовать снова'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + ), + ), + ), + ], + ), + ], + + // Debug tips + const SizedBox(height: 24), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.lightbulb_outline, size: 20, color: Colors.blue.shade700), + const SizedBox(width: 8), + Text( + 'Советы по отладке:', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.blue.shade700, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + '• Скопируйте ошибку и отправьте разработчику\n' + '• Проверьте соединение с интернетом\n' + '• Проверьте логи Flutter в консоли\n' + '• Попробуйте перезапустить приложение', + style: TextStyle( + fontSize: 12, + color: Colors.blue.shade900, + height: 1.5, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +}