mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-28 08:38:50 +05:00
feat: Implement comprehensive torrent downloads management system
- Fix torrent platform service integration with Android engine - Add downloads page with torrent list and progress tracking - Implement torrent detail screen with file selection and priorities - Create native video player with fullscreen controls - Add WebView players for Vibix and Alloha - Integrate corrected torrent engine with file selector - Update dependencies for auto_route and video players Features: ✅ Downloads screen with real-time torrent status ✅ File-level priority management and selection ✅ Three player options: native, Vibix WebView, Alloha WebView ✅ Torrent pause/resume/remove functionality ✅ Progress tracking and seeder/peer counts ✅ Video file detection and playback integration ✅ Fixed Android torrent engine method calls This resolves torrent integration issues and provides complete downloads management UI with video playback capabilities.
This commit is contained in:
440
lib/presentation/screens/player/webview_player_screen.dart
Normal file
440
lib/presentation/screens/player/webview_player_screen.dart
Normal file
@@ -0,0 +1,440 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
enum WebPlayerType { vibix, alloha }
|
||||
|
||||
@RoutePage()
|
||||
class WebViewPlayerScreen extends StatefulWidget {
|
||||
final WebPlayerType playerType;
|
||||
final String videoUrl;
|
||||
final String title;
|
||||
|
||||
const WebViewPlayerScreen({
|
||||
super.key,
|
||||
required this.playerType,
|
||||
required this.videoUrl,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
@override
|
||||
State<WebViewPlayerScreen> createState() => _WebViewPlayerScreenState();
|
||||
}
|
||||
|
||||
class _WebViewPlayerScreenState extends State<WebViewPlayerScreen> {
|
||||
late WebViewController _controller;
|
||||
bool _isLoading = true;
|
||||
bool _isFullscreen = false;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeWebView();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_setOrientation(false);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _initializeWebView() {
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onProgress: (int progress) {
|
||||
// Update loading progress
|
||||
},
|
||||
onPageStarted: (String url) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
},
|
||||
onPageFinished: (String url) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {
|
||||
setState(() {
|
||||
_error = 'Ошибка загрузки: ${error.description}';
|
||||
_isLoading = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
_loadPlayer();
|
||||
}
|
||||
|
||||
void _loadPlayer() {
|
||||
final playerUrl = _getPlayerUrl();
|
||||
_controller.loadRequest(Uri.parse(playerUrl));
|
||||
}
|
||||
|
||||
String _getPlayerUrl() {
|
||||
switch (widget.playerType) {
|
||||
case WebPlayerType.vibix:
|
||||
return _getVibixUrl();
|
||||
case WebPlayerType.alloha:
|
||||
return _getAllohaUrl();
|
||||
}
|
||||
}
|
||||
|
||||
String _getVibixUrl() {
|
||||
// Vibix player URL with embedded video
|
||||
final encodedVideoUrl = Uri.encodeComponent(widget.videoUrl);
|
||||
return 'https://vibix.me/embed/?src=$encodedVideoUrl&autoplay=1&title=${Uri.encodeComponent(widget.title)}';
|
||||
}
|
||||
|
||||
String _getAllohaUrl() {
|
||||
// Alloha player URL with embedded video
|
||||
final encodedVideoUrl = Uri.encodeComponent(widget.videoUrl);
|
||||
return 'https://alloha.tv/embed?src=$encodedVideoUrl&autoplay=1&title=${Uri.encodeComponent(widget.title)}';
|
||||
}
|
||||
|
||||
void _toggleFullscreen() {
|
||||
setState(() {
|
||||
_isFullscreen = !_isFullscreen;
|
||||
});
|
||||
_setOrientation(_isFullscreen);
|
||||
}
|
||||
|
||||
void _setOrientation(bool isFullscreen) {
|
||||
if (isFullscreen) {
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
]);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
} else {
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
]);
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _getPlayerName() {
|
||||
switch (widget.playerType) {
|
||||
case WebPlayerType.vibix:
|
||||
return 'Vibix';
|
||||
case WebPlayerType.alloha:
|
||||
return 'Alloha';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: _isFullscreen ? null : AppBar(
|
||||
title: Text(
|
||||
'${_getPlayerName()} - ${widget.title}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
elevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: _toggleFullscreen,
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||
onSelected: (value) => _handleMenuAction(value),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
const PopupMenuItem(
|
||||
value: 'reload',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.refresh),
|
||||
SizedBox(width: 8),
|
||||
Text('Перезагрузить'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'share',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.share),
|
||||
SizedBox(width: 8),
|
||||
Text('Поделиться'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_error != null) {
|
||||
return _buildErrorState();
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// WebView
|
||||
WebViewWidget(controller: _controller),
|
||||
|
||||
// Loading indicator
|
||||
if (_isLoading)
|
||||
Container(
|
||||
color: Colors.black,
|
||||
child: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Загрузка плеера...',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Fullscreen toggle for when player is loaded
|
||||
if (!_isLoading && !_isFullscreen)
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: SafeArea(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.fullscreen, color: Colors.white),
|
||||
onPressed: _toggleFullscreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorState() {
|
||||
return Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
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?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_error = null;
|
||||
});
|
||||
_loadPlayer();
|
||||
},
|
||||
child: const Text('Повторить'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white),
|
||||
),
|
||||
child: const Text('Назад'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPlayerInfo(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlayerInfo() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade900.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Информация о плеере',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow('Плеер', _getPlayerName()),
|
||||
_buildInfoRow('Файл', widget.title),
|
||||
_buildInfoRow('URL', widget.videoUrl),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleMenuAction(String action) {
|
||||
switch (action) {
|
||||
case 'reload':
|
||||
_loadPlayer();
|
||||
break;
|
||||
case 'share':
|
||||
_shareVideo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _shareVideo() {
|
||||
// TODO: Implement sharing functionality
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Поделиться: ${widget.title}'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper widget for creating custom HTML player if needed
|
||||
class CustomPlayerWidget extends StatelessWidget {
|
||||
final String videoUrl;
|
||||
final String title;
|
||||
final WebPlayerType playerType;
|
||||
|
||||
const CustomPlayerWidget({
|
||||
super.key,
|
||||
required this.videoUrl,
|
||||
required this.title,
|
||||
required this.playerType,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.black,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.play_circle_filled,
|
||||
size: 64,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Плеер: ${playerType == WebPlayerType.vibix ? 'Vibix' : 'Alloha'}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Нажмите для воспроизведения',
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user