import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../../core/constants/app_colors.dart'; import '../../core/constants/app_text_styles.dart'; import '../../data/services/api_service.dart'; import '../widgets/teacher_panel_scaffold.dart'; import 'course_page.dart'; import 'dashboard_page.dart'; import 'license_page.dart'; import 'ticket_page.dart'; import 'transaction_page.dart'; class CourseDetailsPage extends StatefulWidget { final int courseId; const CourseDetailsPage({super.key, required this.courseId}); @override State createState() => _CourseDetailsPageState(); } class _CourseDetailsPageState extends State { bool _isLoading = true; String? _errorMessage; Map? _course; final TextEditingController _videoTitleController = TextEditingController(); List> _courseVideos = >[]; Uint8List? _pickedVideoBytes; String? _pickedVideoName; int? _pickedVideoSize; bool _isUploadingVideo = false; int _uploadPercent = 0; String? _uploadErrorMessage; @override void initState() { super.initState(); _loadCourse(); } @override void dispose() { _videoTitleController.dispose(); super.dispose(); } Future _loadCourse() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final apiService = Provider.of(context, listen: false); final response = await apiService.getCourseById(widget.courseId); final data = response['data']; final course = data is Map ? data : {}; setState(() { _course = course; _isLoading = false; }); _initVideosFromCourse(course); } catch (e) { setState(() { _errorMessage = e.toString(); _isLoading = false; }); } } void _initVideosFromCourse(Map course) { final previewUrl = course['preview_video_url']?.toString(); if (previewUrl == null || previewUrl.isEmpty) { setState(() { _courseVideos = >[]; }); return; } setState(() { _courseVideos = >[ { 'title': 'ویدیوی پیش‌نمایش', 'preview_video_url': previewUrl, 'created_at': '-', 'updated_at': '-', }, ]; }); } Future _pickVideoFile() async { final result = await FilePicker.platform.pickFiles( type: FileType.video, withData: true, ); if (result == null || result.files.isEmpty) { return; } final file = result.files.first; if (file.bytes == null) { setState(() { _uploadErrorMessage = 'خواندن فایل انتخاب‌شده ناموفق بود.'; }); return; } setState(() { _pickedVideoBytes = file.bytes; _pickedVideoName = file.name; _pickedVideoSize = file.size; _uploadErrorMessage = null; }); } Future _uploadVideo() async { final title = _videoTitleController.text.trim(); if (title.isEmpty) { setState(() { _uploadErrorMessage = 'عنوان ویدیو الزامی است.'; }); return; } if (title.length > 255) { setState(() { _uploadErrorMessage = 'حداکثر طول عنوان ویدیو 255 کاراکتر است.'; }); return; } if (_pickedVideoBytes == null) { setState(() { _uploadErrorMessage = 'فایل ویدیو را انتخاب کنید.'; }); return; } setState(() { _isUploadingVideo = true; _uploadPercent = 0; _uploadErrorMessage = null; }); try { final apiService = Provider.of(context, listen: false); final response = await apiService.uploadEncryptedCourseVideo( courseId: widget.courseId, title: title, sourceVideoBytes: _pickedVideoBytes!, onSendProgress: (sent, total) { if (!mounted || total <= 0) return; setState(() { _uploadPercent = ((sent / total) * 100).round(); }); }, ); final data = response['data']; final created = data is Map ? Map.from(data) : {'title': title}; setState(() { _courseVideos = >[created, ..._courseVideos]; _isUploadingVideo = false; _uploadPercent = 100; _videoTitleController.clear(); _pickedVideoBytes = null; _pickedVideoName = null; _pickedVideoSize = null; }); if (!mounted) return; final message = (response['message'] ?? 'ویدیوی رمزنگاری شده با موفقیت دریافت و ثبت شد.') .toString(); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); } catch (e) { setState(() { _isUploadingVideo = false; _uploadErrorMessage = e.toString(); }); } } @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 900; return TeacherPanelScaffold( selectedMenu: PanelMenu.course, onDashboardTap: () { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (context) => const DashboardPage()), ); }, onCourseTap: () { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (context) => const CoursePage()), ); }, onLicenseTap: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => const PanelLicensePage()), ); }, onTransactionTap: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => const TransactionPage()), ); }, onTicketTap: () { Navigator.of( context, ).push(MaterialPageRoute(builder: (context) => const TicketPage())); }, child: Padding( padding: EdgeInsets.all(isDesktop ? 24 : 12), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.35), borderRadius: BorderRadius.circular(10), ), padding: EdgeInsets.all(isDesktop ? 18 : 12), child: _buildContent(isDesktop), ), ), ); } Widget _buildContent(bool isDesktop) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_errorMessage != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( _errorMessage!, style: AppTextStyles.bodyMedium.copyWith(color: Colors.red), textAlign: TextAlign.center, ), const SizedBox(height: 12), OutlinedButton( onPressed: _loadCourse, child: const Text('تلاش مجدد'), ), ], ), ); } if (_course == null || _course!.isEmpty) { return const Center(child: Text('اطلاعات دوره یافت نشد.')); } final title = (_course!['title'] ?? '-').toString(); final description = (_course!['description'] ?? '-').toString(); final price = (_course!['price'] ?? 0).toString(); final coverUrl = _course!['cover_url']?.toString(); final previewVideoUrl = _course!['preview_video_url']?.toString(); final teacher = _extractTeacherName(_course!['teacher_name']); return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ InkWell( onTap: () => Navigator.of(context).pop(), child: const Icon(Icons.arrow_back, color: AppColors.sidebarBg), ), const SizedBox(width: 10), Text( 'نمایش دوره', style: AppTextStyles.headlineMedium.copyWith( color: AppColors.sidebarBg, ), ), ], ), const SizedBox(height: 14), Container(height: 1, color: Colors.black12), const SizedBox(height: 20), if (isDesktop) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 3, child: _buildCover(coverUrl)), const SizedBox(width: 16), Expanded( flex: 5, child: _buildInfoBox( title: title, teacher: teacher, price: price, description: description, previewVideoUrl: previewVideoUrl, ), ), ], ) else ...[ _buildCover(coverUrl), const SizedBox(height: 12), _buildInfoBox( title: title, teacher: teacher, price: price, description: description, previewVideoUrl: previewVideoUrl, ), ], const SizedBox(height: 20), _buildVideoUploadSection(), const SizedBox(height: 12), _buildVideoListSection(), ], ), ); } Widget _buildCover(String? coverUrl) { return Container( height: 280, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black12), color: Colors.white, ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: coverUrl == null || coverUrl.isEmpty ? const Center( child: Icon(Icons.image_not_supported_outlined, size: 56), ) : Image.network( coverUrl, fit: BoxFit.cover, errorBuilder: (_, _, _) => const Center( child: Icon(Icons.broken_image_outlined, size: 56), ), ), ), ); } Widget _buildInfoBox({ required String title, required String teacher, required String price, required String description, required String? previewVideoUrl, }) { return Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _InfoRow(label: 'عنوان دوره', value: title), const SizedBox(height: 10), _InfoRow(label: 'مدرس', value: teacher), const SizedBox(height: 10), _InfoRow(label: 'قیمت', value: '$price تومان'), const SizedBox(height: 10), _InfoRow(label: 'توضیحات', value: description, multiLine: true), const SizedBox(height: 10), _buildPreviewVideoSection(previewVideoUrl), ], ), ); } Widget _buildPreviewVideoSection(String? previewVideoUrl) { final hasUrl = previewVideoUrl != null && previewVideoUrl.isNotEmpty; return Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('لینک ویدیوی پیش‌نمایش', style: AppTextStyles.bodyLarge), const SizedBox(height: 8), SelectableText( hasUrl ? previewVideoUrl : '-', style: AppTextStyles.bodyMedium, ), if (hasUrl) ...[ const SizedBox(height: 8), TextButton.icon( onPressed: () async { await Clipboard.setData(ClipboardData(text: previewVideoUrl)); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('لینک کپی شد.'))); }, icon: const Icon(Icons.copy, size: 16), label: const Text('کپی لینک'), ), ], ], ), ); } Widget _buildVideoUploadSection() { return Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('آپلود ویدیوی جدید', style: AppTextStyles.headlineMedium), const SizedBox(height: 10), TextField( controller: _videoTitleController, decoration: InputDecoration( labelText: 'عنوان ویدیو *', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: Text( _pickedVideoName == null ? 'فایلی انتخاب نشده' : '$_pickedVideoName (${((_pickedVideoSize ?? 0) / (1024 * 1024)).toStringAsFixed(2)} MB)', maxLines: 1, overflow: TextOverflow.ellipsis, style: AppTextStyles.bodyMedium, ), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: _isUploadingVideo ? null : _pickVideoFile, icon: const Icon(Icons.upload_file), label: const Text('انتخاب فایل'), ), ], ), const SizedBox(height: 10), if (_isUploadingVideo) ...[ LinearProgressIndicator(value: _uploadPercent / 100), const SizedBox(height: 6), Text('پیشرفت آپلود: $_uploadPercent%', style: AppTextStyles.bodyMedium), ], if (_uploadErrorMessage != null) ...[ const SizedBox(height: 8), Text( _uploadErrorMessage!, style: AppTextStyles.bodyMedium.copyWith(color: Colors.red), ), ], const SizedBox(height: 10), ElevatedButton.icon( onPressed: _isUploadingVideo ? null : _uploadVideo, icon: const Icon(Icons.cloud_upload_outlined), label: const Text('رمزنگاری و آپلود ویدیو'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.sidebarBg, foregroundColor: Colors.white, ), ), ], ), ); } Widget _buildVideoListSection() { return Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('لیست ویدیوهای آپلودشده', style: AppTextStyles.headlineMedium), const SizedBox(height: 10), if (_courseVideos.isEmpty) Text( 'فعلاً ویدیویی برای این دوره ثبت نشده است.', style: AppTextStyles.bodyMedium, ) else ListView.separated( itemCount: _courseVideos.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), separatorBuilder: (_, _) => const Divider(height: 16), itemBuilder: (context, index) { final item = _courseVideos[index]; final title = (item['title'] ?? 'بدون عنوان').toString(); final createdAt = (item['created_at'] ?? '-').toString(); final previewUrl = item['preview_video_url']?.toString(); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: AppColors.sidebarBg.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.ondemand_video, size: 18), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: AppTextStyles.bodyLarge.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: 4), Text('تاریخ ثبت: $createdAt', style: AppTextStyles.bodyMedium), if (previewUrl != null && previewUrl.isNotEmpty) ...[ const SizedBox(height: 4), SelectableText( previewUrl, style: AppTextStyles.bodyMedium, ), ], ], ), ), ], ); }, ), ], ), ); } String _extractTeacherName(dynamic teacherObj) { if (teacherObj is Map) { final value = teacherObj['user_name']; if (value is String && value.trim().isNotEmpty) { return value; } } return '-'; } } class _InfoRow extends StatelessWidget { final String label; final String value; final bool multiLine; const _InfoRow({ required this.label, required this.value, this.multiLine = false, }); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: multiLine ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ SizedBox( width: 90, child: Text( '$label:', style: AppTextStyles.bodyMedium.copyWith(color: Colors.black54), ), ), Expanded( child: Text( value, maxLines: multiLine ? null : 2, overflow: multiLine ? null : TextOverflow.ellipsis, style: AppTextStyles.bodyLarge, ), ), ], ); } }